@cloudbitmaps/core 0.13.0 → 0.15.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-5W3XS2IU.js → chunk-B7IGS3W6.js} +8 -1
- package/dist/chunk-B7IGS3W6.js.map +7 -0
- package/dist/core/chunk-stream.d.ts +22 -0
- package/dist/core/chunk-window.d.ts +8 -2
- package/dist/core/codec.d.ts +11 -0
- package/dist/core/cost.d.ts +12 -7
- package/dist/core/crbm/plan-reads.d.ts +44 -0
- package/dist/core/crbm/reader.d.ts +84 -3
- package/dist/core/crbm-storage-source.d.ts +49 -5
- package/dist/core/engine.d.ts +96 -14
- package/dist/core/item-pull.d.ts +29 -0
- package/dist/core/metrics.d.ts +13 -6
- package/dist/core/pinned-storage-source.d.ts +7 -1
- package/dist/core/ports.d.ts +69 -0
- package/dist/core/validate.d.ts +5 -0
- package/dist/driver-kit.js +2 -2
- package/dist/driver-kit.js.map +1 -1
- package/dist/drivers/retry/retrying-chunk-source.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +951 -145
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/dist/chunk-5W3XS2IU.js.map +0 -7
|
@@ -166,6 +166,12 @@ function validateUserRef(ref) {
|
|
|
166
166
|
validatePart(ref.segment, "segment");
|
|
167
167
|
if (ref.namespace !== void 0) validateUserNamespace(ref.namespace);
|
|
168
168
|
}
|
|
169
|
+
function validateChunkKeyOrder(keys) {
|
|
170
|
+
for (let i = 1; i < keys.length; i++) {
|
|
171
|
+
if (!(keys[i] >= keys[i - 1]))
|
|
172
|
+
throw new ValidationError("chunk keys must be in ascending order");
|
|
173
|
+
}
|
|
174
|
+
}
|
|
169
175
|
function validateChunkRef(ref) {
|
|
170
176
|
validateSegmentRef(ref);
|
|
171
177
|
if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {
|
|
@@ -746,6 +752,7 @@ export {
|
|
|
746
752
|
isReservedNamespace,
|
|
747
753
|
validateUserNamespace,
|
|
748
754
|
validateUserRef,
|
|
755
|
+
validateChunkKeyOrder,
|
|
749
756
|
validateChunkRef,
|
|
750
757
|
mapWithConcurrency,
|
|
751
758
|
assertRegistryCanWrite,
|
|
@@ -775,4 +782,4 @@ export {
|
|
|
775
782
|
namespaceKeyPart,
|
|
776
783
|
namespacePathPart
|
|
777
784
|
};
|
|
778
|
-
//# sourceMappingURL=chunk-
|
|
785
|
+
//# sourceMappingURL=chunk-B7IGS3W6.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/core/errors.ts", "../src/core/name-codec.ts", "../src/core/validate.ts", "../src/core/concurrency.ts", "../src/core/ports.ts", "../src/drivers/_shared/keys.ts", "../src/core/metadata.ts", "../src/core/token.ts", "../src/drivers/_shared/registry.ts", "../src/drivers/_shared/entropy.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Typed errors \u2014 callers learn *why* something failed, never by parsing strings. Retry is not the engine's job: a\n * driver classifies its backend's failures into this vocabulary, and the store's read wrapper retries the\n * transient ones through `withRetry`.\n */\n\n/**\n * Registry-symbol brands, so an error stays classifiable when the class object is not shared.\n *\n * In an ordinary install there is ONE copy of these classes and `instanceof` holds everywhere \u2014 across the\n * flavor, the driver packages and core itself \u2014 because every package leaves `@cloudbitmaps/*` external and\n * resolves core at runtime rather than inlining it.\n *\n * What still breaks it is a SECOND copy: a version skew between our packages that the installer could not\n * dedupe, or a bundler that emits core twice. Then a driver throws a class the catching side's `instanceof`\n * does not match \u2014 and silently, because a `catch` that stops matching just falls through, defeating\n * transient retry and the publish path's conflict handling with no error of its own.\n *\n * `Symbol.for` is identity-stable across copies, bundles and realms, so classify errors with the exported\n * predicates below (never `instanceof`) anywhere an error may cross that boundary.\n */\nconst ERROR_BRAND: unique symbol = Symbol.for('cloudbitmaps.error');\nconst TRANSIENT_BRAND: unique symbol = Symbol.for('cloudbitmaps.error.transient');\n\n/** Base class for every error CloudRoaring throws. */\nexport class CloudRoaringError extends Error {\n /** Cross-bundle brand \u2014 see the predicates ({@link isCloudRoaringError}, \u2026). Non-enumerable-ish (symbol key \u21D2 not in JSON). */\n readonly [ERROR_BRAND] = true as const;\n constructor(message: string) {\n super(message);\n // Subclass name (works under transpilation since we set it explicitly). Also the discriminator the\n // predicates match on \u2014 a runtime string, so it survives bundling where the class identity does not.\n this.name = new.target.name;\n }\n}\n\n/** Invalid caller input (bad id, segment name, options). Raised before any storage call. */\nexport class ValidationError extends CloudRoaringError {}\n\n/** An OCC conditional write/delete lost the race \u2014 the row changed since it was read. */\nexport class WriteConflictError extends CloudRoaringError {}\n\n/** Bytes from a tier are corrupt, oversized, or fail a checksum/format check. */\nexport class IntegrityError extends CloudRoaringError {}\n\n/**\n * A requested object/row does not exist. Part of the driver error vocabulary: every storage driver this\n * library ships, the in-memory one included, throws it for a read of a generation object that is not there.\n * A registry `get` of a row that is not there returns `null` instead.\n */\nexport class NotFoundError extends CloudRoaringError {}\n\n/**\n * This build/configuration cannot perform the requested operation, though nothing is malformed. Two uses:\n * (1) **format** \u2014 the bytes are well-formed but unreadable here (an unknown `.crbm` major version) \u2014\n * distinct from `IntegrityError` (corruption); and (2)\n * **store configuration** \u2014 an operation this store's wiring doesn't support (e.g. a lifecycle helper like\n * `eraseSubject`/`retireExpired` called on a store built without a storage backend). Raised at\n * operation time, before any mutation.\n */\nexport class UnsupportedError extends CloudRoaringError {}\n\n/**\n * A driver cannot meet a capability the chosen topology requires (e.g. a Storage driver without range\n * reads). Raised fail-fast at wiring time, never mid-operation.\n */\nexport class CapabilityError extends CloudRoaringError {}\n\n/**\n * An operation would exceed its per-op **denial-of-wallet budget** \u2014 too many backend requests for a single\n * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` call \u2014 so it is refused **before** fanning out\n * (hard invariant 6). Default-on but generous (normal ops never hit it); tune it\n * per store (`budget`) or per op, or disable with `budget: false`. Deterministic (never retried): the op is too\n * big by policy, not by luck. Each request's bytes are separately capped (the safe-deserialize ceiling), so\n * bounding the request count transitively bounds bytes. Carries the projected count + the limit, never data.\n */\nexport class BudgetExceededError extends CloudRoaringError {}\n\n/**\n * An encrypted segment's data key (DEK) cannot be unwrapped because the keystore holds none of the\n * key-encryption-keys (KEKs) its wrappings reference \u2014 the KEK was never configured, rotated away without\n * keeping the old key, or lost. Deterministic (never retried): without a KEK the ciphertext is unreadable by\n * design. The flip side of crypto-shred \u2014 when this is *intended* (a destroyed segment) the registry row is\n * already a `destroyed` tombstone; when it's *not*, restore the missing KEK (or its recovery KEK). Carries no\n * key material.\n */\nexport class KeyUnavailableError extends CloudRoaringError {}\n\n/**\n * A **transient** infrastructure fault \u2014 throttling, a 5xx, a dropped connection, a client-side request\n * timeout. Retrying a read that failed this way is safe; a write that failed this way may still have landed, so\n * its caller re-runs the call rather than replaying the request. Drivers classify their backend's retryable faults and raise this (the\n * SDK-specific knowledge stays in the SDK-specific driver); the retry layer (`core/retry`) retries **only**\n * this class, never a deterministic error like {@link ValidationError}, {@link IntegrityError},\n * {@link NotFoundError}, or {@link WriteConflictError} (retrying those is pointless or wrong). The original\n * error is preserved in `cause` so callers can still inspect it.\n *\n * Note for logging hygiene: `cause` is the **raw SDK error**, which may carry operational\n * metadata (endpoint host, request IDs, `$metadata`). The library's own `message` is identifier-only and safe\n * to log; if you serialize the whole error *chain*, be aware you're including that metadata.\n */\nexport class TransientError extends CloudRoaringError {\n /** A second brand so a transient fault is classifiable cross-bundle. */\n readonly [TRANSIENT_BRAND] = true as const;\n constructor(message: string, options?: { cause?: unknown }) {\n super(message);\n if (options && 'cause' in options) this.cause = options.cause;\n }\n}\n\n/**\n * Bundle-safe error predicates \u2014 use these, not `instanceof`, wherever an error may cross the core\u2194driver\n * package boundary (and prefer them in consumer `catch` blocks too, for the same reason). They\n * match the {@link ERROR_BRAND} registry brand + the runtime `name`, both of which survive separate bundling.\n */\nfunction hasBrand(err: unknown, brand: symbol): boolean {\n return (\n typeof err === 'object' && err !== null && (err as Record<symbol, unknown>)[brand] === true\n );\n}\n\n/** Any error thrown by CloudRoaring (any tier, any bundle). */\nexport function isCloudRoaringError(err: unknown): err is CloudRoaringError {\n return hasBrand(err, ERROR_BRAND);\n}\n\n/** An OCC conditional write/delete lost the race \u2014 retry the read-modify-write, don't fail. */\nexport function isWriteConflictError(err: unknown): err is WriteConflictError {\n return isCloudRoaringError(err) && err.name === 'WriteConflictError';\n}\n\n/** A retryable transient infrastructure fault. The retry layer keys on this. */\nexport function isTransientError(err: unknown): err is TransientError {\n return hasBrand(err, TRANSIENT_BRAND);\n}\n\n/** A requested object/row does not exist. */\nexport function isNotFoundError(err: unknown): err is NotFoundError {\n return isCloudRoaringError(err) && err.name === 'NotFoundError';\n}\n\n/** Corrupt/oversized/failed-checksum bytes from a tier. */\nexport function isIntegrityError(err: unknown): err is IntegrityError {\n return isCloudRoaringError(err) && err.name === 'IntegrityError';\n}\n\n/** Invalid caller input. */\nexport function isValidationError(err: unknown): err is ValidationError {\n return isCloudRoaringError(err) && err.name === 'ValidationError';\n}\n", "/**\n * Name \u2192 physical-key encoding. The library validates *length*, and encodes everything else.\n *\n * Lives in `core/` rather than beside the drivers because it is pure string logic \u2014 no I/O, no SDK, no\n * `node:` builtin \u2014 and because `validate.ts` needs it to measure a name against the key budget. Core may\n * not import a driver (invariant 7), and a driver may import core, so this is the only layer both can see.\n *\n * A name is **any non-empty string**, and each physical boundary escapes what *it* cannot take literally. An\n * allowlist of characters would reject names for the storage layer's convenience, and the storage layer's\n * convenience is the library's job, not the caller's. It would also let through the names that are actually\n * dangerous: `con`, `nul` and `com1` are Windows device names made only of letters and digits, which pass any\n * allowlist and fail only on a user's machine. A grammar can be both too narrow and too permissive at once,\n * which is the argument against having one.\n *\n * Percent-encoding, because it is the one escape everybody already reads. `%` escapes itself as `%25` and is\n * encoded **first**, which is what makes the transform injective: every `%` in an encoded string starts an\n * escape, so decoding is unambiguous and two distinct names can never collide on one key.\n *\n * There are two alphabets, because the boundaries differ:\n *\n * - **Object keys** (S3, GCS, Azure Blob) take almost anything. They need `/` escaped so a name cannot\n * invent hierarchy or break a key parser that splits on it, and control characters because they are not\n * legal in the XML an S3 LIST returns. Every other character outside `[A-Za-z0-9._:-]` is escaped as well.\n * - **Filesystem paths** need all of that plus `:` (an NTFS alternate-data-stream separator, where a write can\n * *succeed* while `readdir` never lists the result), plus three hazards that are about the component as a\n * whole rather than its characters \u2014 see {@link encodeNameForPath}.\n *\n * **The key alphabet is fixed for good.** Adding a character to it would change what `encodeNameForKey` emits\n * for every name containing that character, silently moving those segments to keys nothing looks up. A test pins\n * it, one printable ASCII character at a time.\n *\n * **A plain name encodes to itself on the OBJECT-KEY alphabet** \u2014 one made only of `[A-Za-z0-9._:-]` that\n * starts with a letter or digit \u2014 so S3, GCS and Azure keys read as the names that made them. That is the\n * property to preserve if this file is ever edited, and a property test asserts it over every plain name.\n *\n * The **path** alphabet escapes more. Besides `:`, two classes of plain name are escaped on a path: a Windows\n * device-name stem (`con`, `nul`, `com1`, `con.backup`) and a trailing dot (`a.`), since both silently alias\n * or fail on Windows.\n */\n\n/** Characters safe in an object key, left literal so a plain name reads as itself in the bucket. */\nconst KEY_SAFE = /[A-Za-z0-9._:-]/;\n/** Characters safe in a path component. As {@link KEY_SAFE} minus `:`. */\nconst PATH_SAFE = /[A-Za-z0-9._-]/;\n\nconst UTF8 = new TextEncoder();\n\n/**\n * Percent-encode one character's UTF-8 bytes: `\u00E9` \u2192 `%C3%A9`.\n *\n * Only ever called with a WELL-FORMED code point. `TextEncoder` maps an unpaired surrogate to U+FFFD, which\n * would make every surrogate \u2014 and U+FFFD itself \u2014 encode identically and silently break injectivity, so\n * `validate.ts` refuses a name that is not well-formed UTF-16 before any of this runs.\n */\nfunction escapeChar(ch: string): string {\n let out = '';\n for (const byte of UTF8.encode(ch)) {\n out += `%${byte.toString(16).toUpperCase().padStart(2, '0')}`;\n }\n return out;\n}\n\nfunction encodeWith(name: string, safe: RegExp): string {\n let out = '';\n // Iterate by code POINT, not code unit: a surrogate pair is one character and must encode as one UTF-8\n // sequence. Indexing by `charAt` would split an emoji into two unpaired halves and produce garbage bytes.\n for (const ch of name) {\n out += ch !== '%' && safe.test(ch) ? ch : escapeChar(ch);\n }\n // A LEADING underscore is escaped, which is what keeps the reserved namespaces reachable only by us.\n //\n // `_default` is the physical stand-in for an absent namespace. Unescaped, it would hand any caller a way to\n // name a namespace `_default` and have it resolve to everyone else's un-namespaced data. Escaping the first\n // `_` makes the sentinel unreachable by construction rather than by a blocklist that has to be maintained.\n return out.startsWith('_') ? `%5F${out.slice(1)}` : out;\n}\n\nfunction decodePercent(encoded: string): string {\n const bytes: number[] = [];\n for (let i = 0; i < encoded.length;) {\n if (encoded[i] === '%' && i + 2 < encoded.length + 1) {\n const hex = encoded.slice(i + 1, i + 3);\n if (!/^[0-9A-Fa-f]{2}$/.test(hex)) return encoded; // not an escape we wrote\n bytes.push(parseInt(hex, 16));\n i += 3;\n } else {\n for (const b of new TextEncoder().encode(encoded[i])) bytes.push(b);\n i += 1;\n }\n }\n // `ignoreBOM: true` is load-bearing, not a tidy-up. Without it `TextDecoder` STRIPS a leading U+FEFF, so\n // `%EF%BB%BForders` decodes to `orders` \u2014 a different, probably existing, segment. That is a collision in\n // the one direction everything else here rests on, and it is reachable: `validatePart` accepts U+FEFF, and\n // a BOM-prefixed name is what any spreadsheet export or CSV read without BOM stripping hands you.\n //\n // The visible symptom was worse than a failed round-trip. Each parser (`parseRegistryRow`,\n // `parseNamespaceDir`, `parseRegistryKey`) re-encodes what it decoded and refuses a spelling it cannot\n // reproduce, so a BOM-prefixed row could be WRITTEN and then silently skipped by `list()`,\n // `checkConsistency`, `exportSegments`, the retention sweep and subject erasure \u2014 present in the bucket and\n // invisible to every sweep over it.\n //\n // Decode-only, so nothing that works today changes: `encode` is untouched, no stored key moves, and the\n // only names whose decoding changes are ones that are currently rejected by that round-trip guard anyway.\n return new TextDecoder('utf-8', { fatal: false, ignoreBOM: true }).decode(new Uint8Array(bytes));\n}\n\n/**\n * Encode a name for use inside an **object key** (S3, GCS, Azure Blob).\n *\n * Leaves `[A-Za-z0-9._:-]` literal, except a leading `_` and a name that is only `.` or `..`, so a plain name\n * reads as itself, and escapes everything else \u2014 including `/` (which would invent hierarchy and break a parser\n * that splits on it) and control characters (not legal in S3's XML responses).\n */\nexport function encodeNameForKey(name: string): string {\n return defuseDotComponent(encodeWith(name, KEY_SAFE));\n}\n\n/**\n * Escape a component that is exactly `.` or `..`.\n *\n * Applied to **both** alphabets. It is obviously needed for a filesystem path, and it is needed for an object\n * key too: the library's own S3/GCS/Azure calls treat the key space as flat, but the moment those keys touch\n * something hierarchical the component resolves. `aws s3 sync`, `gsutil -m cp -r` and `azcopy` all write\n * `dest/tenant/../segments/\u2026`, which lands *outside* `tenant/`; s3fs and gcsfuse mounts do the same; and on\n * ADLS Gen2, where the namespace really is hierarchical, `prefix/../x` and `x` are the same object. Those are\n * the DR-restore and eject paths this library documents.\n *\n * `normalizeS3Prefix` refuses `.`/`..` segments in a **prefix**, which is trusted config; the name, the input an\n * attacker can influence, is escaped here instead.\n *\n * Every dot is escaped rather than just the first, so the result cannot end in one either.\n */\nfunction defuseDotComponent(encoded: string): string {\n return encoded === '.' || encoded === '..' ? encoded.replaceAll('.', '%2E') : encoded;\n}\n\n/** Inverse of {@link encodeNameForKey}. */\nexport function decodeNameFromKey(encoded: string): string {\n return decodePercent(encoded);\n}\n\n/**\n * Encode a name for use as a **filesystem path component**.\n *\n * Everything {@link encodeNameForKey} escapes, plus `:`, plus three hazards that are properties of the whole\n * component rather than of any character in it. All three are silent corruption rather than errors, which is\n * why they are handled here instead of being left to the OS:\n *\n * 1. **`.` and `..`** are traversal. `join(root, '..')` leaves the storage root.\n * 2. **Windows reserved device names** \u2014 `CON`, `PRN`, `AUX`, `NUL`, `COM1`\u2013`COM9`, `LPT1`\u2013`LPT9`, case\n * insensitive and *with or without an extension*, so `con.0.crbm` is reserved too. Opening one addresses a\n * device rather than a file, so `store.segment('con')` would break only on a user's Windows box.\n * 3. **A trailing `.` or space**, which Windows silently strips \u2014 so `a.` and `a` would become the same\n * directory, quietly merging two segments.\n *\n * Each is fixed by escaping the one character that defuses it, which keeps the encoding injective: a name that\n * needed defusing produces a `%XX` no other name produces.\n */\nexport function encodeNameForPath(name: string): string {\n let encoded = encodeWith(name, PATH_SAFE);\n\n // The traversal case may return on its own only because `defuseDotComponent` escapes EVERY dot. Escaping just\n // the first would turn `..` into `%2E.`, which ends in a dot: hazard 3, reintroduced by the fix for hazard 1.\n const dotted = defuseDotComponent(encoded);\n if (dotted !== encoded) return dotted;\n\n // A Windows device name is reserved with OR without an extension, so the test is on the stem.\n const stem = encoded.split('.')[0] ?? '';\n if (/^(?:CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i.test(stem)) {\n encoded = escapeChar(encoded[0] as string) + encoded.slice(1);\n }\n\n // Windows strips a trailing dot, which would alias two distinct names onto one path. A trailing SPACE is the\n // same hazard but needs no arm here: space is not in `PATH_SAFE`, so the charset already escaped it to `%20`.\n if (encoded.endsWith('.')) encoded = `${encoded.slice(0, -1)}%2E`;\n\n return encoded;\n}\n\n/** Inverse of {@link encodeNameForPath}. */\nexport function decodeNameFromPath(encoded: string): string {\n return decodePercent(encoded);\n}\n", "/**\n * Boundary validation for segment / namespace names.\n *\n * **A name is any non-empty string.** There is no character allowlist, because an allowlist rejects names for\n * the storage layer's convenience \u2014 and the storage layer's convenience is this library's problem, not the\n * caller's. Every physical boundary escapes what *it* cannot take literally (`core/name-codec.ts`),\n * so `dedup:2026-08-01`, `user@example.com`, `orders/2026`, `\u65E5\u672C\u8A9E` and `100%` are all ordinary names.\n *\n * A grammar would fail in both directions. It would reject names that are merely unfamiliar, and it would pass\n * names that are genuinely broken \u2014 `con`, `nul` and `com1` are Windows device names made only of letters and\n * digits, so `store.segment('con')` would validate cleanly and fail only on a user's machine. Encoding handles\n * both: it accepts what is merely unfamiliar, and defuses what is actually dangerous.\n *\n * What remains is **size** and **representability**, both real constraints rather than tastes.\n *\n * Size: S3 caps an object key at 1024 bytes and a name is only one part of it. The cap is applied to the\n * **encoded** length \u2014 and to the longer of the two encodings, since a path escapes `:` three-for-one where a\n * key leaves it literal \u2014 because a limit measured on the input would let a key exceed the backend's.\n *\n * Representability: a name must be well-formed UTF-16, because an unpaired surrogate has no UTF-8 encoding at\n * all. Letting one through would not merely store oddly; every lone surrogate encodes to the same replacement\n * bytes, so four distinct names would claim one key.\n */\nimport { ValidationError } from './errors';\nimport { encodeNameForKey, encodeNameForPath } from './name-codec';\nimport type { ChunkRef, SegmentRef } from './ports';\n\nconst CHUNK_KEY_MAX = 0xffff;\n\n/**\n * The most an encoded name may occupy in a physical key.\n *\n * S3's limit is 1024 bytes for the WHOLE key, which also carries the caller's prefix, the namespace, a fixed\n * infix (`/segments/`) and the `.<generation>.crbm` suffix. 256 leaves generous room for all of that with both\n * a namespace and a segment at the ceiling.\n */\nexport const MAX_NAME_LENGTH = 256;\n\n/**\n * A high surrogate not followed by a low one, or a low surrogate not preceded by a high one.\n *\n * Deliberately NOT `/u` and not `String.prototype.isWellFormed()`: the flag would make the engine read\n * code points, which is the very distinction being tested, and the method needs an ES2024 lib target\n * this package does not set. Matching on code units is what sees a half of a pair.\n */\nconst LONE_SURROGATE = /[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/;\n\n/** Whether `value` is well-formed UTF-16: no unpaired surrogate, so it survives a round trip through UTF-8. */\nexport function isWellFormedString(value: string): boolean {\n return !LONE_SURROGATE.test(value);\n}\n\n/**\n * How long a name is once storage has it \u2014 the longer of the two encodings.\n *\n * Exported because anything that composes a name out of other names (the due index does) has to measure it the\n * same way, and a second copy of that rule is how the two drifted apart before.\n */\nexport function encodedNameLength(value: string): number {\n return Math.max(encodeNameForKey(value).length, encodeNameForPath(value).length);\n}\n\nfunction validatePart(value: string, field: string): void {\n if (typeof value !== 'string') {\n throw new ValidationError(`${field} must be a string; got ${typeof value}`);\n }\n if (value.length === 0) {\n throw new ValidationError(`${field} must not be empty`);\n }\n // Cheap bound BEFORE encoding. Encoding never shrinks a string, so a name already over the cap in raw units\n // is over it encoded \u2014 and rejecting first means an attacker-supplied name cannot make us build a 60 MB\n // string to find out. (Measured: a 10-million-unit name cost 4.2 s of blocking CPU without this.)\n if (value.length > MAX_NAME_LENGTH) {\n throw new ValidationError(\n `${field} is too long: ${value.length} characters (limit ${MAX_NAME_LENGTH} once encoded for a storage key)`,\n );\n }\n // A name has to survive the round trip to UTF-8 and back. An unpaired surrogate does not: `TextEncoder`\n // replaces it with U+FFFD, so every lone surrogate AND U+FFFD itself would encode to the same bytes \u2014 four\n // distinct names collapsing onto one key, which is the one property everything else here rests on. There is\n // no UTF-8 for a lone surrogate, so this is a limit of the medium rather than a rule we chose.\n if (LONE_SURROGATE.test(value)) {\n throw new ValidationError(\n `${field} contains an unpaired surrogate, so it has no UTF-8 encoding and cannot be stored. This is ` +\n `usually a string sliced through an astral character (an emoji, say) \u2014 slice by code point instead.`,\n );\n }\n // Measured on the ENCODED form, because that is what a bucket has to hold \u2014 and on the LONGER of the two\n // encodings. A path escapes `:` three-for-one where a key leaves it literal, so measuring the key form alone\n // let `'a' + ':'.repeat(255)` pass the boundary and then fail inside the driver with a raw ENAMETOOLONG\n // rather than a typed error at the edge.\n const encoded = encodedNameLength(value);\n if (encoded > MAX_NAME_LENGTH) {\n throw new ValidationError(\n `${field} is too long: ${encoded} characters once encoded for storage (limit ${MAX_NAME_LENGTH}). The name ` +\n `itself is ${value.length} characters \u2014 encoding expands anything outside [A-Za-z0-9._-], so a name of ` +\n `mostly non-ASCII text, or one full of colons, reaches the limit sooner than its length suggests.`,\n );\n }\n}\n\nexport function validateSegmentRef(ref: SegmentRef): void {\n validatePart(ref.segment, 'segment');\n if (ref.namespace !== undefined) validatePart(ref.namespace, 'namespace');\n}\n\n/**\n * The namespace prefix the library keeps for its own bookkeeping: the due index stores one pointer row per\n * expiring segment in `cbm.due.<bucket>` (see `due-index.ts`). **One constant, read by both sides** \u2014 the\n * application-facing checks below refuse it in a name an application passes in, and the due index writes and\n * recognises its rows by it \u2014 so the two cannot drift.\n *\n * Only this exact prefix is reserved, not `cbm.` as a whole: `cbm.dueX`, `cbm.due` and `cbmdue.eu` are ordinary\n * namespaces. Every fleet-wide enumeration skips the rows of a reserved namespace as bookkeeping, so a segment\n * there would be invisible to an erasure, a consistency check, an export and a retention sweep.\n *\n * Internal: not exported from any entry. The flavor keeps a copy for its own facade checks, and a test pins the\n * two together.\n */\nexport const RESERVED_NAMESPACE_PREFIX = 'cbm.due.';\n\n/** Does this namespace start with {@link RESERVED_NAMESPACE_PREFIX}? `undefined` (no namespace) never does. */\nexport function isReservedNamespace(namespace: string | undefined): boolean {\n return namespace !== undefined && namespace.startsWith(RESERVED_NAMESPACE_PREFIX);\n}\n\n/**\n * Validate a namespace an application names, as a segment ref's namespace or as a `namespace` option that scopes\n * a scan: the name rules, and **not** in the reserved namespace. Internal; the public `validateSegmentRef` of\n * `@cloudbitmaps/core/driver-kit` is the name rules only, which is what a driver needs, since the due index's own\n * rows live in the reserved namespace and go through the drivers.\n */\nexport function validateUserNamespace(namespace: string): void {\n validatePart(namespace, 'namespace');\n if (isReservedNamespace(namespace)) {\n throw new ValidationError(\n `namespace \"${namespace}\" is reserved: names starting with \"${RESERVED_NAMESPACE_PREFIX}\" hold the ` +\n `library's own bookkeeping rows, which every fleet-wide scan skips, so a segment there would be ` +\n `invisible to an erasure, a consistency check, an export and a retention sweep. Choose another namespace.`,\n );\n }\n}\n\n/** Validate a segment ref an application hands in: the name rules, and a namespace outside the reserved prefix. */\nexport function validateUserRef(ref: SegmentRef): void {\n validatePart(ref.segment, 'segment');\n if (ref.namespace !== undefined) validateUserNamespace(ref.namespace);\n}\n\n/**\n * Validate the keys of a `getChunks` stream: in ascending order, a key may repeat. Checked before anything is\n * resolved or read, so a caller's mistake costs no request.\n */\nexport function validateChunkKeyOrder(keys: readonly number[]): void {\n for (let i = 1; i < keys.length; i++) {\n if (!(keys[i]! >= keys[i - 1]!))\n throw new ValidationError('chunk keys must be in ascending order');\n }\n}\n\n/** Validate a chunk ref: the segment/namespace rules plus `chunkKey` \u2208 `[0, 65535]` (a u16). A driver-boundary check. */\nexport function validateChunkRef(ref: ChunkRef): void {\n validateSegmentRef(ref);\n if (!Number.isInteger(ref.chunkKey) || ref.chunkKey < 0 || ref.chunkKey > CHUNK_KEY_MAX) {\n throw new ValidationError(\n `chunkKey must be an integer in [0, ${CHUNK_KEY_MAX}]; got ${ref.chunkKey}`,\n );\n }\n}\n", "/**\n * Bounded-concurrency fan-out \u2014 the missing primitive for running async work over many items without either\n * a thundering herd (unbounded `Promise.all` \u2192 N simultaneous object-store calls) or full serial latency\n * (`for await`). Pure + I/O-free, so it lives in `core/`; callers inject the async work.\n */\n\nimport { ValidationError } from './errors';\n\n/**\n * Run `fn` over `items` with at most `limit` promises in flight at once, returning results in **input order**\n * (regardless of completion order). If any `fn` rejects, the pool stops scheduling new work, lets the in-flight\n * tasks settle, and rejects with the **first** error \u2014 callers that must not abort on one failure (e.g. an\n * erasure ledger that records per-item faults) should catch inside `fn` so it never rejects.\n *\n * `items` is a materialized array on purpose: every caller here already enumerates its source (registry list,\n * S3 page, chunk map), so an array keeps the pool trivial and race-free (no shared-iterator hazard).\n */\nexport async function mapWithConcurrency<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T, index: number) => Promise<R>,\n): Promise<R[]> {\n if (!Number.isInteger(limit) || limit < 1) {\n throw new ValidationError(`concurrency limit must be a positive integer; got ${limit}`);\n }\n const results = new Array<R>(items.length);\n let next = 0;\n let failed = false;\n let firstError: unknown;\n const worker = async (): Promise<void> => {\n while (!failed) {\n const i = next++;\n if (i >= items.length) return;\n try {\n results[i] = await fn(items[i]!, i);\n } catch (err) {\n if (!failed) {\n failed = true; // first failure wins; stop scheduling new items (in-flight ones still settle)\n firstError = err;\n }\n return;\n }\n }\n };\n const workers = Array.from({ length: Math.min(limit, items.length) }, () => worker());\n await Promise.all(workers);\n if (failed) throw firstError;\n return results;\n}\n", "/**\n * Storage-driver contracts the engine depends on.\n *\n * Two tiers and one pointer. **Storage** is immutable object storage holding `.crbm` generations (`IStorageDriver`,\n * read by the engine through the per-chunk `StorageChunkSource` view the `.crbm` reader implements); the\n * **registry** is the authoritative per-segment record \u2014 which generation is current, the wrapped DEKs, the\n * governance metadata \u2014 under optimistic concurrency (`IRegistryDriver`). Drivers move opaque bytes and OCC\n * tokens; they never understand roaring or the `.crbm` layout.\n */\n\nimport { UnsupportedError, ValidationError } from './errors';\nimport type { BlobSink } from './blob';\nimport type { WrappedDek } from './crypto';\n\n/** Opaque optimistic-concurrency token \u2014 unique per write, compared by equality only. */\nexport type Token = string;\n\n/**\n * The address of one segment: a `namespace` and a `segment` name, both any non-empty string of at most 256\n * characters once encoded for storage. Omitting the namespace addresses a different segment from one that names it.\n *\n * **A namespace starting with `cbm.due.` is reserved**, and every call of the store, and of the free functions\n * behind it, that takes a ref or a `namespace` option throws `ValidationError` for one. The library keeps the due\n * index there (one pointer row per expiring segment), and every fleet-wide scan skips those rows as bookkeeping, so a\n * segment in such a namespace would be invisible to an erasure, a consistency check, an export and a retention sweep.\n * A driver takes the prefix, since the due index's own rows go through it. Only that exact prefix is reserved:\n * `cbm.dueX` and `cbmdue.eu` are ordinary namespaces, and a segment name is never restricted.\n */\nexport interface SegmentRef {\n readonly namespace?: string;\n readonly segment: string;\n}\n\nexport interface ChunkRef extends SegmentRef {\n readonly chunkKey: number;\n}\n\n/** Identifies one immutable `.crbm` object \u2014 a single generation of a segment. */\nexport interface GenKey extends SegmentRef {\n readonly generation: number;\n}\n\n/**\n * A segment's grounded on-disk footprint \u2014 the current generation's Storage object bytes, read cheaply from the\n * `.crbm` footer/index (no payload reads). Powers the grounded `costReport()`.\n */\nexport interface SegmentSize {\n readonly sizeBytes: number;\n}\n\n/**\n * One chunk of a {@link StorageChunkSource.getChunks} stream: the key asked for, its bytes (`null` where the generation\n * holds none), and the version of the generation the bytes were read from, or `null` when the segment has no\n * generation. The version is what {@link StorageChunkSource.currentVersion} reports for that generation, so a caller\n * can key what it keeps by the generation the bytes came from rather than the one it resolved earlier.\n */\nexport interface ChunkRead {\n readonly key: number;\n readonly bytes: Uint8Array | null;\n readonly version: string | null;\n}\n\n/** Options of {@link StorageChunkSource.getChunks}. */\nexport interface ReadChunksOptions {\n /**\n * Runs one storage request, and may run it again if it fails. A source passes each request it makes through\n * this, so a caller that retries transient faults repeats the one request that failed, not the ones that landed.\n * Absent, each request runs once.\n */\n readonly retry?: <T>(request: () => Promise<T>) => Promise<T>;\n /**\n * How many range requests the stream holds ahead of its consumer, in flight or landed and not yet taken: a positive\n * integer, 32 by default. It bounds the requests in flight, and the memory the stream holds, at this many times the\n * largest range (1 MiB, 1 MiB + 28 B for one encrypted chunk).\n */\n readonly concurrency?: number;\n /**\n * Open the window 1, 2, 4 \u2026 ranges wide up to `concurrency` instead of at full width, so a reader that stops early\n * has asked for little; a number starts it that wide instead of at 1 (a positive integer), and it doubles from there.\n */\n readonly ramp?: boolean | number;\n /**\n * Called once for every range request the stream sends, when it settles: a request the consumer took, one that was\n * still in flight when the consumer stopped, and one that failed. `bytes` is what the request moved, the gaps between\n * the chunks it carried included (0 when it failed), and `ms` how long it took, retries included (0 when the source\n * has no clock). It is how a caller counts what it is billed for, since several chunks can share a request and\n * a stopped stream leaves some in flight. A throw from it is ignored.\n */\n readonly onRequest?: (request: { readonly bytes: number; readonly ms: number }) => void;\n}\n\n/** Per-chunk read view of the immutable Storage tier (implemented by the `.crbm` reader). */\nexport interface StorageChunkSource {\n /** Read-only bytes for the chunk, or `null` if absent. Callers must not mutate the buffer. */\n getChunk(ref: ChunkRef): Promise<Uint8Array | null>;\n listChunkKeys(ref: SegmentRef): Promise<number[]>;\n /**\n * Optional: several chunks of one segment as a stream, read **from one generation** with fewer storage requests than\n * one per chunk where the chunks sit near each other in the object. `keys` are chunk keys in ascending order (a key\n * may repeat; any other order is a `ValidationError`). The stream yields one {@link ChunkRead} per position of\n * `keys`, in that order, as the requests that carry them land; a key the generation does not hold yields `null`\n * bytes, as {@link StorageChunkSource.getChunk} answers. Each chunk is checked as a read of it alone is.\n *\n * **Nothing is resolved or read until the first chunk is asked for**, and the source holds at most\n * {@link ReadChunksOptions.concurrency} ranges at a time (in flight, or landed and not yet taken), however many keys\n * there are: a consumer that is slow holds the stream back, and one that stops early (`break`, or `return()`) stops\n * the reads, so the requests it had already started (at most that many) finish and are dropped, and are not retried.\n * A stream that fails raises at once and sends nothing further, and the requests it left in flight finish in the\n * background and never raise. A plain chunk may be a view into a buffer of up to 1 MiB that it shares with its\n * neighbours (and the same view at two positions for a key asked twice), so do not write to it, and copy a chunk to\n * keep it.\n *\n * One stream reads one generation. If that generation is swept, or its object replaced, while the stream runs, a\n * source may re-resolve the segment and continue with the keys not yet yielded from the generation that is current\n * then; each chunk says which version it came from, and each is whole and verified either way. Before it does, it\n * waits for the requests the failed stream left in flight, so a heal never opens a second window beside them: a\n * request that never answers delays a heal, as it would hang a read of one chunk, until the driver's read timeout (if\n * it has one) ends it, and never delays an error. A source that reads a pinned generation never heals.\n *\n * A source that cannot read a range of an object omits this, and a caller reads chunk by chunk; the `.crbm`\n * source, which is what the stores the library ships read through, implements it.\n */\n getChunks?(\n ref: SegmentRef,\n keys: readonly number[],\n options?: ReadChunksOptions,\n ): AsyncIterable<ChunkRead>;\n /**\n * Optional: the current generation's grounded size, cheaply (from the already-parsed `.crbm` index \u2014 no\n * payload reads), or `null` if the segment has no Storage generation. Powers the grounded `costReport()`.\n */\n sizeOf?(ref: SegmentRef): Promise<SegmentSize | null>;\n /**\n * Optional: how often this source re-reads a segment's pointer while the segment is being read, in ms, or 0 if\n * it never does. The grounded `costReport()` prices the pointer refresh at this interval; a source that omits it\n * is priced at the store's default.\n */\n readonly pointerRefreshMs?: number;\n /**\n * Optional: the current generation's **per-chunk cardinality** (`chunkKey \u2192 count`), read from the\n * already-parsed `.crbm` index with **no payload reads**, or `null` if the segment has no Storage generation.\n * Powers the free `count()` of a source with no {@link StorageChunkSource.summary} \u2014 the engine sums the index\n * instead of fetching a single chunk. A source with no\n * index (e.g. the in-memory source) omits this, and `count()` falls back to fetching every chunk.\n */\n cardinalities?(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;\n /**\n * Optional: the current generation's number, id count and metadata, or `null` if the segment has no Storage\n * generation. The `.crbm` source answers from the registry row's summary when it can use it, which is one\n * registry read and no read of the object, and from the opened generation when it cannot. A source with no\n * summary omits this, and `count()` keeps its other paths.\n */\n summary?(ref: SegmentRef): Promise<GenerationSummary | null>;\n /**\n * Optional: the segment's **current generation number** as this source resolves it right now (registry\n * `currentGen`, or the highest storage generation), or `null` if the segment has no Storage generation. The engine\n * keys its chunk cache by this so a generation bump (a load's publish) is observed \u2014 a new generation\n * misses the cache instead of serving a stale decoded chunk, and an erased id can't resurrect from a cached\n * superseded chunk. Cheap: served from the source's own (short-TTL-refreshed) snapshot, **not** a fresh\n * backend read per call. A source that only ever serves one immutable generation may omit this \u2014 the engine\n * then keys the cache without a generation.\n */\n currentGeneration?(ref: SegmentRef): Promise<number | null>;\n /**\n * Optional: forget everything this source has derived from `ref` \u2014 its resolved snapshot, its open reader,\n * and any key material that reader captured \u2014 so the next read resolves from the registry again.\n *\n * The TTL refresh and the generation-keyed chunk cache are both built for **a publish that advances\n * `currentGen`**. Neither covers an event that *destroys* what the cache was derived from: an erasure that\n * deletes the generation holding the bit, a `dropSegment`, a crypto-shred, a retirement. After one of those\n * a source that had already resolved the segment keeps answering from memory \u2014 with no backend read at all,\n * so no storage-side control can close the window \u2014 until its TTL lapses, and with no bound at all if it has\n * no registry, no clock or `cache.genTtlMs: 0`.\n *\n * Callers that destroy or retire a segment must call this. It is synchronous and best-effort: dropping\n * memoized state cannot fail, and a source that memoizes nothing may omit the method entirely.\n */\n invalidate?(ref: SegmentRef): void;\n /**\n * Optional: does this segment **exist as a registered name**, as distinct from resolving to no generation?\n *\n * `currentGeneration` collapses two very different states into `null`: a name nobody ever created, and a\n * segment that exists and is legitimately empty. For a *read* that distinction does not matter \u2014 both answer\n * empty. For an **operand of a combine** it is the difference between a suppression list with nobody on it\n * yet and a suppression list you misspelled, and those must not look alike: the first correctly suppresses\n * nothing, the second silently suppresses nothing while you believe it is working.\n *\n * Consulted **only** when an operand resolved to no chunks at all, so a normal combine never calls it.\n */\n exists?(ref: SegmentRef): Promise<boolean>;\n /**\n * Optional: an opaque token identifying **which bytes** a read of this segment will see right now \u2014\n * the generation *and* the incarnation of the name it belongs to.\n *\n * {@link StorageChunkSource.currentGeneration} is not enough to key a decoded-chunk cache, and the gap is not\n * theoretical: a load numbers its generation from the row's pointer and what is in the bucket, so the number\n * **restarts at 0** once a registry row is purged and the bucket emptied. A retired, re-created name therefore\n * serves different data at the same `currentGen`, and a cache keyed on `(segment, chunk, generation)` hands back\n * the previous incarnation's ids \u2014 an erased id reappearing, with no read to intercept.\n *\n * Note that putting the incarnation in the *object key* would not fix this: the new incarnation still starts\n * at generation 0, so the cache key collides either way. The identity has to reach the cache.\n *\n * Returns `null` when the segment resolves to no generation. A source that only ever serves one immutable\n * generation may omit this, and the engine falls back to the generation alone.\n */\n currentVersion?(ref: SegmentRef): Promise<string | null>;\n}\n\n/** Capabilities a Storage driver advertises; validated at wiring time, fail-fast. */\nexport interface StorageCaps {\n /** REQUIRED \u2014 the format relies on byte-range reads. */\n readonly rangeRead: true;\n /** Largest single object the backend accepts (informs single-object-vs-shard \u2014 future). */\n readonly maxObjectBytes: number;\n /** Optional: enables the pure-object `LATEST`-pointer registry variant (S3 conditional put). */\n readonly conditionalPut?: boolean;\n}\n\n/**\n * Immutable object storage for `.crbm` generations. A \"dumb byte mover\": it\n * understands neither roaring nor the `.crbm` layout, only opaque bytes addressed by a {@link GenKey}.\n * The core never reuses a key, so puts are write-once.\n *\n * **What a driver must do** \u2014 callers rely on each of these, and the storage conformance suite holds every\n * shipped driver to them:\n *\n * - **`putImmutable` is write-once and reports a collision as {@link WriteConflictError}.** It never overwrites a\n * stored object. `load` reads that error as a lost race for the generation number (`superseded`), so a driver\n * that threw anything else for an existing key would surface as a failed load.\n * - **A missing object makes `getRange` and `getTail` throw {@link NotFoundError}** \u2014 never an empty or short\n * result. Heal-forward (a read whose generation a sweep collected re-resolves), the erasure's holder probe and\n * its verify of the rewrite, and a pin's replaced-object check all branch on that one error. (A zero-length\n * `getRange` may answer empty without reaching the backend, so it proves neither that the object exists nor\n * that the offset lies inside it: the cloud drivers answer it empty, the memory and local-filesystem drivers\n * throw for a missing object or an offset past the end.)\n * - **An out-of-range read throws {@link ValidationError}**: a range that runs past the end of the object, or a\n * negative or non-integer offset or length. Never a clamped, short or adjacent read. A pin whose object\n * was purged and loaded again as a smaller one asks for a range past its end, and this error is what sends it\n * to check that the object is still the one it pinned.\n * - **`getTail` reports the object's true total size**, not the number of bytes it returned.\n * - **`delete` is idempotent.** Deleting an absent key is a no-op, not an error: collection passes race each\n * other and retry.\n * - **`list` is strongly consistent, read-after-delete.** Once `delete` has resolved, `list` no longer yields\n * that generation, and once `putImmutable` has resolved it does. The erasure's re-check for a generation still\n * holding the id, `dropSegment`'s `generationsRemaining`, the retention sweep's check that a tombstone's storage\n * is gone, and rollback's post-move check prove a deletion or a presence by listing, so a listing that lags\n * reports an erasure as incomplete or a stale object as gone.\n * - **Never replay a conditional write without telling the replay apart.** A `putImmutable` that lands and then\n * loses its response, sent again, finds its own object and would report a collision. Either send each write\n * once, with the backend client's retry off for that request, or recognise your own write when the\n * precondition fails by reading an id you stored with it back, and treat a match as success. The first is\n * what the registry writes of every shipped driver do; the second is what the Azure Blob driver and the\n * resumable GCS upload do, and what the S3 driver and the single-request GCS upload do when they send an object\n * again after a throttle, which is the one replay a driver may make on purpose.\n * - **Raise a transient fault as {@link TransientError}** (throttling, a 5xx, a dropped connection), so the read\n * retry can ride it out and a write's caller can tell it from a deterministic failure.\n */\nexport interface IStorageDriver {\n capabilities(): StorageCaps;\n /**\n * Stream a new immutable generation. The driver opens a destination, hands `write` a {@link BlobSink},\n * then atomically commits (and computes the content hash). Throws {@link WriteConflictError} if the key\n * already exists (write-once).\n */\n putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }>;\n /**\n * Range read; the caller bounds-checks. Out-of-range throws {@link ValidationError}, never a short/adjacent\n * read, and a missing object throws {@link NotFoundError}.\n */\n getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;\n /**\n * Speculative tail read: the last `min(maxBytes, size)` bytes + the object's **true total size**. A missing\n * object throws {@link NotFoundError}.\n */\n getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }>;\n /** Idempotent: deleting an absent key is a no-op, not an error. */\n delete(key: GenKey): Promise<void>;\n /**\n * Enumerate the generations present for a segment (orphan sweep / latest-gen resolution). Strongly\n * consistent, read-after-delete and read-after-put.\n */\n list(ref: SegmentRef): AsyncIterable<GenKey>;\n}\n\n/**\n * Lifecycle status of a segment. `active` is the steady state; `destroyed` is the post-crypto-shred tombstone\n * (the row is kept for audit but the segment is logically gone).\n */\nexport type RegistryStatus = 'active' | 'destroyed';\n\n/**\n * Free-form, JSON-serializable governance metadata \u2014 a **plain object** (both registry boundaries reject `null`,\n * an array or a primitive, because a reader that tests `'key' in meta` would throw an untyped `TypeError`).\n *\n * `retention` has one **reserved key with defined semantics**: `expiresAt`, an absolute epoch-ms after which a\n * retention sweep may retire the segment \u2014 written by `setSegmentRetention`, parsed by `readRetentionPolicy`. Any\n * other key is yours (a legal hold, a note, an owner) and is preserved across policy writes. `residency` is still\n * shape-later: stored and round-tripped, no semantics attached.\n */\nexport type GovernanceMeta = Record<string, unknown>;\n\n/**\n * A cached description of one generation, carried on the registry row: how many ids it holds, and the metadata it\n * was loaded with. The generation's `.crbm` object stays the truth; this is a copy, written by the same write that\n * moves the pointer, and it names the generation it describes so a copy left behind by a writer that did not\n * carry it can never be taken for another generation's.\n *\n * Two shapes. On a cleartext segment the values are in the clear. On an encrypted segment (a row with\n * `wrappedDeks`) they are sealed under the segment's data key, as the generation's index is, so the row reveals\n * neither the count nor the metadata. A shipped registry refuses a write whose summary disagrees with the row's\n * keys. It still reads a stored row that disagrees, so that one such row cannot stop every listing: a reader must not\n * use that row's summary.\n */\nexport type RegistrySummary = ClearRegistrySummary | SealedRegistrySummary;\n\n/**\n * A generation's metadata: string keys, string or finite-number values, no nesting, at most 1 KiB as canonical JSON.\n * The rules are in `core/metadata.ts`.\n */\nexport type GenerationMetadata = Readonly<Record<string, string | number>>;\n\n/** What a generation is, as one read answers it: its number, its id count and the metadata it was written with. */\nexport interface GenerationSummary {\n readonly generation: number;\n readonly cardinality: number;\n /** Absent when the generation has none. */\n readonly metadata?: GenerationMetadata;\n}\n\n/** {@link RegistrySummary} on a cleartext segment. */\nexport interface ClearRegistrySummary {\n /** The generation this describes. A non-negative safe integer. */\n readonly generation: number;\n /** How many ids the generation holds: an integer from 0 to 2^32. */\n readonly cardinality: number;\n /** The generation's metadata, when it has any. Never the empty object. */\n readonly metadata?: GenerationMetadata;\n}\n\n/** {@link RegistrySummary} on an encrypted segment. */\nexport interface SealedRegistrySummary {\n /** The generation this describes. A non-negative safe integer. */\n readonly generation: number;\n /**\n * Base64 of `nonce(12) \u2016 ciphertext \u2016 tag(16)`, sealing the cardinality as a little-endian u64 followed by the\n * metadata's canonical JSON, if any. The count is fixed-width, so the length reveals only the metadata's size.\n */\n readonly sealed: string;\n}\n\n/**\n * One registry row \u2014 the authoritative per-segment record. Exactly one per segment.\n */\nexport interface RegistryRecord extends SegmentRef {\n /**\n * **The** authoritative LATEST pointer: which immutable Storage generation is current \u2014 or **`null` for a segment\n * that has no Storage generation yet.**\n *\n * `null` is not \"unknown\", it is a positive statement: *this segment exists and has no Storage data.* It is what\n * lets a segment have a registry row **before its first load** \u2014 `setSegmentRetention` mints one so a policy\n * can be recorded ahead of the data, and so the segment is reachable by `registry.list()` and therefore by\n * retention sweeps, `checkConsistency` and every other fleet-wide operation. The alternative \u2014 a row with\n * `currentGen: 0` and no object behind it \u2014 is the forbidden `missing-storage-generation` state, which fails per\n * read with `NotFoundError`. Generation resolution maps `null` onto the same path a segment with **no row**\n * takes: every read answers empty, and the first publish advances the pointer.\n */\n readonly currentGen: number | null;\n /**\n * Per-segment data-key (DEK) wrappings for encryption-at-rest: the DEK envelope-wrapped under one or more KEKs\n * (active + optional recovery). Reading unwraps with any held KEK; **crypto-shred deletes this whole list**,\n * making the segment's at-rest bytes permanently unrecoverable. Absent \u21D2 the segment is cleartext.\n */\n readonly wrappedDeks?: readonly WrappedDek[];\n /**\n * Optional **external** key reference (reserved) \u2014 e.g. a KMS key ARN for a future KMS keystore adapter that\n * keeps wrapped material in the KMS rather than in-band {@link wrappedDeks}. Unused by the in-process BYOK\n * keystore. Clearable on crypto-shred.\n */\n readonly keyId?: string;\n readonly status: RegistryStatus;\n /**\n * Governance policy. `retention.expiresAt` drives the retention sweep (see {@link GovernanceMeta}); `residency`\n * is stored and round-tripped with no semantics yet. Both must be plain objects, and both must survive a\n * `list()` projection \u2014 a fleet sweep reads the policy from the enumeration rather than per-segment.\n */\n readonly retention?: GovernanceMeta;\n readonly residency?: GovernanceMeta;\n /**\n * A cached description of the current generation (see {@link RegistrySummary}). Optional: a row without one is\n * correct, and a reader then opens the generation instead. Trusted only for the generation it names.\n */\n readonly summary?: RegistrySummary;\n /** Epoch-ms of creation / last mutation (from the driver's injected clock). */\n readonly createdAt: number;\n readonly updatedAt: number;\n /**\n * Opaque OCC token \u2014 compare-by-equality, ABA-safe. A shipped registry gives no two writes under one name the same\n * token: not two writes of this row, not a write of an earlier row under the name, and not a write after this row is\n * restored from a backup. It holds with overwhelming probability rather than by construction, since the token carries random parts: a\n * 128-bit incarnation id drawn when the row is created, and a 64-bit part drawn for each write. Two incarnations of one\n * name meet with probability 2^-128 for any pair (about n^2 / 2^129 among n of them), and two writes at one counter,\n * after a restore, with probability 2^-64. A bare decimal token, on a row no 0.12 or later registry has written,\n * carries neither part.\n */\n readonly token: Token;\n}\n\n/** The caller-settable fields at {@link IRegistryDriver.create} (audit + token are driver-managed). */\nexport interface NewRegistryRecord {\n /** `null` \u21D2 the segment has no Storage generation yet \u2014 see {@link RegistryRecord.currentGen}. */\n readonly currentGen: number | null;\n readonly wrappedDeks?: readonly WrappedDek[];\n readonly keyId?: string;\n /** Defaults to `'active'`. */\n readonly status?: RegistryStatus;\n readonly retention?: GovernanceMeta;\n readonly residency?: GovernanceMeta;\n /** Must name `currentGen` when given. */\n readonly summary?: RegistrySummary;\n}\n\n/**\n * Fields a {@link IRegistryDriver.compareAndSwap} may mutate (identity + audit + token are off-limits).\n *\n * Presence-based: a field the patch does not mention is left as it was. `summary` is the one exception, because it\n * describes the current generation: a patch that moves `currentGen` and does not mention `summary` drops the old\n * one rather than keep a description of another generation. A `summary` the patch gives must name the\n * `currentGen` the row will have.\n */\nexport type RegistryPatch = Partial<\n Pick<\n RegistryRecord,\n 'currentGen' | 'wrappedDeks' | 'keyId' | 'status' | 'retention' | 'residency' | 'summary'\n >\n>;\n\n/** Capabilities a registry driver advertises; validated fail-fast at wiring time. */\nexport interface RegCaps {\n /** REQUIRED \u2014 `currentGen` feeds read correctness + the publish CAS, so reads must be strongly consistent. */\n readonly strongRead: true;\n /**\n * `false` when this registry cannot write a row in this runtime, though it reads: a shipped registry draws random\n * bytes for every token it issues, and on a runtime with no Web Crypto it has none to draw. A write path that\n * writes an object before its row (a load, an erasure rewrite) checks it first and refuses with `UnsupportedError`\n * before it writes anything, rather than leave an object no row names. Absent means the registry can write.\n */\n readonly canWrite?: false;\n /**\n * `true` when this registry's `delete` removes a row from its backend for good, so that no later `list` reads it, and\n * removes it only while it is still the exact version the delete read: by a delete the backend applies under a\n * precondition (an S3 or Azure Blob ETag, a GCS object generation), or under a lock no other writer of the backend\n * can take. Only a row whose token carries an incarnation id is removed so. A row first written by a release before\n * 0.12 has a bare decimal token, and a delete still tombstones it: a process on that release, re-creating the name\n * over nothing, would issue those counters again from 0, so its row could not be told apart from the deleted one.\n *\n * `false` or absent: every `delete` leaves a tombstone, which every later full `list` still reads. A shipped registry\n * says which it is.\n */\n readonly conditionalDelete?: boolean;\n}\n\n/**\n * Refuse, with `UnsupportedError`, a write that would write an object before its row when the registry reports it\n * cannot write a row (see {@link RegCaps.canWrite}). Called before the write's first request.\n */\nexport function assertRegistryCanWrite(registry: IRegistryDriver, what: string): void {\n if (registry.capabilities().canWrite === false) {\n throw new UnsupportedError(\n `${what}: the registry reports it cannot write a row in this runtime (capabilities().canWrite is false), ` +\n 'so nothing is written',\n );\n }\n}\n\n/**\n * Brand for {@link StorageBackend}.\n *\n * `Symbol.for` rather than a class check, for the same reason the error brands are: a class object is only\n * shared while one copy of core is loaded, so a backend built from one copy and checked by another \u2014 after a\n * version skew, or behind a duplicating bundler \u2014 would fail an `instanceof`. A registered symbol is\n * identity-stable across all of them, and across realms.\n *\n * A brand string is part of that identity contract, so once a release carries one it never changes: a changed\n * string would stop matching the copies already installed.\n */\nconst STORAGE_BACKEND_BRAND: unique symbol = Symbol.for('cloudbitmaps.storage-backend');\n\n/**\n * Where a store keeps everything: the generations, and the pointer saying which one is current.\n *\n * **Branded on purpose.** The two fields alone are not enough to qualify, and that is the whole point. This\n * shape is also {@link LoadDeps}, so before the brand any `{ storage, registry }`\n * object literal satisfied it \u2014 including one assembling halves from two *unrelated* stores, which the store\n * accepted and then answered **empty** for a segment that holds data, because it read a pointer from a place\n * nothing had ever been written. That is the exact silent-empty failure one-class-per-backend exists to\n * remove, and it was reachable in five lines.\n *\n * So a backend comes from `MemoryStorage`, `LocalFsStorage`, `S3Storage`, `GcsStorage` or\n * `AzureBlobStorage` \u2014 each deriving both halves from one bucket and one prefix. A driver author composing halves\n * of their own says so on purpose, with {@link brandAsBackend}. The halves stay readable as `.storage` /\n * `.registry`, because the free functions genuinely need them.\n *\n * **What this stops is the accident, not a determined caller.** The brand is a registered symbol, so it can\n * be written by hand; nothing here is a security boundary. It stops the two spellings people reach for\n * without deciding to \u2014 an object literal, and a spread of a real backend.\n */\nexport interface StorageBackend {\n /** Cross-bundle brand. Set by the backend classes; not part of the public surface. */\n readonly [STORAGE_BACKEND_BRAND]: true;\n /** Where the immutable `.crbm` generations live. */\n readonly storage: IStorageDriver;\n /** Where the `currentGen` pointer, the discovery index and the wrapped DEKs live. */\n readonly registry: IRegistryDriver;\n}\n\n/**\n * The brand key, for the type-only `declare` field a backend class carries.\n *\n * Exported from `@cloudbitmaps/core/driver-kit` and part of the driver contract: a third-party storage\n * package needs it to declare the field that {@link brandAsBackend} then stamps at runtime.\n */\nexport const STORAGE_BACKEND: typeof STORAGE_BACKEND_BRAND = STORAGE_BACKEND_BRAND;\n\n/**\n * Stamp the brand on a backend, **non-enumerably**, after checking both halves are drivers. Returns `target`.\n *\n * This is the driver author's door, and it works on a backend class (`brandAsBackend(this)` as the last line of\n * its constructor) and on a plain `{ storage, registry }` object alike \u2014 the latter is how a backend of your\n * own halves is built: a driver wrapped for auditing, metrics, tenant scoping or client-side encryption, or\n * paired with a registry in a database you already run. An application does not need it: it gets both halves\n * from one backend class, which derives them from one bucket and one prefix so they cannot disagree.\n *\n * It throws {@link ValidationError} when `storage` has no `putImmutable` or `registry` has no `compareAndSwap`,\n * so a half that is not a driver is refused here rather than at its first write, and when `target` is frozen or\n * non-extensible, since the brand is stamped on it in place.\n *\n * It cannot check that the halves agree: `IStorageDriver` and `IRegistryDriver` do not expose a location, so\n * nothing here can compare one. Branding two unrelated halves is you taking that on \u2014 the plain\n * `{ storage, registry }` literal is refused by the store precisely because it let the two halves come from\n * unrelated places by accident, and answer **empty** rather than fail.\n *\n * Non-enumerable is load-bearing, not tidiness. A class field (`readonly [BRAND] = true`) emits an\n * *enumerable* own property, and spread and `Object.assign` copy exactly those \u2014 so\n * `{ ...backend, registry: other.registry }` carried the brand and was accepted, reproducing the silent-empty\n * bug the brand exists to close. This does not make the brand unforgeable \u2014 the symbol is registered, so anyone\n * who wants it can write `Symbol.for('cloudbitmaps.storage-backend')`. That is deliberate effort equivalent to\n * calling this function, and it is not what the check is for. The check is for the accident.\n */\nexport function brandAsBackend<T extends { storage: IStorageDriver; registry: IRegistryDriver }>(\n target: T,\n): T & StorageBackend {\n if (target === null || typeof target !== 'object') {\n throw new ValidationError('brandAsBackend needs `{ storage, registry }`');\n }\n const { storage, registry } = target;\n if (\n storage === null ||\n typeof storage !== 'object' ||\n typeof storage.putImmutable !== 'function'\n ) {\n throw new ValidationError(\n 'brandAsBackend: `storage` must be an IStorageDriver (it has no `putImmutable`)',\n );\n }\n if (\n registry === null ||\n typeof registry !== 'object' ||\n typeof registry.compareAndSwap !== 'function'\n ) {\n throw new ValidationError(\n 'brandAsBackend: `registry` must be an IRegistryDriver (it has no `compareAndSwap`)',\n );\n }\n try {\n Object.defineProperty(target, STORAGE_BACKEND_BRAND, {\n value: true,\n enumerable: false,\n writable: false,\n configurable: false,\n });\n } catch {\n // A frozen, sealed or non-extensible object cannot take the brand, and the brand is stamped in place.\n throw new ValidationError(\n 'brandAsBackend: cannot brand a frozen or non-extensible object \u2014 pass a plain object or a class instance',\n );\n }\n return target as T & StorageBackend;\n}\n\n/**\n * Is this a branded backend \u2014 from a backend class or from {@link brandAsBackend}?\n *\n * Checks the brand, not the shape \u2014 see {@link StorageBackend} for why the shape alone is not enough.\n */\nexport function isStorageBackend(value: unknown): value is StorageBackend {\n return (\n typeof value === 'object' &&\n value !== null &&\n (value as Record<symbol, unknown>)[STORAGE_BACKEND_BRAND] === true\n );\n}\n\n/**\n * Per-segment registry \u2014 the authoritative source of `currentGen`, the discovery index, and the wrapped-DEK\n * holder. One row per segment under OCC: a never-reused, equality-compared {@link Token} (ABA-safe across\n * delete\u2192recreate).\n *\n * **What a driver must do** \u2014 callers rely on each of these, and the registry conformance suite holds every\n * shipped driver to them:\n *\n * - **`create` and `compareAndSwap` are atomic conditional writes.** A `create` over a live row, and a\n * `compareAndSwap` whose token is not the stored one, throw {@link WriteConflictError} and change nothing.\n * Two racing writers have exactly one winner.\n * - **A token is not reused, not even across `delete` then `create`**, short of a collision of probability 2^-128 for\n * any two incarnations of one name: a recreated row carries a token no earlier incarnation held, so a stale holder\n * cannot swap into it. The shipped drivers draw a random 128-bit incarnation id into the token of every row they\n * create and a random 64-bit part into the token of every write, beside a counter a tombstone (or a global counter)\n * keeps going; where nothing of the earlier row is left, or the row is restored from a backup to an older counter, the\n * random parts alone keep the tokens apart: with probability 1 - 2^-128 per pair of incarnations, and 1 - 2^-64 per\n * pair of writes at one counter.\n * - **`delete` is idempotent** without an `expected` token: deleting an absent row is a no-op, not an error.\n * **With `expected` it is fenced**: it lands only while the row still carries that token, and otherwise throws\n * {@link WriteConflictError} and leaves the row.\n * - **Reads are strongly consistent** (`RegCaps.strongRead`), and `list` yields every existing row,\n * `destroyed` tombstones included, with every field (see {@link IRegistryDriver.list}).\n * - **Never replay a conditional write without telling the replay apart.** A `create` or `compareAndSwap`\n * that lands and then loses its response, sent again, finds its own write and would report a conflict.\n * Either send each write once, with the backend client's retry off for that request, or recognise your own\n * write when the precondition fails by reading an id you stored with it back. The shipped registries do\n * one or the other.\n * - **Raise a transient fault as {@link TransientError}.**\n */\nexport interface IRegistryDriver {\n capabilities(): RegCaps;\n /**\n * The segment's record, or `null` if it doesn't exist (or was deleted). Throw {@link TransientError} for a fault\n * worth riding out (throttling, a 5xx, a dropped connection): a reader whose pointer refresh meets one keeps\n * serving what it holds and asks again shortly, while any other error reaches the read that met it.\n */\n get(ref: SegmentRef): Promise<RegistryRecord | null>;\n /** Create the row; throws {@link WriteConflictError} if it already exists (use CAS to mutate). */\n create(ref: SegmentRef, record: NewRegistryRecord): Promise<{ token: Token }>;\n /** Server-side compare-and-set: apply `patch` iff the stored token equals `expected`, else `WriteConflictError`. */\n compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{ token: Token }>;\n /**\n * Discovery: every **existing** record, optionally scoped to one namespace. Order is unspecified.\n *\n * \"Existing\" means not `delete`d. A **`destroyed` tombstone is still a record and must be yielded** \u2014 a driver\n * that filters by `status` breaks callers silently, and two already depend on seeing them: `runConsistencyCheck`\n * skips them itself, and the retention sweep can only clean up a tombstone row it can see (filtering it makes\n * the cleanup a permanent no-op, indistinguishable from having nothing to do, while dead rows accumulate). Same\n * rule for every other field: a row with **`currentGen: null`** must be yielded like any other, and `retention`\n * must survive the projection \u2014 a fleet sweep reads the policy straight out of this enumeration rather than\n * paying a `get()` per segment, so a `list()` that drops the field means nothing ever expires, silently.\n */\n list(namespace?: string): AsyncIterable<RegistryRecord>;\n /**\n * Remove the row. A registry whose {@link RegCaps.conditionalDelete} is `true` removes a row whose token carries an\n * incarnation id from its backend; every other row is tombstoned. Either way a later `create` gets a token no\n * earlier incarnation of the name held, but for a collision of probability 2^-128 per pair of incarnations.\n *\n * Without `expected`, idempotent: deleting an absent row is a no-op.\n *\n * With `expected`, the delete is **fenced like a {@link IRegistryDriver.compareAndSwap}**: it lands only while\n * the row still carries exactly that token, and otherwise \u2014 the token differs, or the row is absent or already\n * deleted \u2014 throws {@link WriteConflictError} and leaves the row. Pass the token of the row you read when you\n * decided to delete it, so a delete that is delayed, replayed, or racing a re-create cannot tombstone a row\n * created after the decision. A third-party driver that ignores `expected` keeps the unfenced behaviour.\n */\n delete(ref: SegmentRef, expected?: Token): Promise<void>;\n}\n", "/**\n * Shared key-grammar fragments used by every driver's logical-ref \u2192 physical-key mapping.\n *\n * One copy of the `_default` namespace sentinel for every driver that maps a namespace to a key or a path. Pure\n * string logic, no SDK, no I/O \u2014 it lives in the SDK-free `_shared` bundle so any driver may import it.\n */\n\n/**\n * The physical stand-in for an **absent** namespace. A caller MAY name a namespace `_default`; it simply does not collide, because\n * `namespaceKeyPart`/`namespacePathPart` encode the caller's namespace (to `%5Fdefault`) and emit this\n * sentinel literally. The separation is a property of the encoding, not of a grammar; reading it as a grammar is\n * exactly what would make the collision easy to reintroduce.\n */\nexport const DEFAULT_NAMESPACE = '_default';\n\n/**\n * The physical namespace component of an **object key**: the caller's namespace encoded, or the sentinel.\n *\n * The sentinel is emitted **literally, never encoded**, and that asymmetry is the whole point. Encoding it too\n * would send an absent namespace to `%5Fdefault` \u2014 exactly where a caller who names their namespace `_default`\n * already goes, since the encoder escapes their leading underscore. Both would land in one place and read each\n * other's data. Encoding only the caller's side means `_default` (ours) and `%5Fdefault` (theirs) are two\n * different strings, and no caller can produce the first.\n */\nexport function namespaceKeyPart(namespace: string | undefined): string {\n return namespace === undefined ? DEFAULT_NAMESPACE : encodeNameForKey(namespace);\n}\n\n/** As {@link namespaceKeyPart}, for a filesystem path component. */\nexport function namespacePathPart(namespace: string | undefined): string {\n return namespace === undefined ? DEFAULT_NAMESPACE : encodeNameForPath(namespace);\n}\n\n// The name codec lives in `core/` (pure string logic, and `validate.ts` needs it); imported here so the\n// drivers keep one import for key shaping, and re-exported for the same reason.\nimport { encodeNameForKey, encodeNameForPath } from '@/core/name-codec';\n\nexport {\n encodeNameForKey,\n decodeNameFromKey,\n encodeNameForPath,\n decodeNameFromPath,\n} from '@/core/name-codec';\n", "/**\n * Generation metadata: a small, flat record of strings and finite numbers attached to one generation.\n *\n * The rules live here once, for every boundary that meets a value: a caller's input, refused with\n * `ValidationError`, and bytes read back from a tier, refused with `IntegrityError`. The caller passes the error\n * to raise. Pure: no I/O.\n */\nimport type { GenerationMetadata } from './ports';\nimport { isWellFormedString } from './validate';\n\n/** Cap on the canonical JSON of one generation's metadata, in UTF-8 bytes (braces and quotes included). */\nexport const MAX_METADATA_BYTES = 1024;\n/** Cap on one metadata key, in UTF-8 bytes. */\nexport const MAX_METADATA_KEY_BYTES = 128;\n\nconst utf8Length = (s: string): number => new TextEncoder().encode(s).length;\n\n/**\n * Validate `value` as generation metadata (`GenerationMetadata` in the ports) and return its canonical JSON: RFC 8785\n * (the JSON Canonicalization Scheme) for a flat object of strings and finite numbers. Keys are sorted by UTF-16 code\n * unit, strings are escaped as `JSON.stringify` escapes them, numbers are written as ECMAScript's\n * `Number.prototype.toString` writes them (so `-0` is `0`, and `1e21` is `1e+21`), and there is no whitespace. Key\n * order therefore never changes the bytes, and `tests/golden/metadata-canonical.json` holds vectors a port can check\n * itself against. Both caps are in UTF-8 bytes. The container is an object whose prototype is\n * `Object.prototype` or `null`, holding only enumerable data properties with string keys. `fail` raises the error the\n * boundary calls for. The empty object passes and returns `{}`: whether it may be stored is the caller's rule.\n */\nexport function canonicalMetadataJson(value: unknown, fail: (message: string) => never): string {\n if (value === null || typeof value !== 'object') {\n fail(`metadata must be a plain object (got ${value === null ? 'null' : typeof value})`);\n }\n const proto: unknown = Object.getPrototypeOf(value);\n if (\n Object.prototype.toString.call(value) !== '[object Object]' ||\n (proto !== Object.prototype && proto !== null)\n ) {\n fail('metadata must be a plain object, not an array, a Map, a boxed value or a class instance');\n }\n // Each key with the value the rules checked, so the bytes are serialised from that value and never from a second\n // read, which a proxy could answer differently.\n const entries: Array<[string, string | number]> = [];\n const keys = Reflect.ownKeys(value as object);\n for (const key of keys) {\n if (typeof key !== 'string') fail('metadata keys must be strings, not symbols');\n const descriptor = Object.getOwnPropertyDescriptor(value, key);\n if (descriptor === undefined || !('value' in descriptor) || descriptor.enumerable !== true) {\n fail(`metadata key ${JSON.stringify(key)} must be an enumerable data property`);\n }\n if (key.length === 0) fail('metadata keys must be non-empty');\n if (key === '__proto__') fail('metadata may not use the key \"__proto__\"');\n if (!isWellFormedString(key)) fail('metadata keys must be well-formed UTF-16');\n // A UTF-16 unit is at most 3 UTF-8 bytes, so a key that short is under the cap without encoding it.\n if (key.length * 3 > MAX_METADATA_KEY_BYTES) {\n const keyBytes = utf8Length(key);\n if (keyBytes > MAX_METADATA_KEY_BYTES) {\n fail(`metadata key is ${keyBytes}B, over the ${MAX_METADATA_KEY_BYTES}B cap`);\n }\n }\n const v: unknown = descriptor.value;\n if (typeof v === 'string') {\n if (!isWellFormedString(v)) {\n fail(`metadata value of ${JSON.stringify(key)} must be well-formed UTF-16`);\n }\n } else if (typeof v !== 'number' || !Number.isFinite(v)) {\n fail(`metadata value of ${JSON.stringify(key)} must be a string or a finite number`);\n }\n entries.push([key, v]);\n }\n // Keys are unique, so comparing them alone orders the entries by UTF-16 code unit.\n entries.sort(([a], [b]) => (a < b ? -1 : 1));\n const json = `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${JSON.stringify(v)}`).join(',')}}`;\n const bytes = utf8Length(json);\n if (bytes > MAX_METADATA_BYTES) {\n fail(`metadata is ${bytes}B as canonical JSON, over the ${MAX_METADATA_BYTES}B cap`);\n }\n return json;\n}\n\n/**\n * The bytes a generation stores for `value`: its canonical JSON in UTF-8, or `undefined` when it has none (`undefined`\n * or the empty object, which store nothing). Synchronous, so what is stored is what the caller passed at the call,\n * whatever it does with its object afterwards. `fail` raises the boundary's error.\n */\nexport function metadataBytes(\n value: unknown,\n fail: (message: string) => never,\n): Uint8Array | undefined {\n if (value === undefined) return undefined;\n const json = canonicalMetadataJson(value, fail);\n return json === '{}' ? undefined : new TextEncoder().encode(json);\n}\n\n/**\n * A caller's metadata, checked against the rules and copied, so what is stored is what the caller passed at the call,\n * whatever it does with its object afterwards: the frozen record in canonical key order, or `undefined` when there is\n * none (`undefined`, or the empty object, which store nothing). Synchronous. `fail` raises the boundary's error.\n */\nexport function copiedMetadata(\n value: unknown,\n fail: (message: string) => never,\n): GenerationMetadata | undefined {\n const bytes = metadataBytes(value, fail);\n return bytes === undefined ? undefined : metadataFromBytes(bytes, fail);\n}\n\n/**\n * Read metadata back from bytes a tier holds, which are untrusted. They must be at most {@link MAX_METADATA_BYTES},\n * valid UTF-8, and exactly the canonical JSON of a non-empty record, so a stored copy has one spelling: whitespace, a\n * key out of order or listed twice, and a number or an escape `JSON.stringify` would write another way are all\n * refused. Returns a frozen record of its own. `fail` raises the boundary's error.\n */\nexport function metadataFromBytes(\n bytes: Uint8Array,\n fail: (message: string) => never,\n): GenerationMetadata {\n if (bytes.length > MAX_METADATA_BYTES) {\n fail(`metadata is ${bytes.length}B, over the ${MAX_METADATA_BYTES}B cap`);\n }\n let text: string;\n try {\n text = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(bytes);\n } catch {\n fail('metadata is not valid UTF-8');\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n fail('metadata is not JSON');\n }\n const json = canonicalMetadataJson(parsed, fail);\n if (json === '{}') fail('metadata is empty; a generation without metadata carries none');\n if (json !== text) fail('metadata is not in canonical form');\n return Object.freeze(parsed as GenerationMetadata);\n}\n", "/**\n * The registry token's incarnation: the part of a token that says which incarnation of a name a row is.\n *\n * A shipped registry gives a row it creates a token `<incarnation>.<counter>.<write>`: 32 lowercase hex digits drawn\n * from injected entropy when the row is created, a decimal counter, and 16 hex digits drawn for each write. Every write\n * of the row keeps the incarnation, and a name created again draws a new one, so two incarnations are told apart by it\n * even when their rows were created in the same millisecond or one is gone. A row a 0.11 build wrote has no incarnation\n * for as long as it lives (`\"7\"`, then `\"8.<write>\"`), and a registry of someone else's issues tokens in any form.\n *\n * This lives in `core/` so the publish can read it, and the registry drivers, which build the tokens, import it from\n * here: the form has one definition.\n */\nimport type { Token } from './ports';\n\n/** An incarnation-form token: the incarnation id, the counter and a write part, `.`-separated, in lowercase hex. */\nexport const INCARNATION_TOKEN = /^([0-9a-f]{32})\\.(0|[1-9]\\d*)\\.([0-9a-f]{16})$/;\n\n/**\n * The incarnation id in a token, or `undefined` for one that has none: a schema-1-born row's, or any token another\n * driver issues. Two tokens with the same incarnation are writes of one incarnation of a row.\n */\nexport function incarnationOf(token: Token): string | undefined {\n return INCARNATION_TOKEN.exec(token)?.[1];\n}\n\n/**\n * Whether two reads of one name's row are one incarnation of it.\n *\n * - Both tokens carry an incarnation id: the ids decide, and the clock stamps do not enter.\n * - Only one does: different incarnations. A name created again is always given an incarnation id, and a write never\n * takes one away, so a row without an id never becomes a row with one, nor the reverse.\n * - Neither does (a row 0.11 wrote, or a registry of someone else's): there is no id to compare, and the creation stamp\n * is the only signal left. It is a clock reading, so a caller that acts on this answer proves its own object too.\n */\nexport function sameIncarnation(\n a: { readonly token: Token; readonly createdAt: number },\n b: { readonly token: Token; readonly createdAt: number },\n): boolean {\n const ia = incarnationOf(a.token);\n const ib = incarnationOf(b.token);\n if (ia !== undefined || ib !== undefined) return ia === ib;\n return a.createdAt === b.createdAt;\n}\n", "/**\n * Shared, SDK-free helpers for assembling + validating {@link RegistryRecord}s.\n *\n * The registry's record-construction, patch-application, and field-validation are identical across every\n * backend (memory / LocalFs / object store) \u2014 only the *storage* + OCC mechanics differ. This is the one home\n * for that logic so the three drivers can't drift. Pure: no I/O, no SDK, no clock (the caller passes `now`), and no\n * ambient randomness (the caller passes its `Entropy`; the Web Crypto default is in `entropy.ts`).\n */\nimport { IntegrityError, UnsupportedError, ValidationError } from '@/core/errors';\nimport type { Entropy } from '@/core/determinism';\nimport { canonicalMetadataJson, MAX_METADATA_BYTES } from '@/core/metadata';\nimport { INCARNATION_TOKEN, incarnationOf } from '@/core/token';\nimport type {\n NewRegistryRecord,\n RegistryPatch,\n RegistryRecord,\n RegistrySummary,\n SegmentRef,\n Token,\n} from '@/core/ports';\n\n/** The valid {@link RegistryStatus} values \u2014 used to validate both caller input and stored bytes. */\nconst STATUSES: readonly string[] = ['active', 'destroyed'];\n/**\n * The fields a stored record may carry: {@link RegistryRecord}'s, and nothing else. A field no reader resolves\n * through is refused on read-back like any other corruption (invariant 5), rather than ignored.\n */\nexport const RECORD_FIELDS = [\n 'namespace',\n 'segment',\n 'currentGen',\n 'wrappedDeks',\n 'keyId',\n 'status',\n 'retention',\n 'residency',\n 'summary',\n 'createdAt',\n 'updatedAt',\n 'token',\n] as const;\n/** The record fields a schema-1 row may carry: every field but the ones schema 2 added. */\nconst SCHEMA_1_RECORD_FIELDS: readonly string[] = RECORD_FIELDS.filter((f) => f !== 'summary');\n/** The fields of the persisted envelope around a record. */\nconst ENVELOPE_FIELDS: readonly string[] = ['schemaVersion', 'deleted', 'record'];\n/** Cap on a serialized governance blob (retention/residency) \u2014 bounds row size so a row can't be bricked. */\nconst MAX_GOVERNANCE_BYTES = 64 * 1024;\n/** Bounds on the wrapped-DEK list (one DEK wrapped under a few KEKs) \u2014 keeps the row small + rejects abuse. */\nconst MAX_WRAPPED_DEKS = 8;\nconst MAX_WRAPPED_DEK_BYTES = 4 * 1024;\n/** Ids a generation can hold: every 32-bit id. */\nconst MAX_SUMMARY_CARDINALITY = 2 ** 32;\n/** A sealed summary's fixed part: a 12-byte nonce, the u64 count, and a 16-byte tag. */\nconst SEALED_SUMMARY_MIN_BYTES = 12 + 8 + 16;\n/** The fixed part plus metadata at its cap. */\nconst SEALED_SUMMARY_MAX_BYTES = SEALED_SUMMARY_MIN_BYTES + MAX_METADATA_BYTES;\n/** Padded standard base64; canonical once its pad bits are checked too (see {@link base64PadBitsAreZero}). */\nconst BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;\n\n/** `null` is legal and meaningful: the segment has no Storage generation yet (see `RegistryRecord.currentGen`). */\nfunction validateGeneration(gen: number | null): void {\n if (gen === null) return;\n if (!Number.isInteger(gen) || gen < 0) {\n throw new ValidationError(\n `currentGen must be a non-negative integer or null (no Storage generation yet); got ${gen}`,\n );\n }\n}\n\n/**\n * A patch that *mentions* `currentGen` must give it a real value \u2014 `null` to clear the pointer, or a generation.\n * `{ currentGen: undefined }` is refused rather than coerced. `{ currentGen: maybeUndefined }` is what `strict`\n * alone permits, since `exactOptionalPropertyTypes` is off, and a caller writing it means to leave the pointer\n * alone. Under presence-based merging the same call would silently *un-publish* a live segment: every Storage\n * generation goes invisible, and the collection then refuses to collect the objects (no pointer \u21D2 nothing\n * to compare against), so they are stranded and billed forever. Fail fast at the boundary instead.\n */\nfunction validatePatchGeneration(patch: RegistryPatch): void {\n if (!('currentGen' in patch)) return;\n // `validateGeneration(undefined)` fails the integer test on its own, because this check reads the raw patch\n // before `applyRegistryPatch` turns an undefined `currentGen` into `null`; this branch exists to make the\n // *message* say which value to pass instead, because the generic \"must be a non-negative integer or null\"\n // reads like a type error rather than the trap it is.\n if (patch.currentGen === undefined) {\n throw new ValidationError(\n `currentGen was present in the patch but undefined. Pass \\`null\\` to clear the pointer (the segment has no ` +\n `Storage generation), or omit the key to leave it unchanged \u2014 an undefined value is refused because it ` +\n `would un-publish the segment's Storage data.`,\n );\n }\n validateGeneration(patch.currentGen);\n}\n\nfunction validateStatus(status: string): void {\n if (!STATUSES.includes(status)) {\n throw new ValidationError(`status must be one of ${STATUSES.join('/')}; got ${status}`);\n }\n}\n\n/**\n * Validate a governance blob (retention/residency) at the write boundary: it must be JSON-serializable\n * (a `BigInt`/function/circular value fails fast with a typed error instead of a cryptic one deep in a\n * driver) and within a size cap (so an oversized blob can't write a row that's later unreadable \u2014 a brick).\n */\nfunction validateGovernance(meta: unknown, field: string): void {\n if (meta === undefined) return;\n // A plain object, not `null`/an array/a primitive. `JSON.stringify(null)` is 4 valid bytes, so without this the\n // size + serializability checks below happily store `\"retention\": null` \u2014 and `retention.expiresAt` is read with\n // an `in` test, which throws an untyped `TypeError` on a non-object and would abort a whole fleet retention\n // sweep rather than becoming one ledger entry. Reject at the write boundary; the read boundary\n // ({@link assertStoredRecordShape}) rejects it too, for a row edited by hand or written by another writer.\n if (meta === null || typeof meta !== 'object' || Array.isArray(meta)) {\n throw new ValidationError(\n `${field} must be a plain object (got ${meta === null ? 'null' : typeof meta})`,\n );\n }\n let json: string;\n try {\n json = JSON.stringify(meta);\n } catch {\n throw new ValidationError(`${field} must be JSON-serializable`);\n }\n if (json.length > MAX_GOVERNANCE_BYTES) {\n throw new ValidationError(`${field} is ${json.length}B, exceeds cap ${MAX_GOVERNANCE_BYTES}B`);\n }\n}\n\n/**\n * Validate the wrapped-DEK list at the write/read boundary: a bounded array of `{ keyId, wrapped }` with\n * non-empty string fields and a per-entry size cap (so a row can't be bricked, and corrupt bytes are rejected\n * rather than reaching the keystore). `isStored` toggles the error class (write = ValidationError; read =\n * IntegrityError, invariant 5).\n */\nfunction validateWrappedDeks(value: unknown, isStored: boolean): void {\n if (value === undefined) return;\n const fail = (msg: string): never => {\n throw isStored ? new IntegrityError(msg) : new ValidationError(msg);\n };\n if (!Array.isArray(value)) fail('wrappedDeks must be an array');\n const list = value as unknown[];\n if (list.length === 0) fail('wrappedDeks must be non-empty when present');\n if (list.length > MAX_WRAPPED_DEKS)\n fail(`wrappedDeks has ${list.length} entries, cap ${MAX_WRAPPED_DEKS}`);\n for (const w of list) {\n if (w === null || typeof w !== 'object') fail('wrappedDeks entry must be an object');\n const { keyId, wrapped } = w as { keyId?: unknown; wrapped?: unknown };\n if (typeof keyId !== 'string' || keyId.length === 0)\n fail('wrappedDeks entry needs a non-empty keyId');\n if (typeof wrapped !== 'string' || wrapped.length === 0)\n fail('wrappedDeks entry needs a non-empty wrapped blob');\n if ((wrapped as string).length > MAX_WRAPPED_DEK_BYTES)\n fail(`wrappedDeks entry is ${(wrapped as string).length}B, cap ${MAX_WRAPPED_DEK_BYTES}B`);\n }\n}\n\n/** The standard base64 alphabet, whose index of a character is the six bits it carries. */\nconst BASE64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n\n/**\n * Whether a padded standard base64 string is canonical: the bits of its last character past the data are zero.\n * Without this two strings would decode to the same bytes.\n */\nfunction base64PadBitsAreZero(text: string): boolean {\n const pad = text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0;\n if (pad === 0) return true;\n const last = BASE64_ALPHABET.indexOf(text[text.length - pad - 1]!);\n return pad === 2 ? (last & 0b1111) === 0 : (last & 0b11) === 0;\n}\n\n/**\n * Validate a {@link RegistrySummary}'s shape at the write or read boundary, and return a frozen copy built from the\n * checked fields only: exactly one of the two shapes, a non-negative safe-integer generation, a cardinality from 0 to\n * 2^32, metadata by the metadata rules (never the empty object, which is never stored) with its keys in canonical\n * order, or a sealed blob in canonical base64 whose length fits a sealed count plus metadata at its cap. A writer\n * stores the copy, so a caller that changes its object after the call, while the driver awaits the row, changes\n * nothing that is written. Whether a summary may be *used* is the reader's rule, not this one: this only bounds what a\n * row can hold. `isStored` picks the error class (write = ValidationError; read = IntegrityError, invariant 5), and a\n * read names the row by `ctx`.\n */\nfunction validateSummary(\n value: unknown,\n isStored: boolean,\n ctx?: string,\n): RegistrySummary | undefined {\n if (value === undefined) return undefined;\n const fail = (msg: string): never => {\n const where = ctx === undefined ? '' : `: ${ctx}`;\n throw isStored\n ? new IntegrityError(`registry record summary: ${msg}${where}`)\n : new ValidationError(`summary: ${msg}${where}`);\n };\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n fail(\n `must be an object (got ${value === null ? 'null' : Array.isArray(value) ? 'an array' : typeof value})`,\n );\n }\n const s = value as Record<string, unknown>;\n const keys = Object.keys(s);\n const isSealed = keys.includes('sealed');\n const allowed = isSealed ? ['generation', 'sealed'] : ['generation', 'cardinality', 'metadata'];\n const extra = keys.filter((k) => !allowed.includes(k));\n if (extra.length > 0) {\n fail(\n `has fields its ${isSealed ? 'sealed' : 'clear'} shape does not declare (${extra.join(', ')})`,\n );\n }\n const generation = s.generation;\n if (!Number.isSafeInteger(generation) || (generation as number) < 0) {\n fail(`generation must be a non-negative safe integer (got ${String(generation)})`);\n }\n if (isSealed) {\n const sealed = s.sealed;\n if (typeof sealed !== 'string' || !BASE64.test(sealed) || !base64PadBitsAreZero(sealed)) {\n fail('sealed must be canonical, padded standard base64');\n }\n const text = sealed as string;\n const bytes = (text.length / 4) * 3 - (text.endsWith('==') ? 2 : text.endsWith('=') ? 1 : 0);\n if (bytes < SEALED_SUMMARY_MIN_BYTES || bytes > SEALED_SUMMARY_MAX_BYTES) {\n fail(\n `sealed is ${bytes}B, outside ${SEALED_SUMMARY_MIN_BYTES}..${SEALED_SUMMARY_MAX_BYTES}B ` +\n '(a nonce, a fixed-width count, a tag, and metadata up to its cap)',\n );\n }\n return Object.freeze({ generation: generation as number, sealed: text });\n }\n const n = s.cardinality;\n if (!Number.isInteger(n) || (n as number) < 0 || (n as number) > MAX_SUMMARY_CARDINALITY) {\n fail(`cardinality must be an integer from 0 to 2^32 (got ${String(n)})`);\n }\n if (s.metadata === undefined) {\n return Object.freeze({ generation: generation as number, cardinality: n as number });\n }\n const canonical = canonicalMetadataJson(s.metadata, fail);\n if (canonical === '{}') fail('metadata is empty; a generation without metadata carries none');\n // A read only checks the stored shape and keeps the object it parsed, so it builds no copy.\n if (isStored) return s as unknown as RegistrySummary;\n // Rebuilt from the canonical JSON, so its keys are in canonical order (JavaScript still lists integer-like keys\n // first, in numeric order, as it does in every object) and nothing of the caller's object is kept.\n const metadata = Object.freeze(JSON.parse(canonical) as Record<string, string | number>);\n return Object.freeze({ generation: generation as number, cardinality: n as number, metadata });\n}\n\n/** A summary given at a write must describe the generation the row will point at. */\nfunction validateSummaryNames(\n summary: RegistrySummary | undefined,\n currentGen: number | null,\n): void {\n if (summary !== undefined && summary.generation !== currentGen) {\n throw new ValidationError(\n `summary describes generation ${summary.generation}, but the row points at ${String(currentGen)}`,\n );\n }\n}\n\n/**\n * Whether a summary's shape agrees with the row's encryption: sealed on a row with wrapped keys, clear on one without.\n * A clear count or metadata beside a key would leak what the segment's objects hide; a sealed one on a cleartext row\n * could never be opened.\n */\nfunction summaryAgreesWithKeys(\n summary: RegistrySummary,\n wrappedDeks: RegistryRecord['wrappedDeks'],\n): boolean {\n return 'sealed' in summary === (wrappedDeks !== undefined);\n}\n\n/** A summary given at a write must agree with the row's encryption (see {@link summaryAgreesWithKeys}). */\nfunction validateSummaryKeys(\n summary: RegistrySummary | undefined,\n wrappedDeks: RegistryRecord['wrappedDeks'],\n): void {\n if (summary !== undefined && !summaryAgreesWithKeys(summary, wrappedDeks)) {\n throw new ValidationError(\n wrappedDeks === undefined\n ? 'summary is sealed, but the row has no wrapped keys to open it with'\n : \"summary is in the clear, but the row is encrypted: an encrypted segment's summary must be sealed\",\n );\n }\n}\n\n/**\n * Validate the caller-settable fields at `create`, and return the record to store: the caller's, with its summary\n * replaced by the checked, frozen copy {@link validateSummary} builds.\n */\nexport function validateNewRegistryRecord(rec: NewRegistryRecord): NewRegistryRecord {\n validateGeneration(rec.currentGen);\n if (rec.status !== undefined) validateStatus(rec.status);\n validateWrappedDeks(rec.wrappedDeks, false);\n validateGovernance(rec.retention, 'retention');\n validateGovernance(rec.residency, 'residency');\n if (!('summary' in rec)) return rec;\n const summary = validateSummary(rec.summary, false);\n validateSummaryNames(summary, rec.currentGen);\n validateSummaryKeys(summary, rec.wrappedDeks);\n return { ...rec, summary };\n}\n\n/**\n * Validate the caller-settable fields in a `compareAndSwap` patch, and return the patch to apply: the caller's, with\n * its summary replaced by the checked, frozen copy. That a given `summary` names the row's resulting `currentGen`,\n * and agrees with its resulting keys, needs the stored row, so {@link applyRegistryPatch} checks both.\n */\nexport function validateRegistryPatch(patch: RegistryPatch): RegistryPatch {\n validatePatchGeneration(patch);\n if (patch.status !== undefined) validateStatus(patch.status);\n if ('wrappedDeks' in patch) validateWrappedDeks(patch.wrappedDeks, false);\n if ('retention' in patch) validateGovernance(patch.retention, 'retention');\n if ('residency' in patch) validateGovernance(patch.residency, 'residency');\n if (!('summary' in patch)) return patch;\n return { ...patch, summary: validateSummary(patch.summary, false) };\n}\n\n/**\n * The persisted envelope for a registry row: the record plus a tombstone flag. A **deleted** row keeps its\n * record (with an advanced counter) rather than being removed, so a re-create carries the counter on and the two\n * incarnations' tokens are apart by construction, as well as by their random parts \u2014 ABA-safety. Shared by the\n * persistent drivers (LocalFs file, S3 object).\n *\n * `schemaVersion` is a wire-only concern \u2014 it stamps the persisted bytes, not the in-memory domain object \u2014\n * so it lives on the serialize/parse boundary ({@link serializeRegistryEnvelope} /\n * {@link parseRegistryEnvelope}), never on this interface.\n */\nexport interface RegistryEnvelope {\n readonly deleted: boolean;\n readonly record: RegistryRecord;\n}\n\n/**\n * Current registry-row schema version \u2014 a pre-1.0 format-freeze prerequisite. Every persistent\n * registry driver \u2014 every one of which persists the `{ deleted, record }` envelope \u2014 stamps its rows\n * with this so a reader can fail-closed on a future, incompatible layout instead of misparsing it. Bump only\n * on a backward-incompatible change. Policy: a **higher** stamp than this build knows \u2192 `UnsupportedError`\n * (fail-closed); an **absent** or malformed stamp \u2192 `IntegrityError`, since every row this build writes has one.\n *\n * Schema 2 adds the record's `summary` and the incarnation-form token. Every row this build writes is stamped 2,\n * whatever it holds, and a build that reads only schema 1 refuses it, so a fleet cannot go back once one has been\n * written.\n */\nexport const REGISTRY_SCHEMA_VERSION = 2;\n\n/**\n * The oldest schema this build reads. A schema-1 row is read as it was written; the first write to it stamps it 2.\n */\nconst OLDEST_REGISTRY_SCHEMA_VERSION = 1;\n\n/**\n * Validate a persisted registry row's `schemaVersion` (untrusted bytes, invariant 5) and return it: an absent or\n * malformed value \u2192 `IntegrityError`; a version newer than this build \u2192 `UnsupportedError` (fail-closed rather than\n * misread a format we don't understand).\n */\nexport function assertRegistrySchemaVersion(raw: unknown, ctx: string): number {\n if (raw === undefined) {\n throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);\n }\n if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < OLDEST_REGISTRY_SCHEMA_VERSION) {\n throw new IntegrityError(`registry row has a malformed schemaVersion (${String(raw)}): ${ctx}`);\n }\n if (raw > REGISTRY_SCHEMA_VERSION) {\n throw new UnsupportedError(\n `registry row schemaVersion ${raw} is newer than this build reads (v${REGISTRY_SCHEMA_VERSION}): ${ctx}`,\n );\n }\n return raw;\n}\n\n/** Serialize a registry envelope for persistence, stamping the current {@link REGISTRY_SCHEMA_VERSION}. */\nexport function serializeRegistryEnvelope(env: RegistryEnvelope): string {\n return JSON.stringify({\n schemaVersion: REGISTRY_SCHEMA_VERSION,\n deleted: env.deleted,\n record: env.record,\n });\n}\n\n// \u2500\u2500 The OCC token \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// A token is `<incarnation>.<counter>.<write>`. The incarnation is 32 lowercase hex digits drawn from injected\n// entropy when the row is created; it keeps a re-created name from ever being taken for an earlier incarnation of\n// it, even once the earlier row's record is gone and nothing is left to continue a counter from. The counter is a\n// canonical decimal that every write of the row advances by one, and carries on across a tombstone, so while a row's\n// history is intact its tokens are apart by construction. The write part is 16 lowercase hex digits drawn afresh for\n// every write: a row restored from a backup is back at an older counter, and without it the writes after the restore\n// would be given the tokens the writes after the backup already had.\n//\n// A row first written by a schema-1 build has a bare decimal counter (`\"7\"`), and gains no incarnation for as long as\n// it lives: a write to it advances the counter and adds a write part (`\"8.<write>\"`). Only a create starts a new\n// incarnation. So a token's form says which kind of row issued it, and no two forms ever compare equal: they differ\n// in how many `.` they hold. Tokens are compared only by equality; nothing orders them.\n\n/** A schema-1 row's token: a canonical decimal counter. */\nconst COUNTER_TOKEN = /^(0|[1-9]\\d*)$/;\n/** A schema-1-born row's token once this build has written it: the counter, a `.`, and a write part. */\nconst WRITTEN_COUNTER_TOKEN = /^(0|[1-9]\\d*)\\.([0-9a-f]{16})$/;\n/** The incarnation id's width, in bytes. */\nconst INCARNATION_BYTES = 16;\n/** The write part's width, in bytes. */\nconst WRITE_BYTES = 8;\n\n/** A token taken apart: its incarnation (absent on a schema-1-born row) and its counter. */\ninterface TokenParts {\n readonly incarnation: string | undefined;\n readonly counter: number;\n}\n\n/**\n * Take a token apart, refusing anything that is not a form a shipped driver writes (invariant 5): `\"1e3\"`, `\"0x10\"`,\n * `\" 5 \"`, `\"\"`, a counter past 2^53, upper-case hex. A stored row of `schemaVersion` 1 holds only a bare counter, and\n * one of 2 only a form with a write part; a record already read (`schemaVersion` undefined) may hold any of them.\n */\nfunction tokenParts(token: string, schemaVersion: number | undefined, ctx: string): TokenParts {\n const bare =\n schemaVersion === undefined || schemaVersion === 1 ? COUNTER_TOKEN.exec(token) : null;\n const written =\n schemaVersion === undefined || schemaVersion >= 2\n ? (WRITTEN_COUNTER_TOKEN.exec(token) ?? INCARNATION_TOKEN.exec(token))\n : null;\n const match = bare ?? written;\n if (match === null) {\n throw new IntegrityError(\n `registry row token is not one a schema-${schemaVersion ?? REGISTRY_SCHEMA_VERSION} row holds ` +\n `(${JSON.stringify(token)}): ${ctx}`,\n );\n }\n const born = match.length === 4; // the incarnation form has three groups\n const counter = Number(born ? match[2] : match[1]);\n if (!Number.isSafeInteger(counter)) {\n throw new IntegrityError(`registry row token's counter is out of safe-integer range: ${ctx}`);\n }\n return { incarnation: born ? match[1] : undefined, counter };\n}\n\n// `incarnationOf` is defined in `core/token`, where the publish reads it too, and is the same function here.\nexport { incarnationOf };\n\n/** `bytes` fresh bytes from `entropy`, as lowercase hex, refusing a source that does not give them. */\nfunction drawHex(entropy: Entropy, bytes: number): string {\n const drawn = entropy(bytes);\n if (!(drawn instanceof Uint8Array) || drawn.length !== bytes) {\n throw new ValidationError(\n `the registry's entropy source must return ${bytes} bytes as a Uint8Array`,\n );\n }\n let hex = '';\n for (const b of drawn) hex += b.toString(16).padStart(2, '0');\n return hex;\n}\n\n/** A fresh incarnation id from `entropy`: 128 bits as 32 lowercase hex digits. */\nexport function drawIncarnation(entropy: Entropy): string {\n return drawHex(entropy, INCARNATION_BYTES);\n}\n\n/** A fresh write part from `entropy`: 64 bits as 16 lowercase hex digits. */\nexport function drawWrite(entropy: Entropy): string {\n return drawHex(entropy, WRITE_BYTES);\n}\n\n/**\n * The token for the write after `record`'s: the same incarnation, if it has one, the counter one on, and a fresh write\n * part. For a compare-and-swap or a tombstone.\n */\nexport function nextRegistryToken(record: RegistryRecord, entropy: Entropy): Token {\n const { incarnation, counter } = tokenParts(record.token, undefined, record.segment);\n const tail = `${counter + 1}.${drawWrite(entropy)}`;\n return incarnation === undefined ? tail : `${incarnation}.${tail}`;\n}\n\n/**\n * The token of a new row: a fresh incarnation and write part from `entropy`, and a counter that continues\n * `tombstone`'s when the row is created over one, else starts at 0.\n */\nexport function newIncarnationToken(\n entropy: Entropy,\n tombstone: RegistryRecord | undefined,\n): Token {\n const counter =\n tombstone === undefined\n ? 0\n : tokenParts(tombstone.token, undefined, tombstone.segment).counter + 1;\n return `${drawIncarnation(entropy)}.${counter}.${drawWrite(entropy)}`;\n}\n\n/**\n * Parse + structurally validate a persisted `{ deleted, record }` envelope from stored bytes. A published row\n * is always whole (atomic write), so a parse failure or a missing/mistyped field means corruption/tampering \u2014\n * fail fast (invariant 5), never silently report \"absent\". `ctx` names the source (path/key) for the message.\n */\nexport function parseRegistryEnvelope(text: string, ctx: string): RegistryEnvelope {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new IntegrityError(`registry row is not valid JSON: ${ctx}`);\n }\n // Guard before any property access: `JSON.parse` yields `null`/primitives for hostile bytes (e.g. `\"null\"`,\n // `\"5\"`), which would throw an *untyped* TypeError on the dereference below (invariant 5 wants a typed error).\n if (parsed === null || typeof parsed !== 'object') {\n throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);\n }\n const env = parsed as { schemaVersion?: unknown; deleted?: unknown; record?: unknown };\n const schemaVersion = assertRegistrySchemaVersion(env.schemaVersion, ctx);\n const extra = Object.keys(env).filter((k) => !ENVELOPE_FIELDS.includes(k));\n if (extra.length > 0) {\n throw new IntegrityError(\n `registry row has fields its envelope does not declare (${extra.join(', ')}): ${ctx}`,\n );\n }\n if (typeof env.deleted !== 'boolean' || env.record === null || typeof env.record !== 'object') {\n throw new IntegrityError(`registry row has a malformed envelope: ${ctx}`);\n }\n const r = env.record as Record<string, unknown>;\n assertStoredRecordShape(r, ctx, schemaVersion);\n if (typeof r.token !== 'string') {\n throw new IntegrityError(`registry row is missing its token: ${ctx}`);\n }\n tokenParts(r.token, schemaVersion, ctx);\n return { deleted: env.deleted, record: env.record as RegistryRecord };\n}\n\n/** Build a full record from a {@link NewRegistryRecord} plus identity, audit timestamps, and an OCC token. */\nexport function recordFromNew(\n ref: SegmentRef,\n rec: NewRegistryRecord,\n now: number,\n token: Token,\n): RegistryRecord {\n return {\n namespace: ref.namespace,\n segment: ref.segment,\n currentGen: rec.currentGen,\n wrappedDeks: rec.wrappedDeks,\n keyId: rec.keyId,\n status: rec.status ?? 'active',\n retention: rec.retention,\n residency: rec.residency,\n summary: rec.summary,\n createdAt: now,\n updatedAt: now,\n token,\n };\n}\n\n/**\n * Fail-fast structural check on a record read back from a persistent tier (untrusted bytes, invariant 5):\n * a published row is always whole, so a missing/mistyped required field means corruption/tampering \u2014 reject\n * it rather than silently report \"absent\". A row of an older `schemaVersion` may carry only that schema's\n * fields. `token` is checked by the caller.\n */\nexport function assertStoredRecordShape(\n r: Record<string, unknown>,\n ctx: string,\n schemaVersion: number = REGISTRY_SCHEMA_VERSION,\n): void {\n if (\n typeof r.segment !== 'string' ||\n (r.namespace !== undefined && typeof r.namespace !== 'string') ||\n (r.currentGen !== null && typeof r.currentGen !== 'number') ||\n typeof r.status !== 'string' ||\n typeof r.createdAt !== 'number' ||\n typeof r.updatedAt !== 'number'\n ) {\n throw new IntegrityError(`registry record is missing required fields: ${ctx}`);\n }\n // Enforce the same value invariants the write path checks, so corrupt/tampered bytes are rejected at the\n // read boundary (invariant 5) rather than leaking a bad currentGen/status downstream.\n if (r.currentGen !== null && (!Number.isInteger(r.currentGen) || r.currentGen < 0)) {\n throw new IntegrityError(`registry record has an invalid currentGen (${r.currentGen}): ${ctx}`);\n }\n if (!STATUSES.includes(r.status)) {\n throw new IntegrityError(`registry record has an unknown status (${r.status}): ${ctx}`);\n }\n const declared: readonly string[] = schemaVersion >= 2 ? RECORD_FIELDS : SCHEMA_1_RECORD_FIELDS;\n const extra = Object.keys(r).filter((k) => !declared.includes(k));\n if (extra.length > 0) {\n throw new IntegrityError(\n `registry record has fields a schema-${schemaVersion} row does not declare (${extra.join(', ')}): ${ctx}`,\n );\n }\n if (r.keyId !== undefined && typeof r.keyId !== 'string') {\n throw new IntegrityError(`registry record has an invalid keyId: ${ctx}`);\n }\n validateWrappedDeks(r.wrappedDeks, true); // invariant 5: reject a corrupt wrapped-DEK list on read-back\n // The governance blobs' SHAPE is checked on read-back too, because one of them carries semantics:\n // `retention.expiresAt` is read with an `in` test, which throws an untyped `TypeError` on a stored\n // `null`/string/number. That is reachable \u2014 the write boundary only checks\n // JSON-serializability and size, so `\"retention\": null` round-trips through every driver from a hand-edit,\n // another writer, or a restore \u2014 and it would abort a whole fleet retention sweep rather than becoming one\n // ledger entry. Reject it here, typed, at the trust boundary (invariant 5).\n for (const field of ['retention', 'residency'] as const) {\n const meta = r[field];\n if (meta !== undefined && (meta === null || typeof meta !== 'object' || Array.isArray(meta))) {\n throw new IntegrityError(`registry record has a non-object ${field}: ${ctx}`);\n }\n }\n // Shape only. Whether the summary agrees with the row's keys and names its `currentGen` is not checked here: a\n // stored row that disagrees is a summary its reader must not use, not a row to refuse, and refusing it would let\n // one row stop every listing that reaches it.\n validateSummary(r.summary, true, ctx);\n}\n\n/**\n * Apply a patch to an existing record, returning a new one with a fresh `updatedAt` + `token` (identity and\n * `createdAt` are preserved). Optional fields use `'k' in patch` so a patch can *clear* them (set to\n * `undefined`, e.g. dropping `keyId` on crypto-shred); required fields use `??` (they always have a value).\n *\n * `summary` describes the current generation, so it follows the pointer and the keys: a patch that moves\n * `currentGen`, or changes `wrappedDeks` so the summary's shape no longer agrees, without mentioning `summary` drops\n * the old one; and a summary the patch gives must name the resulting `currentGen` and agree with the resulting keys\n * (`ValidationError`, before anything is written).\n */\nexport function applyRegistryPatch(\n prev: RegistryRecord,\n patch: RegistryPatch,\n now: number,\n token: Token,\n): RegistryRecord {\n // `'currentGen' in patch`, NOT `patch.currentGen ?? prev.currentGen`. `null` is a legal, meaningful value\n // here (no Storage generation yet), and `??` treats it as absent \u2014 so the nullish form would silently ignore a\n // patch that clears the pointer and leave the old generation in place. The same reason `wrappedDeks` and\n // `keyId` below use presence: any field whose null is a *value* cannot be merged with `??`.\n const currentGen = 'currentGen' in patch ? (patch.currentGen ?? null) : prev.currentGen;\n const wrappedDeks = 'wrappedDeks' in patch ? patch.wrappedDeks : prev.wrappedDeks;\n let summary: RegistrySummary | undefined;\n if ('summary' in patch) {\n validateSummaryNames(patch.summary, currentGen);\n validateSummaryKeys(patch.summary, wrappedDeks);\n summary = patch.summary;\n } else {\n // Kept only while it still describes the row: the same generation, and the same encryption.\n const kept = currentGen === prev.currentGen ? prev.summary : undefined;\n summary = kept !== undefined && summaryAgreesWithKeys(kept, wrappedDeks) ? kept : undefined;\n }\n return {\n namespace: prev.namespace,\n segment: prev.segment,\n currentGen,\n wrappedDeks,\n keyId: 'keyId' in patch ? patch.keyId : prev.keyId,\n status: patch.status ?? prev.status,\n retention: 'retention' in patch ? patch.retention : prev.retention,\n residency: 'residency' in patch ? patch.residency : prev.residency,\n summary,\n createdAt: prev.createdAt,\n updatedAt: now,\n token,\n };\n}\n", "/**\n * The default entropy a shipped registry draws its tokens' random parts from: the platform's Web Crypto.\n *\n * Kept apart from the record helpers, which are pure, because this reads an ambient global. It is checked when a\n * row is written, not when a registry is built, so a process that only reads runs on a runtime without it.\n */\nimport { UnsupportedError } from '@/core/errors';\nimport type { Entropy } from '@/core/determinism';\n\ninterface WebCrypto {\n getRandomValues(array: Uint8Array): Uint8Array;\n}\n\n/** The platform's Web Crypto, or `undefined` when this runtime has none. */\nfunction webCrypto(): WebCrypto | undefined {\n const c = (globalThis as { crypto?: { getRandomValues?: unknown } }).crypto;\n return c !== undefined && typeof c.getRandomValues === 'function' ? (c as WebCrypto) : undefined;\n}\n\n/** Draws from the platform's Web Crypto; `UnsupportedError` on a runtime without it. */\nexport const webCryptoEntropy: Entropy = (length) => {\n const c = webCrypto();\n if (c === undefined) {\n throw new UnsupportedError(\n 'this runtime has no Web Crypto (crypto.getRandomValues), which a registry needs for the token of every ' +\n 'row it writes: run on a runtime that has it',\n );\n }\n return c.getRandomValues(new Uint8Array(length));\n};\n\n/** Whether `entropy` can draw here: an injected source always, the default only where Web Crypto exists. */\nexport function entropyIsAvailable(entropy: Entropy): boolean {\n return entropy !== webCryptoEntropy || webCrypto() !== undefined;\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ChunkRead } from './ports.js';
|
|
2
|
+
/**
|
|
3
|
+
* One operand's chunks as the source streams them, taken a key at a time. The engine asks for keys in ascending order
|
|
4
|
+
* and the stream yields them in that order, so each `take` is the next item; `take` calls the iterator at once, before
|
|
5
|
+
* anything is awaited, which is what keeps concurrent takes lined up with the keys they were made for (an async
|
|
6
|
+
* generator answers overlapping `next()` calls in the order they were made).
|
|
7
|
+
*
|
|
8
|
+
* A stream that fails fails every take after the one that met it with the same error, and a stream that ends before the
|
|
9
|
+
* key asked for, or answers another key, is an {@link IntegrityError}: a source that misaligns its answers must not
|
|
10
|
+
* hand one chunk's bytes to another key.
|
|
11
|
+
*
|
|
12
|
+
* Internal to core: not exported from any entry point.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ChunkStream {
|
|
15
|
+
private readonly iterator;
|
|
16
|
+
private failure;
|
|
17
|
+
constructor(stream: AsyncIterable<ChunkRead>);
|
|
18
|
+
/** The chunk for `key`, which must be the next key the stream was opened over. */
|
|
19
|
+
take(key: number): Promise<ChunkRead>;
|
|
20
|
+
/** Stop the stream: no further range is started, and ranges already in flight finish and are dropped. */
|
|
21
|
+
close(): void;
|
|
22
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* An ordered window of reads: up to `max` reads open ahead of the one being consumed, handed back in key order.
|
|
3
|
-
* With `ramp`, the window opens 1, 2, 4 … wide up to `max` instead of `max` at once, so a consumer that stops
|
|
3
|
+
* With `ramp`, the window opens 1, 2, 4 … wide (or at `rampStart`, then doubling) up to `max` instead of `max` at once, so a consumer that stops
|
|
4
4
|
* after a few chunks has fetched a handful, not a full window.
|
|
5
5
|
*
|
|
6
6
|
* `T` is whatever one read yields (a decoded chunk, or the raw bytes of one). Each read is wrapped to resolve and
|
|
@@ -15,10 +15,16 @@ export declare class ChunkWindow<T> {
|
|
|
15
15
|
private readonly fetch;
|
|
16
16
|
private readonly max;
|
|
17
17
|
private readonly ramp;
|
|
18
|
+
/** With `ramp`, the width the window opens at (default 1). */
|
|
19
|
+
private readonly rampStart;
|
|
18
20
|
private readonly open;
|
|
19
21
|
private launched;
|
|
20
22
|
private taken;
|
|
21
|
-
constructor(keys: readonly number[], fetch: (chunkKey: number) => Promise<T>, max: number, ramp: boolean
|
|
23
|
+
constructor(keys: readonly number[], fetch: (chunkKey: number) => Promise<T>, max: number, ramp: boolean,
|
|
24
|
+
/** With `ramp`, the width the window opens at (default 1). */
|
|
25
|
+
rampStart?: number);
|
|
26
|
+
/** Resolves once every read launched and not yet taken has settled; it never rejects, as the reads never do. */
|
|
27
|
+
settle(): Promise<void>;
|
|
22
28
|
/** The next read's result in key order, or the error that read raised. */
|
|
23
29
|
take(): Promise<T>;
|
|
24
30
|
}
|
package/dist/core/codec.d.ts
CHANGED
|
@@ -52,6 +52,17 @@ export interface CodecBitmap {
|
|
|
52
52
|
/** Ascending iterator over the set values. */
|
|
53
53
|
[Symbol.iterator](): IterableIterator<number>;
|
|
54
54
|
toArray(): number[];
|
|
55
|
+
/**
|
|
56
|
+
* The set's values as one ascending `Uint32Array` the caller owns: what a batch read converts a chunk with, in one
|
|
57
|
+
* native step instead of one iterator step per value.
|
|
58
|
+
*
|
|
59
|
+
* **Optional.** A codec without it is read through its iterator, which is correct and slower.
|
|
60
|
+
*
|
|
61
|
+
* **It must return a NEW array on every call, which the caller owns:** the engine adds the chunk's base to each
|
|
62
|
+
* value in place and hands the array on, so an array that is shared, cached or a view of the bitmap's own storage
|
|
63
|
+
* would be corrupted, and would corrupt the bitmap. It holds exactly what the iterator yields, ascending.
|
|
64
|
+
*/
|
|
65
|
+
toUint32Array?(): Uint32Array;
|
|
55
66
|
/**
|
|
56
67
|
* The largest value in the set, or `undefined` when empty.
|
|
57
68
|
*
|
package/dist/core/cost.d.ts
CHANGED
|
@@ -110,20 +110,25 @@ export interface Workload {
|
|
|
110
110
|
/** Cache hit rate in `[0, 1]` — hits are free; only misses cost. Default 0. */
|
|
111
111
|
readonly cacheHitRate?: number;
|
|
112
112
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* the
|
|
117
|
-
*
|
|
113
|
+
* Range requests one intersection makes for its chunks, summed over its operands. Default 1. The engine reads each
|
|
114
|
+
* operand's needed chunks (the chunk-skipping survivors) as coalesced ranges: chunks within 256 KiB of each other
|
|
115
|
+
* are read in one request, up to 1 MiB, so this is the requests the chunks need, not the chunks. It follows the
|
|
116
|
+
* bytes the needed chunks span in each object: two segments sharing 100 chunks that sit side by side make one request
|
|
117
|
+
* of each, `2`; the same 100 spread over each object make as many as it takes to cover most of the object, a
|
|
118
|
+
* request per MiB, and read most of its bytes. Count it by running the engine over a layout of your own, as
|
|
119
|
+
* `bench/range-counts.cjs` does, or take it as one request per MiB the shared chunks span, per operand. The model adds each operand's pointer and index reads itself (see
|
|
120
|
+
* {@link Workload.operandsPerIntersect}), so count chunk requests only. One more GET per operand whose index
|
|
121
|
+
* outgrows the reader's tail read (256 KiB by default), which then reads the index whole, belongs here too, and so
|
|
122
|
+
* does a pointer re-read by an intersect slow enough to outlive {@link Workload.genTtlMs}.
|
|
118
123
|
*/
|
|
119
124
|
readonly chunksPerIntersect?: number;
|
|
120
125
|
/**
|
|
121
126
|
* Segments each intersection reads, `exclude` operands included; at least 1. Default 2. An intersection is priced
|
|
122
127
|
* **cold**: before its chunks, each operand's pointer is read, then its index, in one read of the object's tail —
|
|
123
|
-
* 2 GETs an operand on S3 and GCS, so a cold intersect of two segments
|
|
128
|
+
* 2 GETs an operand on S3 and GCS, so a cold intersect that makes `r` range requests of each of two segments is `4 + 2r` GETs there,
|
|
124
129
|
* and 3 on Azure Blob, whose tail read is two requests (see {@link PricingProfile}). `cacheHitRate`
|
|
125
130
|
* does not apply to intersections, so a long-lived reader that answers a repeat from its cache pays less. Other
|
|
126
|
-
* combines read their operands the same way and can be priced here too, with the
|
|
131
|
+
* combines read their operands the same way and can be priced here too, with the chunk requests they make.
|
|
127
132
|
*/
|
|
128
133
|
readonly operandsPerIntersect?: number;
|
|
129
134
|
/**
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The most unneeded bytes one read may carry between two needed chunks: reading 256 KiB that nothing wants costs
|
|
3
|
+
* a few milliseconds of transfer, while the request it saves costs a round trip.
|
|
4
|
+
*/
|
|
5
|
+
export declare const MAX_COALESCE_GAP_BYTES: number;
|
|
6
|
+
/**
|
|
7
|
+
* The most bytes one read may span, unless it holds a single chunk. It equals the decode cap, so a window of
|
|
8
|
+
* coalesced reads holds the window's width times the per-chunk cap that bounds a window of single-chunk reads.
|
|
9
|
+
*/
|
|
10
|
+
export declare const MAX_COALESCED_READ_BYTES: number;
|
|
11
|
+
/**
|
|
12
|
+
* The default number of range reads a chunk stream holds ahead of its consumer, in flight or landed and not yet taken;
|
|
13
|
+
* the rest of the plan follows as the consumer takes them. A range is at most {@link MAX_COALESCED_READ_BYTES} unless
|
|
14
|
+
* it is one chunk, so a stream holds at most this many times the per-chunk decode cap, whatever the key list and the
|
|
15
|
+
* object: the memory bound a window of single-chunk reads has, and no more connections than it opens.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MAX_RANGES_IN_FLIGHT = 32;
|
|
18
|
+
/** One needed chunk, as its index entry places it in the object. */
|
|
19
|
+
export interface ChunkExtent {
|
|
20
|
+
readonly key: number;
|
|
21
|
+
readonly offset: number;
|
|
22
|
+
readonly length: number;
|
|
23
|
+
}
|
|
24
|
+
/** One byte range to read, and the needed chunks inside it, in object order. */
|
|
25
|
+
export interface PlannedRead {
|
|
26
|
+
readonly offset: number;
|
|
27
|
+
readonly length: number;
|
|
28
|
+
readonly chunks: readonly ChunkExtent[];
|
|
29
|
+
}
|
|
30
|
+
/** The part of an object that holds chunk payloads: `[start, end)`. */
|
|
31
|
+
export interface ChunkRegion {
|
|
32
|
+
readonly start: number;
|
|
33
|
+
readonly end: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Plan the reads for `extents`: the needed chunks of one object, ascending by offset (the index lists chunks in key
|
|
37
|
+
* order, which is object order), each at least a byte long, none overlapping another. The reads come back
|
|
38
|
+
* ascending and disjoint, each covering one or more of the chunks, every chunk in exactly one read.
|
|
39
|
+
*
|
|
40
|
+
* Throws {@link IntegrityError} for an extent that is not a pair of safe integers, or is outside `region`, or out of
|
|
41
|
+
* order, or overlapping the one before it:
|
|
42
|
+
* the index a plan is made from is untrusted bytes, and a read is never planned outside the chunk region.
|
|
43
|
+
*/
|
|
44
|
+
export declare function planChunkReads(extents: readonly ChunkExtent[], region: ChunkRegion): readonly PlannedRead[];
|