@cloudbitmaps/core 0.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +12 -0
- package/README.md +22 -0
- package/dist/azure/index.cjs +368 -0
- package/dist/azure/index.cjs.map +1 -0
- package/dist/azure/index.d.cts +48 -0
- package/dist/azure/index.d.ts +48 -0
- package/dist/azure/index.js +304 -0
- package/dist/azure/index.js.map +1 -0
- package/dist/cassandra/index.cjs +265 -0
- package/dist/cassandra/index.cjs.map +1 -0
- package/dist/cassandra/index.d.cts +52 -0
- package/dist/cassandra/index.d.ts +52 -0
- package/dist/cassandra/index.js +204 -0
- package/dist/cassandra/index.js.map +1 -0
- package/dist/chunk-2YDULGXS.js +203 -0
- package/dist/chunk-2YDULGXS.js.map +1 -0
- package/dist/chunk-7LMLYSVJ.js +43 -0
- package/dist/chunk-7LMLYSVJ.js.map +1 -0
- package/dist/chunk-AS6ODRLT.js +6 -0
- package/dist/chunk-AS6ODRLT.js.map +1 -0
- package/dist/chunk-NUIDEEFZ.js +91 -0
- package/dist/chunk-NUIDEEFZ.js.map +1 -0
- package/dist/chunk-SNJVZ227.js +35 -0
- package/dist/chunk-SNJVZ227.js.map +1 -0
- package/dist/dynamodb/index.cjs +731 -0
- package/dist/dynamodb/index.cjs.map +1 -0
- package/dist/dynamodb/index.d.cts +106 -0
- package/dist/dynamodb/index.d.ts +106 -0
- package/dist/dynamodb/index.js +460 -0
- package/dist/dynamodb/index.js.map +1 -0
- package/dist/gcs/index.cjs +343 -0
- package/dist/gcs/index.cjs.map +1 -0
- package/dist/gcs/index.d.cts +46 -0
- package/dist/gcs/index.d.ts +46 -0
- package/dist/gcs/index.js +279 -0
- package/dist/gcs/index.js.map +1 -0
- package/dist/index.cjs +4031 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1945 -0
- package/dist/index.d.ts +1945 -0
- package/dist/index.js +3642 -0
- package/dist/index.js.map +1 -0
- package/dist/mongodb/index.cjs +260 -0
- package/dist/mongodb/index.cjs.map +1 -0
- package/dist/mongodb/index.d.cts +45 -0
- package/dist/mongodb/index.d.ts +45 -0
- package/dist/mongodb/index.js +199 -0
- package/dist/mongodb/index.js.map +1 -0
- package/dist/mysql/index.cjs +281 -0
- package/dist/mysql/index.cjs.map +1 -0
- package/dist/mysql/index.d.cts +56 -0
- package/dist/mysql/index.d.ts +56 -0
- package/dist/mysql/index.js +211 -0
- package/dist/mysql/index.js.map +1 -0
- package/dist/ports-D3BrJ6ax.d.cts +357 -0
- package/dist/ports-D3BrJ6ax.d.ts +357 -0
- package/dist/postgres/index.cjs +273 -0
- package/dist/postgres/index.cjs.map +1 -0
- package/dist/postgres/index.d.cts +54 -0
- package/dist/postgres/index.d.ts +54 -0
- package/dist/postgres/index.js +203 -0
- package/dist/postgres/index.js.map +1 -0
- package/dist/redis/index.cjs +257 -0
- package/dist/redis/index.cjs.map +1 -0
- package/dist/redis/index.d.cts +42 -0
- package/dist/redis/index.d.ts +42 -0
- package/dist/redis/index.js +197 -0
- package/dist/redis/index.js.map +1 -0
- package/dist/s3/index.cjs +891 -0
- package/dist/s3/index.cjs.map +1 -0
- package/dist/s3/index.d.cts +112 -0
- package/dist/s3/index.d.ts +112 -0
- package/dist/s3/index.js +555 -0
- package/dist/s3/index.js.map +1 -0
- package/package.json +172 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/errors.ts","../../src/core/ports.ts","../../src/drivers/_shared/aws-errors.ts","../../src/drivers/dynamodb/dynamodb-errors.ts","../../src/core/validate.ts","../../src/drivers/_shared/keys.ts","../../src/drivers/dynamodb/keys.ts","../../src/drivers/dynamodb/warm.ts","../../src/drivers/_shared/registry.ts","../../src/drivers/dynamodb/registry.ts"],"names":["GetItemCommand","UpdateItemCommand","QueryCommand","ConditionalCheckFailedException","MUTATE_NAMES","ONE","TRUE","FALSE","ScanCommand"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Typed errors — callers learn *why* something failed, never by parsing strings. Retry is the driver\n * decorators' job, not the engine's: a driver classifies its backend's failures into this vocabulary, and\n * `withRetry` decides what is transient.\n */\n\n/**\n * Registry-symbol brands. The package ships as multiple bundles — the core entry and the `./s3` / `./dynamodb`\n * subpaths — and the builder inlines `core/errors` into each. A driver in a subpath bundle therefore throws a\n * *different* class object than the one the core engine/retry code would `instanceof`-check, so `instanceof`\n * silently returns false across that boundary in the published CJS package (defeating OCC/transient retry and\n * compaction race-handling). These `Symbol.for` brands are identity-stable across bundles/realms; classify\n * errors with the exported predicates below (never `instanceof`) anywhere an error may cross the boundary.\n */\nconst ERROR_BRAND: unique symbol = Symbol.for('cloud-roaring.error');\nconst TRANSIENT_BRAND: unique symbol = Symbol.for('cloud-roaring.error.transient');\n\n/** Base class for every error CloudRoaring throws. */\nexport class CloudRoaringError extends Error {\n /** Cross-bundle brand — see the predicates ({@link isCloudRoaringError}, …). Non-enumerable-ish (symbol key ⇒ 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 — 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 — 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; thrown by\n * persistent drivers from Phase 2 — the Phase-1 engine + in-memory drivers return `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** — the bytes are well-formed but unreadable here (an unknown `.crbm` major version, an\n * encrypted file before the crypto path exists) — distinct from `IntegrityError` (corruption); and (2)\n * **store configuration** — an operation this store's wiring doesn't support (e.g. a lifecycle helper like\n * `compact`/`eraseSubject` called on a store built without a raw cold driver + registry). 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 Cold 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** — too many backend requests for a single\n * `count`/`iterate`/`intersect`/`subjectReport`/`eraseSubject` call — so it is refused **before** fanning out\n * (Decision #3 / invariant T3). 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 — 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 — 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 that is safe to retry — throttling, a 5xx, a dropped connection,\n * a client-side request timeout. 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 (threat-model S12): `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 the whole transient subtree (incl. {@link TimeoutError}) 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 * A single attempt exceeded its time budget. Subclass of {@link TransientError} so the retry layer treats a\n * timeout as retryable by default — a stalled request often succeeds on a fresh connection. Raised by a\n * driver whose injected client reports a request timeout — setting one on your injected client is the\n * recommended way to bound a hang.\n */\nexport class TimeoutError extends TransientError {}\n\n/**\n * Bundle-safe error predicates — use these, not `instanceof`, wherever an error may cross the core↔driver\n * (`./s3` / `./dynamodb`) 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 — 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 (incl. {@link TimeoutError}). 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 * Storage-driver contracts the engine depends on.\n *\n * Phase 1 uses the subset needed by the in-memory engine: the per-chunk Warm store (under OCC) and a\n * per-chunk read view of Cold. Drivers move opaque bytes + an OCC token; they never understand roaring,\n * `adds`/`removes`, or the `.crbm` layout. The full `IColdDriver` (blob/range) + `IRegistryDriver` arrive\n * with generations in Phase 2/3 — the `.crbm` reader will implement `ColdChunkSource`.\n */\n\nimport type { BlobSink } from './blob';\nimport type { WrappedDek } from './crypto';\n\n/** Opaque optimistic-concurrency token — unique per write, compared by equality only. */\nexport type Token = string;\n\n/** Sentinel for `putConditional` meaning \"the row must not exist yet\" (create). */\n// `Symbol.for` (the global registry), NOT `Symbol()`: the package ships multiple bundles (the core entry and\n// the `./dynamodb` / `./s3` subpaths), and tsup inlines `core/ports` into each. A plain `Symbol('no-row')`\n// would be a DISTINCT instance per bundle, so the engine's `NO_ROW` wouldn't `===` the one the DynamoDb warm\n// driver compares against — every create would misroute to the token-fenced path and fail. A registry symbol\n// is identity-stable across bundles/realms. (Invisible in tests, which share one source module graph.)\nexport const NO_ROW: unique symbol = Symbol.for('cloud-roaring.no-row');\nexport type NoRow = typeof NO_ROW;\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 — a single generation of a segment. */\nexport interface GenKey extends SegmentRef {\n readonly generation: number;\n}\n\nexport interface WarmRow {\n readonly token: Token;\n /** Read-only: owned by the driver. Callers must NOT mutate the buffer (re-encode to write). */\n readonly bytes: Uint8Array;\n}\n\n/**\n * Read consistency for a Warm fetch (gap #9). `consistent: true` (the default — omitted ⇒ true) is a\n * strong, read-your-writes read, as the OCC read-modify-write path requires. `consistent: false` requests an\n * **eventually-consistent** read — on DynamoDB that is ~½ the RCU cost; a driver that is always strongly\n * consistent (in-memory, LocalFs) ignores it. The engine uses it only on read paths (`has`/`count`/`iterate`/\n * `intersect`) when the store opts into `warmReadConsistency: 'eventual'`, trading read-after-write for cost.\n */\nexport interface WarmReadOptions {\n readonly consistent?: boolean;\n}\n\n/** Per-chunk Warm store under optimistic concurrency. Returned byte buffers are read-only. */\nexport interface IWarmDriver {\n get(ref: ChunkRef, opts?: WarmReadOptions): Promise<WarmRow | null>;\n /** OCC write. `expected` = `NO_ROW` to create, else the token previously read. Throws `WriteConflictError` on mismatch. */\n putConditional(\n ref: ChunkRef,\n bytes: Uint8Array,\n expected: Token | NoRow,\n ): Promise<{ token: Token }>;\n /**\n * Fenced delete — only if the stored token still equals `expected`; throws `WriteConflictError`\n * otherwise. **Used by compaction (Phase 4); the Phase-1 engine never calls it** — drivers still\n * implement it to satisfy the conformance suite.\n */\n deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;\n /** All dirty chunks of a segment, ascending `chunkKey`. Yielded `bytes` are read-only. */\n listChunks(\n ref: SegmentRef,\n opts?: WarmReadOptions,\n ): AsyncIterable<{ chunkKey: number } & WarmRow>;\n}\n\n/**\n * A segment's grounded on-disk footprint (Phase 5b) — the current generation's Cold object bytes, read\n * cheaply from the `.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 Cold tier (implemented by the `.crbm` reader from Phase 2). */\nexport interface ColdChunkSource {\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 (Phase 5b): the current generation's grounded size, cheaply (from the already-parsed\n * `.crbm` index — no payload reads), or `null` if the segment has no Cold generation. Powers the\n * grounded `costReport()`.\n */\n sizeOf?(ref: SegmentRef): Promise<SegmentSize | null>;\n /**\n * Optional (Phase 5c): the current generation's **per-chunk cardinality** (`chunkKey → count`), read\n * from the already-parsed `.crbm` index with **no payload reads**, or `null` if the segment has no Cold\n * generation. Powers the cheap `count()` — a warm-delta-free chunk is counted straight from the index\n * instead of fetching + deserializing it. A source with no index (e.g. the in-memory source) omits this,\n * and `count()` falls back to fetching + merging every chunk.\n */\n cardinalities?(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;\n /**\n * Optional (Phase B, gap #4): the segment's **current generation number** as this source resolves it right now\n * (registry `currentGen`, or the highest cold generation), or `null` if the segment has no Cold generation. The\n * engine keys its HOT chunk cache by this so a generation bump (a compaction commit) is observed — a new\n * generation misses the cache instead of serving a stale decoded chunk, and an erased id can't resurrect from a\n * cached pre-compaction chunk. Cheap: served from the source's own (short-TTL-refreshed) snapshot, **not** a\n * fresh backend read per call. A source that pins one immutable generation for its whole lifetime and never\n * refreshes may omit this — the engine then keys the cache without a generation, exactly as before.\n */\n currentGeneration?(ref: SegmentRef): Promise<number | null>;\n}\n\n/** Capabilities a Cold driver advertises; validated at wiring time, fail-fast. */\nexport interface ColdCaps {\n /** REQUIRED — the format relies on byte-range reads. */\n readonly rangeRead: true;\n /** Largest single object the backend accepts (informs single-object-vs-shard, B7 — 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 */\nexport interface IColdDriver {\n capabilities(): ColdCaps;\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 if the key already exists (write-once).\n */\n putImmutable(\n key: GenKey,\n write: (sink: BlobSink) => Promise<void>,\n ): Promise<{ size: number; sha256: string }>;\n /** Range read; the caller bounds-checks. Out-of-range is rejected, never a short/adjacent read. */\n getRange(key: GenKey, offset: number, length: number): Promise<Uint8Array>;\n /** Speculative tail read: the last `min(maxBytes, size)` bytes + the total object size. */\n getTail(key: GenKey, maxBytes: number): Promise<{ bytes: Uint8Array; size: number }>;\n delete(key: GenKey): Promise<void>;\n /** Enumerate the generations present for a segment (orphan sweep / latest-gen resolution). */\n list(ref: SegmentRef): AsyncIterable<GenKey>;\n}\n\n/**\n * Lifecycle status of a segment. `active` is the steady state;\n * `compacting`/`erasing` are transient flags a daemon sets while it works (Phase 4d/4e); `destroyed` is the\n * post-crypto-shred tombstone (the row is kept for audit but the segment is logically gone). The 4c registry\n * stores and round-trips the field; the *transitions* are driven by their owning features.\n */\nexport type RegistryStatus = 'active' | 'compacting' | 'erasing' | 'destroyed';\n\n/** Free-form, JSON-serializable governance metadata (retention/residency policy); shape lands in Phase 6. */\nexport type GovernanceMeta = Record<string, unknown>;\n\n/**\n * One registry row — the authoritative per-segment record. Exactly one per segment.\n */\nexport interface RegistryRecord extends SegmentRef {\n /** **The** authoritative LATEST pointer: which immutable Cold generation is current. */\n readonly currentGen: number;\n /**\n * Per-segment data-key (DEK) wrappings for encryption-at-rest (Phase 4e): the DEK envelope-wrapped under one\n * or more KEKs (active + optional recovery). Reading unwraps with any held KEK; **crypto-shred deletes this\n * whole list**, making the segment's at-rest bytes permanently unrecoverable. Absent ⇒ the segment is\n * cleartext. See [DECISIONS #19].\n */\n readonly wrappedDeks?: readonly WrappedDek[];\n /**\n * Optional **external** key reference (reserved) — 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 /**\n * Dirty-row hint written by `findCompactable`. Discovery still recomputes the live dirty count by scanning\n * Warm each cycle (the count that decides candidacy), but it now **reads** this hint to gate a change-guarded\n * CAS — the hint is only rewritten when the count actually moved, so an all-idle fleet issues no per-segment\n * registry writes. It does not yet drive candidacy — turning the Warm scan itself into O(dirty) is a\n * deferred cheap-enumeration fix.\n */\n readonly dirtyChunkCount: number;\n readonly status: RegistryStatus;\n /**\n * Daemon health (Phase D, gap #2): epoch-ms of the last **successfully committed** compaction, and the\n * count of **consecutive** compaction failures (reset to 0 by a successful commit). `lastCompactedAt`\n * powers a dead-man's-switch / staleness alarm; `consecutiveFailures` drives **poison-segment quarantine** —\n * discovery skips a segment past a failure threshold so one corrupt Warm row can't freeze the compaction of a\n * segment **that has a committed generation** forever (with no alarm) while its Warm backlog grows unbounded.\n * (A segment whose *very first* compaction keeps failing has no registry row to count against yet —\n * bootstrap faults are surfaced as `error` results but not yet quarantined; gap #2.) Both\n * **optional** (absent on rows written before Phase D, and on a segment never compacted) — read them as\n * `lastCompactedAt ?? undefined`, `consecutiveFailures ?? 0`.\n */\n readonly lastCompactedAt?: number;\n readonly consecutiveFailures?: number;\n /**\n * Compaction lease (Phase 4d): the opaque id of the worker currently compacting this segment, and the\n * epoch-ms at which its lease expires. `status === 'compacting'` with `leaseExpiresAt` in the future means\n * a live daemon owns it; once `leaseExpiresAt` is past, another worker may **steal** the lease (the prior\n * holder crashed). Both absent in the steady (`active`) state. The lease is an efficiency guard only — the\n * 2-phase commit is correct without it (OCC-fenced swap + write-once generations + version-fenced purge).\n */\n readonly leaseOwner?: string;\n readonly leaseExpiresAt?: number;\n /** Governance policy (Phase 6); stored + round-tripped now, semantics later. */\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 — compare-by-equality, never reused (ABA-safe), exactly like {@link WarmRow.token}. */\n readonly token: Token;\n}\n\n/** The caller-settable fields at {@link IRegistryDriver.create} (audit + token are driver-managed). */\nexport interface NewRegistryRecord {\n readonly currentGen: number;\n readonly wrappedDeks?: readonly WrappedDek[];\n readonly keyId?: string;\n /** Defaults to 0. */\n readonly dirtyChunkCount?: number;\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'\n | 'wrappedDeks'\n | 'keyId'\n | 'dirtyChunkCount'\n | 'status'\n | 'leaseOwner'\n | 'leaseExpiresAt'\n | 'lastCompactedAt'\n | 'consecutiveFailures'\n | 'retention'\n | 'residency'\n >\n>;\n\n/** Capabilities a registry driver advertises; validated fail-fast at wiring time. */\nexport interface RegCaps {\n /** REQUIRED — `currentGen` feeds read correctness + compaction CAS, so reads must be strongly consistent. */\n readonly strongRead: true;\n}\n\n/**\n * Per-segment registry — the authoritative source of `currentGen`, the discovery index, and (Phase 4e) the\n * wrapped-DEK holder. One row per segment under OCC: a\n * never-reused, equality-compared {@link Token} (ABA-safe across delete→recreate, like the Warm tier).\n */\nexport interface IRegistryDriver {\n capabilities(): RegCaps;\n /** The segment's record, or `null` if it doesn't exist (or was deleted). */\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 /** Discovery: every live record, optionally scoped to one namespace. Order is unspecified. */\n list(namespace?: string): AsyncIterable<RegistryRecord>;\n /** Remove the row (tombstoned for ABA-safety — a later `create` still gets a fresh, greater token). */\n delete(ref: SegmentRef): Promise<void>;\n}\n","/**\n * Shared, SDK-free helpers for classifying AWS-style errors (used by the S3 + DynamoDB drivers, Phase 4b).\n *\n * These only read structural shapes an AWS SDK v3 error carries — `name`, `$metadata.httpStatusCode`, a\n * lower-level `code`/`errno`, and the SDK's own `$retryable` marker — so the (subtle, easy-to-get-wrong)\n * transient-vs-fatal decision is unit-testable without a live backend or even the SDK installed. They live\n * under `drivers/_shared` (part of the SDK-free core bundle): a driver may import them; they import no SDK.\n */\n\nexport function httpStatus(err: unknown): number | undefined {\n return (err as { $metadata?: { httpStatusCode?: number } } | null)?.$metadata?.httpStatusCode;\n}\n\nexport function errorName(err: unknown): string | undefined {\n return (err as { name?: string } | null)?.name;\n}\n\n/** A lower-level transport code (e.g. `ECONNRESET`) — the Node networking layer sets `code`. */\nexport function errorCode(err: unknown): string | undefined {\n return (err as { code?: string } | null)?.code;\n}\n\n/** The AWS SDK v3 tags retryable errors with a `$retryable` object (throttling faults carry `.throttling`). */\nexport function isSdkRetryable(err: unknown): boolean {\n return (err as { $retryable?: unknown } | null)?.$retryable != null;\n}\n\n/** Any 5xx is a server-side fault that's safe to retry (the request didn't deterministically fail). */\nexport function isServerSide(err: unknown): boolean {\n const status = httpStatus(err);\n return status !== undefined && status >= 500 && status <= 599;\n}\n\nconst NETWORK_NAMES = new Set([\n 'TimeoutError',\n 'RequestTimeout',\n 'RequestTimeoutException',\n 'NetworkingError',\n 'AbortError',\n]);\nconst NETWORK_CODES = new Set([\n 'ETIMEDOUT',\n 'ECONNRESET',\n 'ECONNREFUSED',\n 'EPIPE',\n 'ENOTFOUND',\n 'EAI_AGAIN',\n 'ECONNABORTED',\n]);\n\n// Message-text fallback for when the structural signals (name/code/$metadata/$retryable) are absent. Kept\n// SPECIFIC on purpose: a loose `/timed? ?out/` matches deterministic messages like \"value timed out of\n// range\" and would wrongly retry them, so we only match timeout/network phrases anchored to a transport word\n// (connection/request/socket/read/write) plus the unambiguous standalone phrases.\nconst NETWORK_MESSAGE =\n /socket hang up|network (error|failure)|(connection|request|socket|operation|read|write)\\s+tim(e|ed)\\s?out|connection (reset|refused|aborted|closed)/i;\n\n/** A dropped/timed-out connection — transient by nature; a retry on a fresh connection usually succeeds. */\nexport function isNetworkOrTimeout(err: unknown): boolean {\n if (NETWORK_NAMES.has(errorName(err) ?? '')) return true;\n if (NETWORK_CODES.has(errorCode(err) ?? '')) return true;\n return NETWORK_MESSAGE.test((err as { message?: string } | null)?.message ?? '');\n}\n","/**\n * Pure helpers for classifying DynamoDB SDK errors (Phase 4a conflict mapping; transient class 4b).\n *\n * SDK-free + side-effect-free (read structural shapes only), so the transient-vs-fatal decision is\n * unit-testable without DynamoDB-Local or the SDK. Shared AWS shapes come from `_shared/aws-errors`.\n */\nimport { errorName, isNetworkOrTimeout, isSdkRetryable, isServerSide } from '../_shared/aws-errors';\n\n/** A conditional `UpdateItem`/`DeleteItem` failed its condition — an OCC conflict (or write-once collision). */\nexport function isConditionalCheckFailed(err: unknown): boolean {\n return errorName(err) === 'ConditionalCheckFailedException';\n}\n\n/** Names DynamoDB uses for capacity/throttling + server-side faults that are safe to retry. */\nconst TRANSIENT_NAMES = new Set([\n 'ThrottlingException',\n 'ProvisionedThroughputExceededException',\n 'RequestLimitExceeded',\n 'InternalServerError',\n 'ServiceUnavailable',\n 'LimitExceededException',\n]);\n\n/**\n * A transient DynamoDB fault that is safe to retry: throttling / capacity-exceeded, any 5xx, a dropped or\n * timed-out connection, or anything the SDK itself marks retryable. A failed condition is **never** transient\n * (it's a deterministic OCC outcome — the engine's read-modify-write loop owns that retry, not the wrapper).\n */\nexport function isTransient(err: unknown): boolean {\n if (isConditionalCheckFailed(err)) return false;\n return (\n TRANSIENT_NAMES.has(errorName(err) ?? '') ||\n isServerSide(err) ||\n isNetworkOrTimeout(err) ||\n isSdkRetryable(err)\n );\n}\n","/**\n * Boundary validation for segment / namespace names (finding S2).\n * Names become object keys, partition keys, and file paths — so they're validated before they\n * ever reach storage: strict charset, bounded length, no path traversal.\n */\nimport { ValidationError } from './errors';\nimport type { ChunkRef, SegmentRef } from './ports';\n\nconst CHUNK_KEY_MAX = 0xffff;\n\n// The locked name grammar: 1 leading alphanumeric + up to 255 more (max 256 chars).\nconst NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/;\n\nfunction validatePart(value: string, field: string): void {\n if (typeof value !== 'string' || !NAME.test(value) || value.includes('..')) {\n throw new ValidationError(\n `${field} must match ${String(NAME)} and contain no \"..\": got ${JSON.stringify(value)}`,\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/** Validate a chunk ref: the segment/namespace grammar plus `chunkKey` ∈ `[0, 65535]` (a u16). */\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 * Shared key-grammar fragments used by every driver's logical-ref → physical-key mapping (Phase 4c).\n *\n * Extracted once the registry drivers became the 4th/5th consumer of the same `_default` namespace sentinel\n * (it lived copy-pasted in `s3/keys`, `dynamodb/keys`, `localfs/paths`). Pure string logic, no SDK, no I/O —\n * 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. The name grammar forbids a leading underscore\n * so `_default` can never collide with a real\n * namespace — `segment(\"s\")` and `segment(\"s\", { namespace: \"_default\" })` would be a grammar error, not an\n * aliasing hazard.\n */\nexport const DEFAULT_NAMESPACE = '_default';\n\n/** Map an optional namespace to its physical part: the namespace itself, or {@link DEFAULT_NAMESPACE}. */\nexport function namespacePart(namespace: string | undefined): string {\n return namespace ?? DEFAULT_NAMESPACE;\n}\n","/**\n * Logical-ref → DynamoDB key mapping for {@link DynamoDbWarmDriver} (Phase 4a).\n *\n * **Single-table design** (DECISIONS #15): every item is partitioned by segment (`PK`), and the sort key\n * (`SK`) prefix distinguishes entity types — `chunk#<key>` for warm chunk rows, `reg#…` for registry rows\n * (Phase 4b). Pure string logic, no SDK dependency, so it's unit-testable without DynamoDB-Local. Names are\n * re-validated at the boundary (defense in depth — S2); the absent namespace maps to `_default`, which can't\n * collide with a real namespace (the grammar forbids a leading underscore).\n */\nimport { ValidationError } from '@/core/errors';\nimport { validateChunkRef, validateSegmentRef } from '@/core/validate';\nimport type { ChunkRef, SegmentRef } from '@/core/ports';\nimport { namespacePart } from '../_shared/keys';\n\nconst CHUNK_SK_PREFIX = 'chunk#';\n/** chunkKey ∈ [0, 65535] → zero-pad to 5 digits so SK lexicographic order == ascending chunkKey. */\nconst CHUNK_KEY_DIGITS = 5;\n\n/**\n * Validate a caller-supplied `keyPrefix` so prefix-isolation is *structural*, not convention: it must not\n * contain the PK delimiters (`|`, `#`) — which would let one prefix's PK alias another's — or control\n * characters. Empty/undefined means no prefix.\n */\nexport function assertValidKeyPrefix(prefix: string | undefined): void {\n if (prefix === undefined || prefix === '') return;\n for (const ch of prefix) {\n const code = ch.charCodeAt(0);\n if (ch === '|' || ch === '#' || code < 0x20) {\n throw new ValidationError(`keyPrefix must not contain \"|\", \"#\", or control characters`);\n }\n }\n}\n\n/**\n * Partition key for all of a segment's items: `[<prefix>|]ns#<ns>|seg#<segment>`. An optional caller\n * `prefix` lets several logical stores share one physical table (and gives tests cheap isolation); it's\n * opaque (the PK is matched exactly, never parsed back), so no traversal concerns apply.\n */\nexport function partitionKey(ref: SegmentRef, prefix?: string): string {\n validateSegmentRef(ref);\n const base = `ns#${namespacePart(ref.namespace)}|seg#${ref.segment}`;\n return prefix === undefined || prefix === '' ? base : `${prefix}|${base}`;\n}\n\n/** Sort key for one chunk's warm row: `chunk#<zero-padded chunkKey>`. */\nexport function chunkSortKey(chunkKey: number): string {\n return `${CHUNK_SK_PREFIX}${String(chunkKey).padStart(CHUNK_KEY_DIGITS, '0')}`;\n}\n\n/** The `(PK, SK)` of one chunk's warm row, validating the full ref. */\nexport function chunkKeyPair(ref: ChunkRef, prefix?: string): { pk: string; sk: string } {\n validateChunkRef(ref);\n return { pk: partitionKey(ref, prefix), sk: chunkSortKey(ref.chunkKey) };\n}\n\n/** The `begins_with` prefix for querying a segment's chunk rows (excludes `reg#…` items). */\nexport function chunkSortKeyPrefix(): string {\n return CHUNK_SK_PREFIX;\n}\n\n/** Sort key of a segment's single registry row — co-located with its chunk rows (DECISIONS #15). */\nconst REGISTRY_SK = 'reg#';\nexport function registrySortKey(): string {\n return REGISTRY_SK;\n}\n\n/** The `(PK, SK)` of a segment's registry row, validating the ref. */\nexport function registryKeyPair(ref: SegmentRef, prefix?: string): { pk: string; sk: string } {\n validateSegmentRef(ref);\n return { pk: partitionKey(ref, prefix), sk: REGISTRY_SK };\n}\n\n/** Parse a chunkKey out of a `chunk#<n>` sort key, or `null` if it doesn't match (canonical 5-digit). */\nexport function parseChunkSortKey(sk: string): number | null {\n if (!sk.startsWith(CHUNK_SK_PREFIX)) return null;\n const middle = sk.slice(CHUNK_SK_PREFIX.length);\n // Exactly CHUNK_KEY_DIGITS digits — the canonical form the writer emits (so nothing else aliases it).\n if (!new RegExp(`^\\\\d{${CHUNK_KEY_DIGITS}}$`).test(middle)) return null;\n const chunkKey = Number(middle);\n return chunkKey <= 0xffff ? chunkKey : null;\n}\n","/**\n * `DynamoDbWarmDriver` — an {@link IWarmDriver} over DynamoDB (Phase 4a).\n *\n * The production warm tier: real **cross-process** optimistic concurrency via DynamoDB conditional writes\n * (unlike the LocalFs driver's in-process mutex). Each chunk is one item in a **single table** (DECISIONS\n * #15): `PK = ns#…|seg#…`, `SK = chunk#<key>`, with attributes `v` (the OCC token — a monotonic counter),\n * `b` (the delta payload), `del` (tombstone flag). Every mutation is a conditional `UpdateItem` that does\n * the compare-and-set server-side: `ADD v :one` makes the counter strictly increasing and **never reused**,\n * even across delete→recreate (ABA-safe, D3), because a delete *tombstones* (`del=true`, counter advances)\n * rather than removing the item.\n *\n * `@aws-sdk/client-dynamodb` is an **optional peer dependency** (DECISIONS #13) — only consumers of\n * `cloud-roaring/dynamodb` install it; the client is injected. Drivers may use cloud SDKs; only `core/` is\n * SDK-free (lint-enforced).\n */\nimport {\n ConditionalCheckFailedException,\n GetItemCommand,\n QueryCommand,\n UpdateItemCommand,\n type AttributeValue,\n type DynamoDBClient,\n} from '@aws-sdk/client-dynamodb';\nimport {\n IntegrityError,\n TransientError,\n WriteConflictError,\n isIntegrityError,\n} from '@/core/errors';\nimport { NO_ROW } from '@/core/ports';\nimport type {\n ChunkRef,\n IWarmDriver,\n NoRow,\n SegmentRef,\n Token,\n WarmReadOptions,\n WarmRow,\n} from '@/core/ports';\nimport { isTransient } from './dynamodb-errors';\nimport {\n assertValidKeyPrefix,\n chunkKeyPair,\n chunkSortKeyPrefix,\n parseChunkSortKey,\n partitionKey,\n} from './keys';\n\nexport interface DynamoDbWarmDriverOptions {\n /** A constructed DynamoDB client (point its `endpoint` at DynamoDB-Local for tests). */\n readonly client: DynamoDBClient;\n /** The single table holding warm rows (and, in Phase 4b, registry rows). Must already exist. */\n readonly tableName: string;\n /** Optional partition-key prefix so several logical stores can share one table. */\n readonly keyPrefix?: string;\n}\n\n// Attribute names are aliased in every expression (defense against DynamoDB reserved words). DynamoDB\n// rejects any name placeholder that is declared but unreferenced (\"ExpressionAttributeNames unused in\n// expressions\"), so each command declares ONLY the names its own expressions use — no shared catch-all map.\nconst MUTATE_NAMES = { '#v': 'v', '#b': 'b', '#del': 'del' } as const; // put/delete expressions\nconst QUERY_NAMES = { '#pk': 'PK', '#sk': 'SK', '#del': 'del' } as const; // listChunks key + filter\nconst ONE: AttributeValue = { N: '1' };\nconst TRUE: AttributeValue = { BOOL: true };\nconst FALSE: AttributeValue = { BOOL: false };\n\nexport class DynamoDbWarmDriver implements IWarmDriver {\n private readonly client: DynamoDBClient;\n private readonly table: string;\n private readonly keyPrefix: string | undefined;\n\n constructor(options: DynamoDbWarmDriverOptions) {\n assertValidKeyPrefix(options.keyPrefix); // fail fast: a prefix with PK delimiters could alias stores\n this.client = options.client;\n this.table = options.tableName;\n this.keyPrefix = options.keyPrefix;\n }\n\n async get(ref: ChunkRef, opts?: WarmReadOptions): Promise<WarmRow | null> {\n const { pk, sk } = chunkKeyPair(ref, this.keyPrefix);\n let res;\n try {\n res = await this.client.send(\n new GetItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n // Strong by default (the OCC read-modify-write must see the latest committed value); a read path\n // may opt into an eventually-consistent read (~½ RCU) via `warmReadConsistency: 'eventual'` (gap #9).\n ConsistentRead: opts?.consistent ?? true,\n }),\n );\n } catch (err) {\n throw this.mapError(err, ref.chunkKey);\n }\n if (res.Item === undefined || res.Item.del?.BOOL === true) return null;\n return this.rowFrom(res.Item, ref.chunkKey);\n }\n\n async putConditional(\n ref: ChunkRef,\n bytes: Uint8Array,\n expected: Token | NoRow,\n ): Promise<{ token: Token }> {\n const { pk, sk } = chunkKeyPair(ref, this.keyPrefix);\n // Create-if-absent (or over a tombstone) vs token-fenced update — both server-side CAS via a condition.\n const command =\n expected === NO_ROW\n ? new UpdateItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n ConditionExpression: 'attribute_not_exists(#v) OR #del = :true',\n UpdateExpression: 'ADD #v :one SET #b = :b, #del = :false',\n ExpressionAttributeNames: MUTATE_NAMES,\n ExpressionAttributeValues: {\n ':one': ONE,\n ':b': { B: bytes },\n ':true': TRUE,\n ':false': FALSE,\n },\n ReturnValues: 'UPDATED_NEW',\n })\n : new UpdateItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n ConditionExpression: '#v = :expected AND #del = :false',\n UpdateExpression: 'ADD #v :one SET #b = :b',\n ExpressionAttributeNames: MUTATE_NAMES,\n ExpressionAttributeValues: {\n ':expected': { N: expected },\n ':one': ONE,\n ':b': { B: bytes },\n ':false': FALSE,\n },\n ReturnValues: 'UPDATED_NEW',\n });\n try {\n const res = await this.client.send(command);\n const token = res.Attributes?.v?.N;\n if (token === undefined) throw new IntegrityError('DynamoDB UpdateItem returned no token');\n return { token };\n } catch (err) {\n throw this.mapError(err, ref.chunkKey);\n }\n }\n\n async deleteConditional(ref: ChunkRef, expected: Token): Promise<void> {\n const { pk, sk } = chunkKeyPair(ref, this.keyPrefix);\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n // Tombstone (advance the counter, drop the payload) — keeps the token monotonic for ABA-safety.\n ConditionExpression: '#v = :expected AND #del = :false',\n UpdateExpression: 'ADD #v :one SET #del = :true REMOVE #b',\n ExpressionAttributeNames: MUTATE_NAMES,\n ExpressionAttributeValues: {\n ':expected': { N: expected },\n ':one': ONE,\n ':true': TRUE,\n ':false': FALSE,\n },\n }),\n );\n } catch (err) {\n throw this.mapError(err, ref.chunkKey);\n }\n }\n\n async *listChunks(\n ref: SegmentRef,\n opts?: WarmReadOptions,\n ): AsyncIterable<{ chunkKey: number } & WarmRow> {\n const pk = partitionKey(ref, this.keyPrefix);\n let startKey: Record<string, AttributeValue> | undefined;\n do {\n let res;\n try {\n res = await this.client.send(\n new QueryCommand({\n TableName: this.table,\n KeyConditionExpression: '#pk = :pk AND begins_with(#sk, :prefix)',\n FilterExpression: '#del = :false', // exclude tombstones (server-side)\n ExpressionAttributeNames: QUERY_NAMES,\n ExpressionAttributeValues: {\n ':pk': { S: pk },\n ':prefix': { S: chunkSortKeyPrefix() },\n ':false': FALSE,\n },\n ConsistentRead: opts?.consistent ?? true, // strong by default; eventual when a read path opts in (gap #9)\n ExclusiveStartKey: startKey,\n }),\n );\n } catch (err) {\n // -1: a list error isn't scoped to one chunk. The decorator re-enumerates the whole listing on a\n // transient fault, so a mid-pagination blip is survived without re-yielding earlier pages downstream.\n throw this.mapError(err, -1);\n }\n // Query returns items in ascending SK order; zero-padded chunk# ⇒ ascending chunkKey.\n for (const item of res.Items ?? []) {\n const chunkKey = parseChunkSortKey(item.SK?.S ?? '');\n if (chunkKey === null) continue; // a reg#… or foreign row slipped the filter — skip\n yield { chunkKey, ...this.rowFrom(item, chunkKey) };\n }\n startKey = res.LastEvaluatedKey;\n } while (startKey !== undefined);\n }\n\n /**\n * Build a {@link WarmRow} from a raw item. Only ever called for a **live** row (callers filter\n * tombstones first), so both `v` and `b` MUST be present — a missing attribute means a corrupt or\n * foreign item, which we reject (untrusted-bytes posture, invariant 5) rather than paper over.\n */\n private rowFrom(item: Record<string, AttributeValue>, chunkKey: number): WarmRow {\n const token = item.v?.N;\n if (token === undefined) {\n throw new IntegrityError(`warm row for chunk ${chunkKey} is missing its token`);\n }\n const bytes = item.b?.B;\n if (bytes === undefined) {\n throw new IntegrityError(`live warm row for chunk ${chunkKey} is missing its payload`);\n }\n return { token, bytes };\n }\n\n /**\n * Translate a raw SDK/driver error to our typed vocabulary: a failed condition is an OCC conflict; a\n * throttle/5xx/dropped-connection is a (retryable) {@link TransientError}; everything else propagates\n * unchanged. Applied at every `client.send` site so callers (and the retry decorator) only ever see typed\n * errors. Order matters: a conflict is **never** transient (it's deterministic — the engine's OCC loop\n * owns that retry), so it's classified first.\n */\n private mapError(err: unknown, chunkKey: number): unknown {\n if (isIntegrityError(err)) return err;\n // `instanceof` is the primary check; the name fallback covers an SDK that re-wraps the error so the\n // class identity is lost — a conflict must never silently leak as a generic error.\n if (\n err instanceof ConditionalCheckFailedException ||\n (err as { name?: string } | null)?.name === 'ConditionalCheckFailedException'\n ) {\n return new WriteConflictError(`OCC conflict on chunk ${chunkKey}`);\n }\n if (isTransient(err)) {\n return new TransientError(\n `transient DynamoDB fault on chunk ${chunkKey}: ${(err as { name?: string } | null)?.name ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n","/**\n * Shared, SDK-free helpers for assembling + validating {@link RegistryRecord}s (Phase 4c).\n *\n * The registry's record-construction, patch-application, and field-validation are identical across every\n * backend (memory / LocalFs / DynamoDB) — 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 — used to validate both caller input and stored bytes. */\nconst STATUSES: readonly string[] = ['active', 'compacting', 'erasing', 'destroyed'];\n/** Cap on a serialized governance blob (retention/residency) — 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) — keeps the row small + rejects abuse. */\nconst MAX_WRAPPED_DEKS = 8;\nconst MAX_WRAPPED_DEK_BYTES = 4 * 1024;\n\nfunction validateGeneration(gen: number): void {\n if (!Number.isInteger(gen) || gen < 0) {\n throw new ValidationError(`currentGen must be a non-negative integer; got ${gen}`);\n }\n}\n\nfunction validateCount(count: number, field = 'dirtyChunkCount'): void {\n if (!Number.isInteger(count) || count < 0) {\n throw new ValidationError(`${field} must be a non-negative integer; got ${count}`);\n }\n}\n\n/** Validate an epoch-ms timestamp (non-negative, finite). */\nfunction validateTimestamp(ms: number, field: string): void {\n if (!Number.isFinite(ms) || ms < 0) {\n throw new ValidationError(`${field} must be a non-negative finite number; got ${ms}`);\n }\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 — a brick).\n */\nfunction validateGovernance(meta: unknown, field: string): void {\n if (meta === undefined) return;\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.dirtyChunkCount !== undefined) validateCount(rec.dirtyChunkCount);\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 if (patch.currentGen !== undefined) validateGeneration(patch.currentGen);\n if (patch.dirtyChunkCount !== undefined) validateCount(patch.dirtyChunkCount);\n if (patch.consecutiveFailures !== undefined)\n validateCount(patch.consecutiveFailures, 'consecutiveFailures');\n if (patch.lastCompactedAt !== undefined)\n validateTimestamp(patch.lastCompactedAt, 'lastCompactedAt');\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→recreate — ABA-safety. Shared by the persistent drivers (LocalFs file, S3 object).\n *\n * `schemaVersion` is a wire-only concern — it stamps the persisted bytes, not the in-memory domain object —\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 — a pre-1.0 format-freeze prerequisite (Phase G, DECISIONS #41). Every persistent\n * registry driver — the LocalFs/S3 `{ deleted, record }` envelope and the DynamoDB body — 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 → `UnsupportedError`\n * (fail-closed); an **absent** stamp → legacy pre-freeze row, tolerated as v1.\n */\nexport const REGISTRY_SCHEMA_VERSION = 1;\n\n/**\n * Validate a persisted registry row's `schemaVersion` (untrusted bytes, invariant 5): absent → legacy v1\n * (tolerated); a malformed value → `IntegrityError`; a version newer than this build → `UnsupportedError`\n * (fail-closed rather than misread a format we don't understand).\n */\nexport function assertRegistrySchemaVersion(raw: unknown, ctx: string): void {\n if (raw === undefined) return; // pre-stamp rows are v1\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 — 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 —\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 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 dirtyChunkCount: rec.dirtyChunkCount ?? 0,\n status: rec.status ?? 'active',\n consecutiveFailures: 0, // daemon health (Phase D); lastCompactedAt stays absent until first compaction\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 — 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 typeof r.currentGen !== 'number' ||\n typeof r.dirtyChunkCount !== '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 (!Number.isInteger(r.currentGen) || r.currentGen < 0) {\n throw new IntegrityError(`registry record has an invalid currentGen (${r.currentGen}): ${ctx}`);\n }\n if (!Number.isInteger(r.dirtyChunkCount) || r.dirtyChunkCount < 0) {\n throw new IntegrityError(`registry record has an invalid dirtyChunkCount: ${ctx}`);\n }\n if (!STATUSES.includes(r.status)) {\n throw new IntegrityError(`registry record has an unknown status (${r.status}): ${ctx}`);\n }\n if (r.leaseOwner !== undefined && typeof r.leaseOwner !== 'string') {\n throw new IntegrityError(`registry record has an invalid leaseOwner: ${ctx}`);\n }\n if (r.leaseExpiresAt !== undefined && typeof r.leaseExpiresAt !== 'number') {\n throw new IntegrityError(`registry record has an invalid leaseExpiresAt: ${ctx}`);\n }\n if (\n r.consecutiveFailures !== undefined &&\n (!Number.isInteger(r.consecutiveFailures) || (r.consecutiveFailures as number) < 0)\n ) {\n throw new IntegrityError(`registry record has an invalid consecutiveFailures: ${ctx}`);\n }\n if (\n r.lastCompactedAt !== undefined &&\n (typeof r.lastCompactedAt !== 'number' ||\n !Number.isFinite(r.lastCompactedAt) ||\n (r.lastCompactedAt as number) < 0)\n ) {\n throw new IntegrityError(`registry record has an invalid lastCompactedAt: ${ctx}`);\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}\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: patch.currentGen ?? prev.currentGen,\n wrappedDeks: 'wrappedDeks' in patch ? patch.wrappedDeks : prev.wrappedDeks,\n keyId: 'keyId' in patch ? patch.keyId : prev.keyId,\n dirtyChunkCount: patch.dirtyChunkCount ?? prev.dirtyChunkCount,\n status: patch.status ?? prev.status,\n leaseOwner: 'leaseOwner' in patch ? patch.leaseOwner : prev.leaseOwner,\n leaseExpiresAt: 'leaseExpiresAt' in patch ? patch.leaseExpiresAt : prev.leaseExpiresAt,\n lastCompactedAt: 'lastCompactedAt' in patch ? patch.lastCompactedAt : prev.lastCompactedAt,\n consecutiveFailures: patch.consecutiveFailures ?? prev.consecutiveFailures ?? 0,\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","/**\n * `DynamoDbRegistryDriver` — an {@link IRegistryDriver} over DynamoDB (Phase 4c).\n *\n * One item per segment, **co-located with that segment's warm rows** in the single table (DECISIONS #15):\n * `PK = ns#…|seg#…`, `SK = reg#`. OCC mirrors the warm tier — a conditional `UpdateItem` with `ADD v :one`\n * gives a monotonic, never-reused token; a delete **tombstones** (`del=true`, counter advances) so a\n * recreate's token is always greater (ABA-safe). The record body is stored as a JSON string (`r`); the OCC\n * token is the counter `v` (so the body never has to encode its own token).\n *\n * `compareAndSwap` is a read-merge-conditional-write: it fetches the current body to apply the caller's\n * `patch`, then writes under `#v = :expected` — so a concurrent change between the read and the write fails\n * the condition (no lost update), exactly like the engine's chunk OCC.\n *\n * `list` is a `Scan` with a `reg#` filter (discovery is infrequent — the compaction daemon, Phase 4d). On a\n * large shared table that reads every partition; a namespace-keyed GSI is the scale-up, deferred until a\n * deployment needs it (YAGNI). `@aws-sdk/client-dynamodb` is an optional peer dependency (DECISIONS #13).\n */\nimport {\n ConditionalCheckFailedException,\n GetItemCommand,\n ScanCommand,\n UpdateItemCommand,\n type AttributeValue,\n type DynamoDBClient,\n} from '@aws-sdk/client-dynamodb';\nimport {\n IntegrityError,\n TransientError,\n WriteConflictError,\n isIntegrityError,\n} from '@/core/errors';\nimport type {\n IRegistryDriver,\n NewRegistryRecord,\n RegCaps,\n RegistryPatch,\n RegistryRecord,\n SegmentRef,\n Token,\n} from '@/core/ports';\nimport {\n applyRegistryPatch,\n assertRegistrySchemaVersion,\n assertStoredRecordShape,\n recordFromNew,\n REGISTRY_SCHEMA_VERSION,\n validateNewRegistryRecord,\n validateRegistryPatch,\n} from '../_shared/registry';\nimport { isTransient } from './dynamodb-errors';\nimport { assertValidKeyPrefix, partitionKey, registryKeyPair, registrySortKey } from './keys';\n\n// Only the names each command's expressions reference (DynamoDB rejects unused declared placeholders).\nconst MUTATE_NAMES = { '#v': 'v', '#r': 'r', '#del': 'del' } as const;\nconst SCAN_NAMES = { '#sk': 'SK', '#del': 'del', '#pk': 'PK' } as const;\nconst ONE: AttributeValue = { N: '1' };\nconst TRUE: AttributeValue = { BOOL: true };\nconst FALSE: AttributeValue = { BOOL: false };\n\nexport interface DynamoDbRegistryDriverOptions {\n readonly client: DynamoDBClient;\n /** The single table holding warm + registry rows. Must already exist. */\n readonly tableName: string;\n /** Optional partition-key prefix so several logical stores can share one table. */\n readonly keyPrefix?: string;\n /** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\nexport class DynamoDbRegistryDriver implements IRegistryDriver {\n private readonly client: DynamoDBClient;\n private readonly table: string;\n private readonly keyPrefix: string | undefined;\n private readonly now: () => number;\n\n constructor(options: DynamoDbRegistryDriverOptions) {\n assertValidKeyPrefix(options.keyPrefix);\n this.client = options.client;\n this.table = options.tableName;\n this.keyPrefix = options.keyPrefix;\n this.now = options.now ?? (() => Date.now());\n }\n\n capabilities(): RegCaps {\n return { strongRead: true };\n }\n\n async get(ref: SegmentRef): Promise<RegistryRecord | null> {\n const { pk, sk } = registryKeyPair(ref, this.keyPrefix);\n let res;\n try {\n res = await this.client.send(\n new GetItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n ConsistentRead: true, // currentGen feeds correctness — must be the latest committed value\n }),\n );\n } catch (err) {\n throw this.mapError(err, ref);\n }\n if (res.Item === undefined || res.Item.del?.BOOL === true) return null;\n return rowToRecord(res.Item, ref);\n }\n\n async create(ref: SegmentRef, record: NewRegistryRecord): Promise<{ token: Token }> {\n validateNewRegistryRecord(record);\n const { pk, sk } = registryKeyPair(ref, this.keyPrefix);\n const now = this.now();\n const body = serializeBody(recordFromNew(ref, record, now, '')); // token derived from `v`, not stored\n try {\n const res = await this.client.send(\n new UpdateItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n ConditionExpression: 'attribute_not_exists(#v) OR #del = :true',\n UpdateExpression: 'ADD #v :one SET #r = :r, #del = :false',\n ExpressionAttributeNames: MUTATE_NAMES,\n ExpressionAttributeValues: {\n ':one': ONE,\n ':r': { S: body },\n ':true': TRUE,\n ':false': FALSE,\n },\n ReturnValues: 'UPDATED_NEW',\n }),\n );\n return { token: tokenOf(res.Attributes) };\n } catch (err) {\n throw this.mapError(err, ref, 'already exists');\n }\n }\n\n async compareAndSwap(\n ref: SegmentRef,\n expected: Token,\n patch: RegistryPatch,\n ): Promise<{ token: Token }> {\n validateRegistryPatch(patch);\n const current = await this.get(ref); // fetch the body to merge; the conditional write fences concurrency\n if (current === null || current.token !== expected) {\n throw new WriteConflictError(`OCC token mismatch for registry row ${ref.segment}`);\n }\n const { pk, sk } = registryKeyPair(ref, this.keyPrefix);\n const body = serializeBody(applyRegistryPatch(current, patch, this.now(), ''));\n try {\n const res = await this.client.send(\n new UpdateItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n ConditionExpression: '#v = :expected AND #del = :false',\n UpdateExpression: 'ADD #v :one SET #r = :r',\n ExpressionAttributeNames: MUTATE_NAMES,\n ExpressionAttributeValues: {\n ':expected': { N: expected },\n ':one': ONE,\n ':r': { S: body },\n ':false': FALSE,\n },\n ReturnValues: 'UPDATED_NEW',\n }),\n );\n return { token: tokenOf(res.Attributes) };\n } catch (err) {\n throw this.mapError(err, ref);\n }\n }\n\n async *list(namespace?: string): AsyncIterable<RegistryRecord> {\n const filter = ['#sk = :reg', '#del = :false'];\n const values: Record<string, AttributeValue> = {\n ':reg': { S: registrySortKey() },\n ':false': FALSE,\n };\n // Scope by namespace (and/or this store's keyPrefix) via a PK begins_with — items still scanned, filtered.\n const pkPrefix = this.scanPkPrefix(namespace);\n if (pkPrefix !== undefined) {\n filter.push('begins_with(#pk, :pk)');\n values[':pk'] = { S: pkPrefix };\n }\n let startKey: Record<string, AttributeValue> | undefined;\n do {\n let res;\n try {\n res = await this.client.send(\n new ScanCommand({\n TableName: this.table,\n FilterExpression: filter.join(' AND '),\n ExpressionAttributeNames:\n pkPrefix !== undefined ? SCAN_NAMES : { '#sk': 'SK', '#del': 'del' },\n ExpressionAttributeValues: values,\n ExclusiveStartKey: startKey,\n }),\n );\n } catch (err) {\n throw this.mapError(err);\n }\n for (const item of res.Items ?? []) {\n yield rowToRecord(item, namespaceOf(item));\n }\n startKey = res.LastEvaluatedKey;\n } while (startKey !== undefined);\n }\n\n async delete(ref: SegmentRef): Promise<void> {\n const { pk, sk } = registryKeyPair(ref, this.keyPrefix);\n // Tombstone (advance the counter, drop the body) — idempotent, and keeps the token monotonic (ABA-safe).\n try {\n await this.client.send(\n new UpdateItemCommand({\n TableName: this.table,\n Key: { PK: { S: pk }, SK: { S: sk } },\n UpdateExpression: 'ADD #v :one SET #del = :true REMOVE #r',\n ExpressionAttributeNames: MUTATE_NAMES,\n ExpressionAttributeValues: { ':one': ONE, ':true': TRUE },\n }),\n );\n } catch (err) {\n throw this.mapError(err, ref);\n }\n }\n\n /** The PK prefix to scope a `list` scan, or `undefined` for an unscoped scan (no namespace, no keyPrefix). */\n private scanPkPrefix(namespace: string | undefined): string | undefined {\n if (namespace !== undefined) {\n // partitionKey for a sentinel segment, minus the segment, gives the `…|seg#` prefix shared by the ns.\n const full = partitionKey({ namespace, segment: 'x' }, this.keyPrefix);\n return full.slice(0, full.length - 'x'.length);\n }\n return this.keyPrefix !== undefined ? `${this.keyPrefix}|` : undefined;\n }\n\n /** Map a raw SDK error to the typed vocabulary (conflict / transient / passthrough). */\n private mapError(err: unknown, ref?: SegmentRef, conflictHint = 'OCC token mismatch'): unknown {\n if (isIntegrityError(err)) return err;\n if (\n err instanceof ConditionalCheckFailedException ||\n (err as { name?: string } | null)?.name === 'ConditionalCheckFailedException'\n ) {\n return new WriteConflictError(\n `registry ${conflictHint}${ref ? ` for segment ${ref.segment}` : ''}`,\n );\n }\n if (isTransient(err)) {\n return new TransientError(\n `transient DynamoDB fault on registry${ref ? ` ${ref.segment}` : ''}: ${(err as { name?: string } | null)?.name ?? 'unknown'}`,\n { cause: err },\n );\n }\n return err;\n }\n}\n\n/** The OCC token from an `UPDATED_NEW` response (the post-increment counter). */\nfunction tokenOf(attrs: Record<string, AttributeValue> | undefined): Token {\n const token = attrs?.v?.N;\n if (token === undefined)\n throw new IntegrityError('DynamoDB registry UpdateItem returned no token');\n return token;\n}\n\n/** Serialize the record body (everything except the token, which is the OCC counter `v`). */\nfunction serializeBody(record: RegistryRecord): string {\n const body: Omit<RegistryRecord, 'token'> = {\n namespace: record.namespace,\n segment: record.segment,\n currentGen: record.currentGen,\n wrappedDeks: record.wrappedDeks,\n keyId: record.keyId,\n dirtyChunkCount: record.dirtyChunkCount,\n status: record.status,\n leaseOwner: record.leaseOwner,\n leaseExpiresAt: record.leaseExpiresAt,\n lastCompactedAt: record.lastCompactedAt,\n consecutiveFailures: record.consecutiveFailures,\n retention: record.retention,\n residency: record.residency,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n };\n // `schemaVersion` stamps the persisted body (format freeze) alongside the LocalFs/S3 envelope. JSON.stringify\n // drops `undefined` fields (keyId/retention/residency).\n return JSON.stringify({ schemaVersion: REGISTRY_SCHEMA_VERSION, ...body });\n}\n\n/** Reconstruct the namespace from a scanned item's body (so `list` doesn't have to parse the PK). */\nfunction namespaceOf(item: Record<string, AttributeValue>): SegmentRef {\n const body = parseBody(item.r?.S, 'scan');\n return { namespace: body.namespace, segment: body.segment };\n}\n\n/** Build a {@link RegistryRecord} from a live item: parse the JSON body, attach the token from `v`. */\nfunction rowToRecord(item: Record<string, AttributeValue>, ref: SegmentRef): RegistryRecord {\n const token = item.v?.N;\n if (token === undefined) {\n throw new IntegrityError(`registry row for segment ${ref.segment} is missing its token`);\n }\n const body = parseBody(item.r?.S, ref.segment);\n return { ...body, token };\n}\n\n/** Parse + structurally validate the stored JSON body (untrusted bytes, invariant 5). */\nfunction parseBody(json: string | undefined, ctx: string): Omit<RegistryRecord, 'token'> {\n if (json === undefined) throw new IntegrityError(`registry row ${ctx} is missing its body`);\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new IntegrityError(`registry row ${ctx} body is not valid JSON`);\n }\n if (parsed === null || typeof parsed !== 'object') {\n throw new IntegrityError(`registry row ${ctx} body is malformed`);\n }\n // `schemaVersion` is a wire-only stamp (format freeze): validate it, then strip it so the in-memory record stays\n // pure (absent → legacy v1; a newer version → UnsupportedError).\n const { schemaVersion, ...body } = parsed as Record<string, unknown>;\n assertRegistrySchemaVersion(schemaVersion, ctx);\n assertStoredRecordShape(body, ctx);\n return body as Omit<RegistryRecord, 'token'>;\n}\n"]}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { a as IWarmDriver, b as ChunkRef, W as WarmReadOptions, c as WarmRow, T as Token, N as NoRow, S as SegmentRef, d as IRegistryDriver, R as RegCaps, e as RegistryRecord, f as NewRegistryRecord, g as RegistryPatch } from '../ports-D3BrJ6ax.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `DynamoDbWarmDriver` — an {@link IWarmDriver} over DynamoDB (Phase 4a).
|
|
6
|
+
*
|
|
7
|
+
* The production warm tier: real **cross-process** optimistic concurrency via DynamoDB conditional writes
|
|
8
|
+
* (unlike the LocalFs driver's in-process mutex). Each chunk is one item in a **single table** (DECISIONS
|
|
9
|
+
* #15): `PK = ns#…|seg#…`, `SK = chunk#<key>`, with attributes `v` (the OCC token — a monotonic counter),
|
|
10
|
+
* `b` (the delta payload), `del` (tombstone flag). Every mutation is a conditional `UpdateItem` that does
|
|
11
|
+
* the compare-and-set server-side: `ADD v :one` makes the counter strictly increasing and **never reused**,
|
|
12
|
+
* even across delete→recreate (ABA-safe, D3), because a delete *tombstones* (`del=true`, counter advances)
|
|
13
|
+
* rather than removing the item.
|
|
14
|
+
*
|
|
15
|
+
* `@aws-sdk/client-dynamodb` is an **optional peer dependency** (DECISIONS #13) — only consumers of
|
|
16
|
+
* `cloud-roaring/dynamodb` install it; the client is injected. Drivers may use cloud SDKs; only `core/` is
|
|
17
|
+
* SDK-free (lint-enforced).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
interface DynamoDbWarmDriverOptions {
|
|
21
|
+
/** A constructed DynamoDB client (point its `endpoint` at DynamoDB-Local for tests). */
|
|
22
|
+
readonly client: DynamoDBClient;
|
|
23
|
+
/** The single table holding warm rows (and, in Phase 4b, registry rows). Must already exist. */
|
|
24
|
+
readonly tableName: string;
|
|
25
|
+
/** Optional partition-key prefix so several logical stores can share one table. */
|
|
26
|
+
readonly keyPrefix?: string;
|
|
27
|
+
}
|
|
28
|
+
declare class DynamoDbWarmDriver implements IWarmDriver {
|
|
29
|
+
private readonly client;
|
|
30
|
+
private readonly table;
|
|
31
|
+
private readonly keyPrefix;
|
|
32
|
+
constructor(options: DynamoDbWarmDriverOptions);
|
|
33
|
+
get(ref: ChunkRef, opts?: WarmReadOptions): Promise<WarmRow | null>;
|
|
34
|
+
putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
|
|
35
|
+
token: Token;
|
|
36
|
+
}>;
|
|
37
|
+
deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
|
|
38
|
+
listChunks(ref: SegmentRef, opts?: WarmReadOptions): AsyncIterable<{
|
|
39
|
+
chunkKey: number;
|
|
40
|
+
} & WarmRow>;
|
|
41
|
+
/**
|
|
42
|
+
* Build a {@link WarmRow} from a raw item. Only ever called for a **live** row (callers filter
|
|
43
|
+
* tombstones first), so both `v` and `b` MUST be present — a missing attribute means a corrupt or
|
|
44
|
+
* foreign item, which we reject (untrusted-bytes posture, invariant 5) rather than paper over.
|
|
45
|
+
*/
|
|
46
|
+
private rowFrom;
|
|
47
|
+
/**
|
|
48
|
+
* Translate a raw SDK/driver error to our typed vocabulary: a failed condition is an OCC conflict; a
|
|
49
|
+
* throttle/5xx/dropped-connection is a (retryable) {@link TransientError}; everything else propagates
|
|
50
|
+
* unchanged. Applied at every `client.send` site so callers (and the retry decorator) only ever see typed
|
|
51
|
+
* errors. Order matters: a conflict is **never** transient (it's deterministic — the engine's OCC loop
|
|
52
|
+
* owns that retry), so it's classified first.
|
|
53
|
+
*/
|
|
54
|
+
private mapError;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `DynamoDbRegistryDriver` — an {@link IRegistryDriver} over DynamoDB (Phase 4c).
|
|
59
|
+
*
|
|
60
|
+
* One item per segment, **co-located with that segment's warm rows** in the single table (DECISIONS #15):
|
|
61
|
+
* `PK = ns#…|seg#…`, `SK = reg#`. OCC mirrors the warm tier — a conditional `UpdateItem` with `ADD v :one`
|
|
62
|
+
* gives a monotonic, never-reused token; a delete **tombstones** (`del=true`, counter advances) so a
|
|
63
|
+
* recreate's token is always greater (ABA-safe). The record body is stored as a JSON string (`r`); the OCC
|
|
64
|
+
* token is the counter `v` (so the body never has to encode its own token).
|
|
65
|
+
*
|
|
66
|
+
* `compareAndSwap` is a read-merge-conditional-write: it fetches the current body to apply the caller's
|
|
67
|
+
* `patch`, then writes under `#v = :expected` — so a concurrent change between the read and the write fails
|
|
68
|
+
* the condition (no lost update), exactly like the engine's chunk OCC.
|
|
69
|
+
*
|
|
70
|
+
* `list` is a `Scan` with a `reg#` filter (discovery is infrequent — the compaction daemon, Phase 4d). On a
|
|
71
|
+
* large shared table that reads every partition; a namespace-keyed GSI is the scale-up, deferred until a
|
|
72
|
+
* deployment needs it (YAGNI). `@aws-sdk/client-dynamodb` is an optional peer dependency (DECISIONS #13).
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
interface DynamoDbRegistryDriverOptions {
|
|
76
|
+
readonly client: DynamoDBClient;
|
|
77
|
+
/** The single table holding warm + registry rows. Must already exist. */
|
|
78
|
+
readonly tableName: string;
|
|
79
|
+
/** Optional partition-key prefix so several logical stores can share one table. */
|
|
80
|
+
readonly keyPrefix?: string;
|
|
81
|
+
/** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */
|
|
82
|
+
readonly now?: () => number;
|
|
83
|
+
}
|
|
84
|
+
declare class DynamoDbRegistryDriver implements IRegistryDriver {
|
|
85
|
+
private readonly client;
|
|
86
|
+
private readonly table;
|
|
87
|
+
private readonly keyPrefix;
|
|
88
|
+
private readonly now;
|
|
89
|
+
constructor(options: DynamoDbRegistryDriverOptions);
|
|
90
|
+
capabilities(): RegCaps;
|
|
91
|
+
get(ref: SegmentRef): Promise<RegistryRecord | null>;
|
|
92
|
+
create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
|
|
93
|
+
token: Token;
|
|
94
|
+
}>;
|
|
95
|
+
compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
|
|
96
|
+
token: Token;
|
|
97
|
+
}>;
|
|
98
|
+
list(namespace?: string): AsyncIterable<RegistryRecord>;
|
|
99
|
+
delete(ref: SegmentRef): Promise<void>;
|
|
100
|
+
/** The PK prefix to scope a `list` scan, or `undefined` for an unscoped scan (no namespace, no keyPrefix). */
|
|
101
|
+
private scanPkPrefix;
|
|
102
|
+
/** Map a raw SDK error to the typed vocabulary (conflict / transient / passthrough). */
|
|
103
|
+
private mapError;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { DynamoDbRegistryDriver, type DynamoDbRegistryDriverOptions, DynamoDbWarmDriver, type DynamoDbWarmDriverOptions };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
2
|
+
import { a as IWarmDriver, b as ChunkRef, W as WarmReadOptions, c as WarmRow, T as Token, N as NoRow, S as SegmentRef, d as IRegistryDriver, R as RegCaps, e as RegistryRecord, f as NewRegistryRecord, g as RegistryPatch } from '../ports-D3BrJ6ax.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `DynamoDbWarmDriver` — an {@link IWarmDriver} over DynamoDB (Phase 4a).
|
|
6
|
+
*
|
|
7
|
+
* The production warm tier: real **cross-process** optimistic concurrency via DynamoDB conditional writes
|
|
8
|
+
* (unlike the LocalFs driver's in-process mutex). Each chunk is one item in a **single table** (DECISIONS
|
|
9
|
+
* #15): `PK = ns#…|seg#…`, `SK = chunk#<key>`, with attributes `v` (the OCC token — a monotonic counter),
|
|
10
|
+
* `b` (the delta payload), `del` (tombstone flag). Every mutation is a conditional `UpdateItem` that does
|
|
11
|
+
* the compare-and-set server-side: `ADD v :one` makes the counter strictly increasing and **never reused**,
|
|
12
|
+
* even across delete→recreate (ABA-safe, D3), because a delete *tombstones* (`del=true`, counter advances)
|
|
13
|
+
* rather than removing the item.
|
|
14
|
+
*
|
|
15
|
+
* `@aws-sdk/client-dynamodb` is an **optional peer dependency** (DECISIONS #13) — only consumers of
|
|
16
|
+
* `cloud-roaring/dynamodb` install it; the client is injected. Drivers may use cloud SDKs; only `core/` is
|
|
17
|
+
* SDK-free (lint-enforced).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
interface DynamoDbWarmDriverOptions {
|
|
21
|
+
/** A constructed DynamoDB client (point its `endpoint` at DynamoDB-Local for tests). */
|
|
22
|
+
readonly client: DynamoDBClient;
|
|
23
|
+
/** The single table holding warm rows (and, in Phase 4b, registry rows). Must already exist. */
|
|
24
|
+
readonly tableName: string;
|
|
25
|
+
/** Optional partition-key prefix so several logical stores can share one table. */
|
|
26
|
+
readonly keyPrefix?: string;
|
|
27
|
+
}
|
|
28
|
+
declare class DynamoDbWarmDriver implements IWarmDriver {
|
|
29
|
+
private readonly client;
|
|
30
|
+
private readonly table;
|
|
31
|
+
private readonly keyPrefix;
|
|
32
|
+
constructor(options: DynamoDbWarmDriverOptions);
|
|
33
|
+
get(ref: ChunkRef, opts?: WarmReadOptions): Promise<WarmRow | null>;
|
|
34
|
+
putConditional(ref: ChunkRef, bytes: Uint8Array, expected: Token | NoRow): Promise<{
|
|
35
|
+
token: Token;
|
|
36
|
+
}>;
|
|
37
|
+
deleteConditional(ref: ChunkRef, expected: Token): Promise<void>;
|
|
38
|
+
listChunks(ref: SegmentRef, opts?: WarmReadOptions): AsyncIterable<{
|
|
39
|
+
chunkKey: number;
|
|
40
|
+
} & WarmRow>;
|
|
41
|
+
/**
|
|
42
|
+
* Build a {@link WarmRow} from a raw item. Only ever called for a **live** row (callers filter
|
|
43
|
+
* tombstones first), so both `v` and `b` MUST be present — a missing attribute means a corrupt or
|
|
44
|
+
* foreign item, which we reject (untrusted-bytes posture, invariant 5) rather than paper over.
|
|
45
|
+
*/
|
|
46
|
+
private rowFrom;
|
|
47
|
+
/**
|
|
48
|
+
* Translate a raw SDK/driver error to our typed vocabulary: a failed condition is an OCC conflict; a
|
|
49
|
+
* throttle/5xx/dropped-connection is a (retryable) {@link TransientError}; everything else propagates
|
|
50
|
+
* unchanged. Applied at every `client.send` site so callers (and the retry decorator) only ever see typed
|
|
51
|
+
* errors. Order matters: a conflict is **never** transient (it's deterministic — the engine's OCC loop
|
|
52
|
+
* owns that retry), so it's classified first.
|
|
53
|
+
*/
|
|
54
|
+
private mapError;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* `DynamoDbRegistryDriver` — an {@link IRegistryDriver} over DynamoDB (Phase 4c).
|
|
59
|
+
*
|
|
60
|
+
* One item per segment, **co-located with that segment's warm rows** in the single table (DECISIONS #15):
|
|
61
|
+
* `PK = ns#…|seg#…`, `SK = reg#`. OCC mirrors the warm tier — a conditional `UpdateItem` with `ADD v :one`
|
|
62
|
+
* gives a monotonic, never-reused token; a delete **tombstones** (`del=true`, counter advances) so a
|
|
63
|
+
* recreate's token is always greater (ABA-safe). The record body is stored as a JSON string (`r`); the OCC
|
|
64
|
+
* token is the counter `v` (so the body never has to encode its own token).
|
|
65
|
+
*
|
|
66
|
+
* `compareAndSwap` is a read-merge-conditional-write: it fetches the current body to apply the caller's
|
|
67
|
+
* `patch`, then writes under `#v = :expected` — so a concurrent change between the read and the write fails
|
|
68
|
+
* the condition (no lost update), exactly like the engine's chunk OCC.
|
|
69
|
+
*
|
|
70
|
+
* `list` is a `Scan` with a `reg#` filter (discovery is infrequent — the compaction daemon, Phase 4d). On a
|
|
71
|
+
* large shared table that reads every partition; a namespace-keyed GSI is the scale-up, deferred until a
|
|
72
|
+
* deployment needs it (YAGNI). `@aws-sdk/client-dynamodb` is an optional peer dependency (DECISIONS #13).
|
|
73
|
+
*/
|
|
74
|
+
|
|
75
|
+
interface DynamoDbRegistryDriverOptions {
|
|
76
|
+
readonly client: DynamoDBClient;
|
|
77
|
+
/** The single table holding warm + registry rows. Must already exist. */
|
|
78
|
+
readonly tableName: string;
|
|
79
|
+
/** Optional partition-key prefix so several logical stores can share one table. */
|
|
80
|
+
readonly keyPrefix?: string;
|
|
81
|
+
/** Injected clock for `createdAt`/`updatedAt`; defaults to `Date.now`. */
|
|
82
|
+
readonly now?: () => number;
|
|
83
|
+
}
|
|
84
|
+
declare class DynamoDbRegistryDriver implements IRegistryDriver {
|
|
85
|
+
private readonly client;
|
|
86
|
+
private readonly table;
|
|
87
|
+
private readonly keyPrefix;
|
|
88
|
+
private readonly now;
|
|
89
|
+
constructor(options: DynamoDbRegistryDriverOptions);
|
|
90
|
+
capabilities(): RegCaps;
|
|
91
|
+
get(ref: SegmentRef): Promise<RegistryRecord | null>;
|
|
92
|
+
create(ref: SegmentRef, record: NewRegistryRecord): Promise<{
|
|
93
|
+
token: Token;
|
|
94
|
+
}>;
|
|
95
|
+
compareAndSwap(ref: SegmentRef, expected: Token, patch: RegistryPatch): Promise<{
|
|
96
|
+
token: Token;
|
|
97
|
+
}>;
|
|
98
|
+
list(namespace?: string): AsyncIterable<RegistryRecord>;
|
|
99
|
+
delete(ref: SegmentRef): Promise<void>;
|
|
100
|
+
/** The PK prefix to scope a `list` scan, or `undefined` for an unscoped scan (no namespace, no keyPrefix). */
|
|
101
|
+
private scanPkPrefix;
|
|
102
|
+
/** Map a raw SDK error to the typed vocabulary (conflict / transient / passthrough). */
|
|
103
|
+
private mapError;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { DynamoDbRegistryDriver, type DynamoDbRegistryDriverOptions, DynamoDbWarmDriver, type DynamoDbWarmDriverOptions };
|