@cloudbitmaps/core 0.11.2 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/{chunk-5OMC5U7U.js → chunk-5W3XS2IU.js} +292 -22
  2. package/dist/chunk-5W3XS2IU.js.map +7 -0
  3. package/dist/core/audit.d.ts +6 -0
  4. package/dist/core/chunk-window.d.ts +24 -0
  5. package/dist/core/codec.d.ts +33 -1
  6. package/dist/core/consistency.d.ts +21 -2
  7. package/dist/core/cost.d.ts +91 -10
  8. package/dist/core/crbm/format.d.ts +42 -3
  9. package/dist/core/crbm/reader.d.ts +45 -3
  10. package/dist/core/crbm/writer.d.ts +32 -3
  11. package/dist/core/crbm-storage-source.d.ts +204 -23
  12. package/dist/core/crypto.d.ts +11 -7
  13. package/dist/core/determinism.d.ts +7 -0
  14. package/dist/core/engine.d.ts +41 -8
  15. package/dist/core/erase-id.d.ts +23 -3
  16. package/dist/core/erasure.d.ts +8 -4
  17. package/dist/core/generation-gc.d.ts +102 -4
  18. package/dist/core/load-input.d.ts +55 -0
  19. package/dist/core/load.d.ts +65 -10
  20. package/dist/core/metadata.d.ts +42 -0
  21. package/dist/core/metrics.d.ts +7 -1
  22. package/dist/core/pinned-storage-source.d.ts +2 -1
  23. package/dist/core/ports.d.ts +119 -13
  24. package/dist/core/registry-scan.d.ts +6 -0
  25. package/dist/core/retention-sweep.d.ts +47 -2
  26. package/dist/core/retry.d.ts +14 -0
  27. package/dist/core/rollback.d.ts +34 -4
  28. package/dist/core/summary.d.ts +61 -0
  29. package/dist/core/token.d.ts +36 -0
  30. package/dist/core/validate.d.ts +2 -0
  31. package/dist/driver-kit.js +39 -20
  32. package/dist/driver-kit.js.map +2 -2
  33. package/dist/drivers/_shared/entropy.d.ts +5 -0
  34. package/dist/drivers/_shared/object-registry.d.ts +36 -6
  35. package/dist/drivers/_shared/registry.d.ts +47 -19
  36. package/dist/drivers/localfs/registry.d.ts +10 -0
  37. package/dist/drivers/memory.d.ts +9 -3
  38. package/dist/drivers/retry/retrying-chunk-source.d.ts +2 -1
  39. package/dist/index.d.ts +4 -3
  40. package/dist/index.js +1878 -501
  41. package/dist/index.js.map +4 -4
  42. package/dist/testing/fuzz-core.d.ts +7 -6
  43. package/package.json +1 -1
  44. package/dist/chunk-5OMC5U7U.js.map +0 -7
@@ -1,14 +1,15 @@
1
1
  /**
2
2
  * Internal surface for the coverage-guided fuzz harness, CORE half.
3
3
  *
4
- * Exposes the untrusted-bytes entry points the fuzz targets drive — including `parseIndex`, which is
5
- * deliberately **NOT public API**. Bundled by its own esbuild entry in `scripts/build.mjs` to the git-ignored repo-root `fuzz/build/`,
4
+ * Exposes the untrusted-bytes entry points the fuzz targets drive — including `parseIndex` and `parseExtension`,
5
+ * which are deliberately **NOT public API**. Bundled by its own esbuild entry in `scripts/build.mjs` to the git-ignored repo-root `fuzz/build/`,
6
6
  * so it never enters `dist/` or the published package. Keeping it here (rather than widening
7
7
  * `packages/core/src/index.ts`) is the point: a test harness must not expand the published surface.
8
8
  * The codec half lives in the flavor package (`packages/roaring/src/testing/fuzz-codec.ts`), because a concrete
9
9
  * bitmap codec is exactly what core does not have.
10
10
  */
11
- export { CrbmReader, parseIndex } from '../core/crbm/reader.js';
12
- export { BufferReader } from '../core/blob.js';
13
- export { CloudRoaringError, isCloudRoaringError } from '../core/errors.js';
14
- export { DEFAULT_MAX_PAYLOAD_BYTES } from '../core/crbm/format.js';
11
+ export { CrbmReader, parseExtension, parseIndex } from '../core/crbm/reader.js';
12
+ export { CrbmWriter } from '../core/crbm/writer.js';
13
+ export { BufferReader, BufferSink } from '../core/blob.js';
14
+ export { CloudRoaringError, IntegrityError, isCloudRoaringError } from '../core/errors.js';
15
+ export { DEFAULT_MAX_BITMAP_BYTES, DEFAULT_MAX_PAYLOAD_BYTES } from '../core/crbm/format.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbitmaps/core",
3
- "version": "0.11.2",
3
+ "version": "0.13.0",
4
4
  "description": "Codec-agnostic engine for CloudBitmaps: the .crbm format, the write-once load/publish path, generation GC, encryption-at-rest, and the driver ports. No cloud SDK.",
