/** * Partition extraction. Walks the FK closure, re-encrypts * the selected records under fresh per-collection DEKs, seals those DEKs * under a one-time transfer key, and serializes an unowned * `extracted-partition` bundle. * * @module */ import type { Vault } from '../kernel/vault.js'; import type { EncryptedEnvelope } from '../kernel/types.js'; import { type EnclaveKey } from '../kernel/enclave/index.js'; import { type WalkClosureOptions, type DanglingRefNotice } from './walk-closure.js'; import { type TransferSealPayload } from '../with-pod/bundle.js'; /** Re-keyed collections snapshot + the fresh DEKs used. */ export interface ReKeyResult { readonly collections: Record>; readonly deks: Map; } /** * Re-encrypt every record in `closure` under a fresh per-collection DEK. * Reads raw source envelopes, decrypts under the source DEK, re-encrypts * under the new DEK. Plaintext-pipeline: requires an unlocked vault. */ export declare function reKeyClosure(vault: Vault, closure: Map>, fieldProjection?: Record): Promise; /** * Re-key the persisted JSON Schemas (`_schemas/`) for the * closure collections under the destination DEKs. Returns a * `{ collection: envelope }` map for the carried collections that actually * have a schema; collections without one are omitted. */ export declare function reKeySchemas(vault: Vault, closure: Map>, destDeks: Map, fieldProjection?: Record): Promise>; export interface ReKeyLedgerResult { /** { paddedIndex: re-encrypted entry envelope } for backup._internal._ledger. */ readonly entries: Record; /** Recomputed ledgerHead for the carried chain (index -1 when empty). */ readonly head: { hash: string; index: number; ts: string; }; } /** * Build the carried `_ledger` chain for an extracted partition. * Filters source entries to the closure, RE-CHAINS them (fresh index + prevHash), * and re-encrypts under `ledgerDek`. The `payloadHash` is recomputed against the * re-keyed envelope ONLY for the latest `put` per (collection,id) — the entry * `verifyBackupIntegrity` cross-checks; earlier puts + deletes keep their source * `payloadHash` verbatim (recomputing an intermediate put would assert a false * hash for an older version). Amendments + out-of-closure entries are dropped; * `_ledger_deltas`/`_history` are deferred to slice 2. */ export declare function reKeyLedger(vault: Vault, closure: Map>, reKeyedCollections: Record>, ledgerDek: EnclaveKey): Promise; /** Carried blob internals + the fresh transfer `_blob` DEK (present only when * the closure references at least one chunk-based blob). */ export interface ReKeyBlobsResult { /** `_blob_slots_` / `_blob_versions_` / `_blob_index` / `_blob_chunks` * envelopes for the bundle's `_internal` map. */ readonly internal: Record>; /** Fresh transfer `_blob` DEK — seal it so owner-creation wraps it under the * recipient KEK. Undefined when no blob travels (source keyring untouched). */ readonly blobDek?: EnclaveKey; } /** * Carry the FK-closed slice's blobs — HARDENED key handling (no master-key leak). * * The source vault's shared `_blob` DEK decrypts (or unwraps the content CEK of) * EVERY blob in the source. Sealing it into the transfer would hand the recipient * a key to blobs far outside their slice. Instead we mint a **fresh transfer * `_blob` DEK** and arrange every carried blob into per-blob-CEK mode under it: * * - **erasable blob** (`_cek` present): unwrap the per-blob content CEK under the * SOURCE `_blob` DEK, re-wrap it under the FRESH transfer DEK. Chunks travel * **verbatim** (still ciphertext under that same content CEK — passthrough). * - **legacy blob** (no `_cek`, chunks under the shared `_blob` DEK): promote it * IN-BUNDLE — mint a fresh content CEK, decrypt each chunk under the source * `_blob` DEK and re-encrypt under the content CEK (same AAD, so the eTag-bound * integrity holds), then wrap the content CEK under the transfer DEK. The * SOURCE is never mutated (non-destructive), and the bundle never holds * plaintext blob bytes (zero-knowledge preserved). * * **eTag identity is preserved** (not re-HMAC'd). eTags are HMAC-keyed off the * `_blob` DEK, but they are stored as OPAQUE keys (`_blob_index/`, * `_blob_chunks/_`) and never recomputed on read — only on a `put()` * for dedup. Keeping them verbatim keeps every slot/version/index/chunk key and * the chunk AAD coherent with zero chunk-key churn. The only consequence: a * future `put()` of identical bytes in the adopted vault computes a different * eTag (HMAC under the fresh DEK) and will NOT dedup against the carried blob — * an accepted, documented trade for hardened key isolation. * * Slots/versions are re-keyed under their parent collection's destination DEK * (honoring `fieldProjection` — projected-out blob fields' slots never travel); * `BlobObject.refCount` is recomputed from carried references only. * * `external` slots reference an unencrypted shared-bucket object that is not in * the bundle — their slot metadata travels (so the catalog entry survives) but * the bytes do not (a documented v1 limitation; their eTag is `''`). */ export declare function reKeyBlobs(vault: Vault, closure: Map>, destDeks: Map, fieldProjection?: Record): Promise; /** A minted transfer key (raw 32 bytes) + the seal carrying the DEK set. */ export interface SealResult { readonly seal: TransferSealPayload; readonly transferKey: Uint8Array; } /** * Mint a random 32-byte transfer key, export each DEK to raw bytes, and * AES-256-GCM-seal the `{ collection: base64(rawDEK) }` map under the * transfer key. The transfer key is returned to the caller out-of-band; * only the sealed bytes travel in the bundle. Layout: iv(12) ‖ ct ‖ tag. */ export declare function sealDeks(deks: Map): Promise; export interface ExtractPartitionResult { readonly bundleBytes: Uint8Array; /** Raw 32-byte transfer key — deliver out-of-band; required to adopt. */ readonly transferKey: Uint8Array; readonly sealId: string; /** * #759: outbound FK edges whose referenced parent was excluded from the * partition (missing, or tier-elevated and therefore invisible). The * child record still carries its FK value; the parent did not travel — * callers should surface this to the operator rather than assume * referential completeness. */ readonly danglingRefs: ReadonlyArray; } /** * Extract a re-keyed, transfer-sealed partition. Owner-only * (invariant 5): producing a standalone re-keyed vault is an * ownership operation. Non-destructive on the source. */ export type ExtractPartitionOptions = WalkClosureOptions & { readonly compression?: 'auto' | 'brotli' | 'gzip' | 'none'; readonly carrySchemas?: boolean; readonly carryLedger?: boolean; /** * FR-7 structural field projection: per-collection allow-list of fields * to keep. Non-listed fields are dropped from each record BEFORE * re-encryption (so they never travel in the bundle); `id` is always * preserved. A projected collection's persisted schema is NOT carried. * Absent/empty → un-projected behavior (byte-identical to today). */ readonly fieldProjection?: Record; }; /** * Public extract-partition entry — gated behind `cargoStrategy: withCargo()` * (S4). Routes through the source vault's cargo strategy so an un-opted-in * caller hits `NO_CARGO`'s throw; `withCargo()` dynamically imports and runs * {@link extractPartitionCore} (the crypto engine, kept out of the floor). */ export declare function extractPartition(vault: Vault, opts: ExtractPartitionOptions): Promise; /** * The extract-partition crypto engine — reached only when `withCargo()` is * opted in (via the lazy `with-cargo/active.ts` chunk). Body unchanged from the * hardened implementation; only the public gate wraps it. */ export declare function extractPartitionCore(vault: Vault, opts: ExtractPartitionOptions): Promise;