5
5
  "keywords": [
6
6
  "roaring",
@@ -1,7 +0,0 @@
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/drivers/_shared/registry.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/**\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/** 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 { 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/** 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: 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()` \u2014 the engine sums the index 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 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 `max(currentGen, highest object) + 1`, so the number **restarts at 0** once\n * a registry row is purged and the bucket emptied. A retired, re-created name therefore serves different\n * data at the same `currentGen`, and a cache keyed on `(segment, chunk, generation)` hands back the previous\n * 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 S3 driver and the single-request GCS upload do; the second is what the Azure Blob driver and the\n * resumable GCS upload do.\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 * 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 /** Epoch-ms of creation / last mutation (from the driver's injected clock). */\n readonly createdAt: number;\n readonly updatedAt: number;\n /** Opaque OCC token \u2014 compare-by-equality, never reused (ABA-safe). */\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}\n\n/** Fields a {@link IRegistryDriver.compareAndSwap} may mutate (identity + audit + token are off-limits). */\nexport type RegistryPatch = Partial<\n Pick<\n RegistryRecord,\n 'currentGen' | 'wrappedDeks' | 'keyId' | 'status' | 'retention' | 'residency'\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\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 * - **Tokens are never reused**, not even across `delete` then `create`: a delete leaves a tombstone (or a\n * global counter) so a recreated row always carries a fresh token and a stale holder cannot swap into it.\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 (tombstoned for ABA-safety \u2014 a later `create` still gets a fresh, greater token).\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 * 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`).\n */\nimport { IntegrityError, UnsupportedError, ValidationError } from '@/core/errors';\nimport type {\n NewRegistryRecord,\n RegistryPatch,\n RegistryRecord,\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 'createdAt',\n 'updatedAt',\n 'token',\n] as const;\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\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/** Validate the caller-settable fields at `create`. */\nexport function validateNewRegistryRecord(rec: NewRegistryRecord): void {\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}\n\n/** Validate the caller-settable fields in a `compareAndSwap` patch. */\nexport function validateRegistryPatch(patch: RegistryPatch): void {\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}\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 the monotonic token survives a\n * delete\u2192recreate \u2014 ABA-safety. Shared by the 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 */\nexport const REGISTRY_SCHEMA_VERSION = 1;\n\n/**\n * Validate a persisted registry row's `schemaVersion` (untrusted bytes, invariant 5): an absent or malformed value\n * \u2192 `IntegrityError`; a version newer than this build \u2192 `UnsupportedError` (fail-closed rather than misread a\n * format we don't understand).\n */\nexport function assertRegistrySchemaVersion(raw: unknown, ctx: string): void {\n if (raw === undefined) {\n throw new IntegrityError(`registry row has no schemaVersion: ${ctx}`);\n }\n if (typeof raw !== 'number' || !Number.isInteger(raw) || raw < 1) {\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}\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/**\n * The OCC counter encoded in a record's token. A published row always has a **canonical decimal** token\n * (`String(counter)`), so anything else (`\"1e3\"`, `\"0x10\"`, `\" 5 \"`, `\"\"`, a >2^53 value) is corruption /\n * tampering \u2014 reject it (invariant 5) rather than let `Number()` coerce it and silently break monotonicity.\n */\nexport function registryCounterOf(record: RegistryRecord): number {\n if (!/^(0|[1-9]\\d*)$/.test(record.token)) {\n throw new IntegrityError(`registry row token is not a canonical counter: ${record.token}`);\n }\n const n = Number(record.token);\n if (!Number.isSafeInteger(n)) {\n throw new IntegrityError(`registry row token is out of safe-integer range: ${record.token}`);\n }\n return n;\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 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);\n if (typeof r.token !== 'string') {\n throw new IntegrityError(`registry row is missing its token: ${ctx}`);\n }\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 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\". `token` is checked by the caller (its source differs per backend:\n * an in-body field vs a derived OCC counter).\n */\nexport function assertStoredRecordShape(r: Record<string, unknown>, ctx: string): 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 extra = Object.keys(r).filter((k) => !(RECORD_FIELDS as readonly string[]).includes(k));\n if (extra.length > 0) {\n throw new IntegrityError(\n `registry record has fields it 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}\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 */\nexport function applyRegistryPatch(\n prev: RegistryRecord,\n patch: RegistryPatch,\n now: number,\n token: Token,\n): RegistryRecord {\n return {\n namespace: prev.namespace,\n segment: prev.segment,\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 currentGen: 'currentGen' in patch ? (patch.currentGen ?? null) : prev.currentGen,\n wrappedDeks: 'wrappedDeks' in patch ? patch.wrappedDeks : prev.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 createdAt: prev.createdAt,\n updatedAt: now,\n token,\n };\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }