@tearleads/client-sdk 0.1.28 → 0.1.29

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.
@@ -1,6 +1,6 @@
1
- import { AES_GCM_IV_BYTES, assertAesGcmIv, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, CONTENT_RECORD_ENCRYPTION_SUITE, serializeKeyingCanonicalJson, toFingerprint, } from "../../../../internal/crypto/index.js";
1
+ import { AES_GCM_IV_BYTES, assertAesGcmIv, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, CONTENT_RECORD_ENCRYPTION_SUITE, serializeKeyingCanonicalJson, } from "../../../../internal/crypto/index.js";
2
2
  import { bytesToBase64 } from "../../../../internal/encoding/index.js";
3
- import { BLOB_CONTENT_RECORD_AAD_DOMAIN, BLOB_CONTENT_RECORD_HKDF_SALT, BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN, BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN, BLOB_CONTENT_RECORD_NONCE_DOMAIN, TEXT_ENCODER, } from "./types.js";
3
+ import { BLOB_CONTENT_RECORD_AAD_DOMAIN, BLOB_CONTENT_RECORD_HKDF_SALT, BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN, BLOB_CONTENT_RECORD_NONCE_DOMAIN, TEXT_ENCODER, } from "./types.js";
4
4
  const SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;
5
5
  // The chunk layout is part of the IV family: a persisted nonce seed can
6
6
  // outlive a chunk-size change (upload resume across an app update), and the
@@ -24,21 +24,6 @@ export async function deriveBlobBaseIv(input) {
24
24
  }))));
25
25
  return digest.slice(0, AES_GCM_IV_BYTES);
26
26
  }
27
- export async function blobContentMetadataHash(input) {
28
- return toFingerprint(TEXT_ENCODER.encode(serializeKeyingCanonicalJson({
29
- domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,
30
- payload: {
31
- version: BLOB_ENCRYPTED_BYTES_VERSION,
32
- recordKind: "blob_bytes",
33
- blobId: input.blobId,
34
- byteLength: input.byteLength,
35
- chunkCount: input.chunkCount,
36
- chunkSize: input.chunkSize,
37
- contentKeyEpoch: input.contentKeyEpoch,
38
- format: BLOB_ENCRYPTED_BYTES_FORMAT,
39
- },
40
- })));
41
- }
42
27
  function contentRecordDerivationPayload(input) {
43
28
  return {
44
29
  version: BLOB_ENCRYPTED_BYTES_VERSION,
@@ -1 +1 @@
1
- {"version":3,"file":"blobRecordCrypto.js","sourceRoot":"","sources":["blobRecordCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_GCM_IV_BYTES,\n assertAesGcmIv,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n type KeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n toFingerprint,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport {\n BLOB_CONTENT_RECORD_AAD_DOMAIN,\n BLOB_CONTENT_RECORD_HKDF_SALT,\n BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n TEXT_ENCODER,\n} from \"./types\";\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n// The chunk layout is part of the IV family: a persisted nonce seed can\n// outlive a chunk-size change (upload resume across an app update), and the\n// record key does not vary with the layout, so the same seed must never\n// yield the same per-chunk IVs over two different plaintext chunkings.\nexport async function deriveBlobBaseIv(input: {\n readonly chunkSize: number;\n readonly nonceSeed: Uint8Array;\n readonly plaintextSha256: string;\n}): Promise<Uint8Array<ArrayBuffer>> {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n if (!SHA256_HEX_PATTERN.test(input.plaintextSha256)) {\n throw new Error(\"Blob plaintext SHA-256 is invalid\");\n }\n if (!Number.isInteger(input.chunkSize) || input.chunkSize <= 0) {\n throw new Error(\"Blob chunk size is invalid\");\n }\n\n const digest = new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n payload: {\n chunkSize: input.chunkSize,\n nonceSeed: bytesToBase64(input.nonceSeed),\n plaintextSha256: input.plaintextSha256,\n },\n }),\n ),\n ),\n );\n return digest.slice(0, AES_GCM_IV_BYTES);\n}\n\nexport async function blobContentMetadataHash(input: {\n blobId: string;\n byteLength: number;\n chunkCount: number;\n chunkSize: number;\n contentKeyEpoch: number;\n}): Promise<string> {\n return toFingerprint(\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n payload: {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n recordKind: \"blob_bytes\",\n blobId: input.blobId,\n byteLength: input.byteLength,\n chunkCount: input.chunkCount,\n chunkSize: input.chunkSize,\n contentKeyEpoch: input.contentKeyEpoch,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n },\n }),\n ),\n );\n}\n\nfunction contentRecordDerivationPayload(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Record<string, KeyingCanonicalJson> {\n return {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: input.contentRecordId,\n };\n}\n\nfunction contentRecordDerivationBytes(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n payload: contentRecordDerivationPayload(input),\n }),\n );\n}\n\nexport function contentRecordAdditionalDataBytes(input: {\n blobId: string;\n chunkCount: number;\n chunkIndex: number;\n chunkPlaintextByteLength: number;\n chunkSize: number;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n organizationId: string;\n plaintextByteLength: number;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_AAD_DOMAIN,\n payload: {\n ...contentRecordDerivationPayload(input),\n blobFormat: BLOB_ENCRYPTED_BYTES_FORMAT,\n blobFormatVersion: BLOB_ENCRYPTED_BYTES_VERSION,\n chunkCount: input.chunkCount,\n chunkIndex: input.chunkIndex,\n chunkPlaintextByteLength: input.chunkPlaintextByteLength,\n chunkSize: input.chunkSize,\n metadataHash: input.metadataHash,\n nonceDomainHash: input.nonceDomainHash,\n plaintextByteLength: input.plaintextByteLength,\n },\n }),\n );\n}\n\nexport async function deriveBlobContentRecordKey(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentKeyMaterial: CryptoKey;\n contentRecordId: string;\n organizationId: string;\n usage: \"decrypt\" | \"encrypt\";\n}): Promise<CryptoKey> {\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: BLOB_CONTENT_RECORD_HKDF_SALT,\n info: contentRecordDerivationBytes(input),\n },\n input.contentKeyMaterial,\n { name: \"AES-GCM\", length: 256 },\n false,\n [input.usage],\n );\n}\n"]}
1
+ {"version":3,"file":"blobRecordCrypto.js","sourceRoot":"","sources":["blobRecordCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_GCM_IV_BYTES,\n assertAesGcmIv,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n type KeyingCanonicalJson,\n serializeKeyingCanonicalJson,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport {\n BLOB_CONTENT_RECORD_AAD_DOMAIN,\n BLOB_CONTENT_RECORD_HKDF_SALT,\n BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n TEXT_ENCODER,\n} from \"./types\";\n\nconst SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/;\n\n// The chunk layout is part of the IV family: a persisted nonce seed can\n// outlive a chunk-size change (upload resume across an app update), and the\n// record key does not vary with the layout, so the same seed must never\n// yield the same per-chunk IVs over two different plaintext chunkings.\nexport async function deriveBlobBaseIv(input: {\n readonly chunkSize: number;\n readonly nonceSeed: Uint8Array;\n readonly plaintextSha256: string;\n}): Promise<Uint8Array<ArrayBuffer>> {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n if (!SHA256_HEX_PATTERN.test(input.plaintextSha256)) {\n throw new Error(\"Blob plaintext SHA-256 is invalid\");\n }\n if (!Number.isInteger(input.chunkSize) || input.chunkSize <= 0) {\n throw new Error(\"Blob chunk size is invalid\");\n }\n\n const digest = new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_NONCE_DOMAIN,\n payload: {\n chunkSize: input.chunkSize,\n nonceSeed: bytesToBase64(input.nonceSeed),\n plaintextSha256: input.plaintextSha256,\n },\n }),\n ),\n ),\n );\n return digest.slice(0, AES_GCM_IV_BYTES);\n}\n\nfunction contentRecordDerivationPayload(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Record<string, KeyingCanonicalJson> {\n return {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch: input.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: input.contentRecordId,\n };\n}\n\nfunction contentRecordDerivationBytes(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentRecordId: string;\n organizationId: string;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN,\n payload: contentRecordDerivationPayload(input),\n }),\n );\n}\n\nexport function contentRecordAdditionalDataBytes(input: {\n blobId: string;\n chunkCount: number;\n chunkIndex: number;\n chunkPlaintextByteLength: number;\n chunkSize: number;\n contentKeyEpoch: number;\n contentRecordId: string;\n metadataHash: string;\n nonceDomainHash: string;\n organizationId: string;\n plaintextByteLength: number;\n}): Uint8Array<ArrayBuffer> {\n return TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_AAD_DOMAIN,\n payload: {\n ...contentRecordDerivationPayload(input),\n blobFormat: BLOB_ENCRYPTED_BYTES_FORMAT,\n blobFormatVersion: BLOB_ENCRYPTED_BYTES_VERSION,\n chunkCount: input.chunkCount,\n chunkIndex: input.chunkIndex,\n chunkPlaintextByteLength: input.chunkPlaintextByteLength,\n chunkSize: input.chunkSize,\n metadataHash: input.metadataHash,\n nonceDomainHash: input.nonceDomainHash,\n plaintextByteLength: input.plaintextByteLength,\n },\n }),\n );\n}\n\nexport async function deriveBlobContentRecordKey(input: {\n blobId: string;\n contentKeyEpoch: number;\n contentKeyMaterial: CryptoKey;\n contentRecordId: string;\n organizationId: string;\n usage: \"decrypt\" | \"encrypt\";\n}): Promise<CryptoKey> {\n return crypto.subtle.deriveKey(\n {\n name: \"HKDF\",\n hash: \"SHA-256\",\n salt: BLOB_CONTENT_RECORD_HKDF_SALT,\n info: contentRecordDerivationBytes(input),\n },\n input.contentKeyMaterial,\n { name: \"AES-GCM\", length: 256 },\n false,\n [input.usage],\n );\n}\n"]}
@@ -1,7 +1,7 @@
1
- import { AES_256_KEY_BYTES, AES_GCM_TAG_BYTES, assertAesGcmIv, BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobChunkCount, computeBlobEncryptedByteLength, computeContentRecordNonceDomainHash, createAesGcmIv, createIncrementalSha256, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, normalizeBlobChunkSize, } from "../../../../internal/crypto/index.js";
1
+ import { AES_256_KEY_BYTES, AES_GCM_TAG_BYTES, assertAesGcmIv, BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, blobContentMetadataHash, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobChunkCount, computeBlobEncryptedByteLength, computeContentRecordNonceDomainHash, createAesGcmIv, createIncrementalSha256, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, normalizeBlobChunkSize, } from "../../../../internal/crypto/index.js";
2
2
  import { bytesToBase64 } from "../../../../internal/encoding/index.js";
3
3
  import { importContentKeyMaterial } from "../../shared/contentRecordKeys.js";
4
- import { blobContentMetadataHash, contentRecordAdditionalDataBytes, deriveBlobBaseIv, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
4
+ import { contentRecordAdditionalDataBytes, deriveBlobBaseIv, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
5
5
  import { inspectBlobSource, readVerifiedBlobSourceChunk, } from "./blobSourceSnapshot.js";
6
6
  import { createEncryptedUploadParts } from "./encryptedUploadParts.js";
7
7
  export const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;
@@ -1 +1 @@
1
- {"version":3,"file":"chunkedBlobCrypto.js","sourceRoot":"","sources":["chunkedBlobCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_256_KEY_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n computeContentRecordNonceDomainHash,\n createAesGcmIv,\n createIncrementalSha256,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n normalizeBlobChunkSize,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport type { BlobContentKeyBundleRequest } from \"@tearleads/validators/request\";\nimport type { BlobByteSource, BlobBytes } from \"../../../blobContracts\";\nimport { importContentKeyMaterial } from \"../../shared/contentRecordKeys\";\nimport {\n blobContentMetadataHash,\n contentRecordAdditionalDataBytes,\n deriveBlobBaseIv,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nimport {\n type BlobSourceSnapshot,\n inspectBlobSource,\n readVerifiedBlobSourceChunk,\n} from \"./blobSourceSnapshot\";\nimport { createEncryptedUploadParts } from \"./encryptedUploadParts\";\nimport type { BlobEncryptionPlan } from \"./types\";\n\nexport const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;\n\ninterface PrepareBlobEncryptionInput {\n readonly blobId: string;\n readonly chunkSize?: number | undefined;\n readonly contentKey: Uint8Array;\n readonly contentKeyBundle: BlobContentKeyBundleRequest;\n readonly nonceSeed?: Uint8Array | undefined;\n readonly organizationId: string;\n readonly source: BlobByteSource;\n readonly sourceSnapshot?: BlobSourceSnapshot | undefined;\n}\n\ninterface BlobEncryptionContext {\n readonly baseIv: BlobBytes;\n readonly blobId: string;\n readonly chunkSize: number;\n readonly contentKeyEpoch: number;\n readonly contentRecordId: string;\n readonly headerBytes: BlobBytes;\n readonly metadataHash: string;\n readonly nonceDomainHash: string;\n readonly organizationId: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly recordKey: CryptoKey;\n}\n\nasync function createBlobEncryptionContext(\n input: PrepareBlobEncryptionInput,\n snapshot: BlobSourceSnapshot,\n): Promise<BlobEncryptionContext> {\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const plaintextByteLength = input.source.byteLength;\n const partCount = computeBlobChunkCount(plaintextByteLength, chunkSize);\n const contentRecordId = input.blobId;\n const { contentKeyEpoch } = input.contentKeyBundle;\n const nonceSeed = new Uint8Array(input.nonceSeed ?? createAesGcmIv());\n const baseIv = await deriveBlobBaseIv({\n chunkSize,\n nonceSeed,\n plaintextSha256: snapshot.sha256,\n });\n\n const [nonceDomainHash, metadataHash] = await Promise.all([\n computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n }),\n blobContentMetadataHash({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n }),\n ]);\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: input.blobId,\n contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n return {\n baseIv,\n blobId: input.blobId,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n headerBytes: encodeBlobEnvelopeV2Header({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n iv: bytesToBase64(baseIv),\n metadataHash,\n nonceDomainHash,\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n }),\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n partCount,\n plaintextByteLength,\n recordKey,\n };\n}\n\nasync function encryptBlobChunk(\n context: BlobEncryptionContext,\n chunkIndex: number,\n plaintext: BlobBytes,\n): Promise<BlobBytes> {\n return new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: deriveBlobChunkIv(context.baseIv, chunkIndex),\n additionalData: contentRecordAdditionalDataBytes({\n blobId: context.blobId,\n chunkCount: context.partCount,\n chunkIndex,\n chunkPlaintextByteLength: plaintext.byteLength,\n chunkSize: context.chunkSize,\n contentKeyEpoch: context.contentKeyEpoch,\n contentRecordId: context.contentRecordId,\n metadataHash: context.metadataHash,\n nonceDomainHash: context.nonceDomainHash,\n organizationId: context.organizationId,\n plaintextByteLength: context.plaintextByteLength,\n }),\n },\n context.recordKey,\n plaintext,\n ),\n );\n}\n\nasync function runEncryptionHashPass(\n source: BlobByteSource,\n snapshot: BlobSourceSnapshot,\n context: BlobEncryptionContext,\n): Promise<string> {\n const objectHash = createIncrementalSha256();\n objectHash.update(context.headerBytes);\n for (let chunkIndex = 0; chunkIndex < context.partCount; chunkIndex += 1) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n objectHash.update(await encryptBlobChunk(context, chunkIndex, plaintext));\n }\n return bytesToHex(objectHash.digest());\n}\n\nfunction createBlobEncryptionPlan(input: {\n readonly context: BlobEncryptionContext;\n readonly sha256: string;\n readonly snapshot: BlobSourceSnapshot;\n readonly source: BlobByteSource;\n}): BlobEncryptionPlan {\n const { context, sha256, snapshot, source } = input;\n const byteLength = computeBlobEncryptedByteLength({\n chunkCount: context.partCount,\n headerByteLength: context.headerBytes.byteLength,\n plaintextByteLength: context.plaintextByteLength,\n });\n return {\n byteLength,\n chunkSize: context.chunkSize,\n metadataHash: context.metadataHash,\n plaintextByteLength: context.plaintextByteLength,\n plaintextSha256: snapshot.sha256,\n sha256,\n ...createEncryptedUploadParts({\n byteLength,\n chunkSize: context.chunkSize + AES_GCM_TAG_BYTES,\n header: context.headerBytes,\n async encryptChunk(chunkIndex) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n return encryptBlobChunk(context, chunkIndex, plaintext);\n },\n }),\n };\n}\n\nexport async function prepareBlobEncryption(\n input: PrepareBlobEncryptionInput,\n): Promise<BlobEncryptionPlan> {\n if (input.contentKey.byteLength !== AES_256_KEY_BYTES) {\n throw new Error(\"Blob content key must be 32 bytes\");\n }\n if (input.nonceSeed) {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n }\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const snapshot =\n input.sourceSnapshot ?? (await inspectBlobSource(input.source, chunkSize));\n const context = await createBlobEncryptionContext(input, snapshot);\n if (\n snapshot.byteLength !== context.plaintextByteLength ||\n snapshot.chunkSize !== context.chunkSize\n ) {\n throw new Error(\"Blob source snapshot does not match encryption layout\");\n }\n const sha256 = await runEncryptionHashPass(input.source, snapshot, context);\n return createBlobEncryptionPlan({\n context,\n sha256,\n snapshot,\n source: input.source,\n });\n}\n"]}
1
+ {"version":3,"file":"chunkedBlobCrypto.js","sourceRoot":"","sources":["chunkedBlobCrypto.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AES_256_KEY_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n blobContentMetadataHash,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n computeContentRecordNonceDomainHash,\n createAesGcmIv,\n createIncrementalSha256,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n normalizeBlobChunkSize,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport type { BlobContentKeyBundleRequest } from \"@tearleads/validators/request\";\nimport type { BlobByteSource, BlobBytes } from \"../../../blobContracts\";\nimport { importContentKeyMaterial } from \"../../shared/contentRecordKeys\";\nimport {\n contentRecordAdditionalDataBytes,\n deriveBlobBaseIv,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nimport {\n type BlobSourceSnapshot,\n inspectBlobSource,\n readVerifiedBlobSourceChunk,\n} from \"./blobSourceSnapshot\";\nimport { createEncryptedUploadParts } from \"./encryptedUploadParts\";\nimport type { BlobEncryptionPlan } from \"./types\";\n\nexport const DEFAULT_BLOB_CHUNK_SIZE_BYTES = BLOB_CHUNK_SIZE_BYTES;\n\ninterface PrepareBlobEncryptionInput {\n readonly blobId: string;\n readonly chunkSize?: number | undefined;\n readonly contentKey: Uint8Array;\n readonly contentKeyBundle: BlobContentKeyBundleRequest;\n readonly nonceSeed?: Uint8Array | undefined;\n readonly organizationId: string;\n readonly source: BlobByteSource;\n readonly sourceSnapshot?: BlobSourceSnapshot | undefined;\n}\n\ninterface BlobEncryptionContext {\n readonly baseIv: BlobBytes;\n readonly blobId: string;\n readonly chunkSize: number;\n readonly contentKeyEpoch: number;\n readonly contentRecordId: string;\n readonly headerBytes: BlobBytes;\n readonly metadataHash: string;\n readonly nonceDomainHash: string;\n readonly organizationId: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly recordKey: CryptoKey;\n}\n\nasync function createBlobEncryptionContext(\n input: PrepareBlobEncryptionInput,\n snapshot: BlobSourceSnapshot,\n): Promise<BlobEncryptionContext> {\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const plaintextByteLength = input.source.byteLength;\n const partCount = computeBlobChunkCount(plaintextByteLength, chunkSize);\n const contentRecordId = input.blobId;\n const { contentKeyEpoch } = input.contentKeyBundle;\n const nonceSeed = new Uint8Array(input.nonceSeed ?? createAesGcmIv());\n const baseIv = await deriveBlobBaseIv({\n chunkSize,\n nonceSeed,\n plaintextSha256: snapshot.sha256,\n });\n\n const [nonceDomainHash, metadataHash] = await Promise.all([\n computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: input.organizationId,\n objectKind: \"blob\",\n objectId: input.blobId,\n contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId,\n }),\n blobContentMetadataHash({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n }),\n ]);\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: input.blobId,\n contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId,\n organizationId: input.organizationId,\n usage: \"encrypt\",\n });\n return {\n baseIv,\n blobId: input.blobId,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n headerBytes: encodeBlobEnvelopeV2Header({\n blobId: input.blobId,\n byteLength: plaintextByteLength,\n chunkCount: partCount,\n chunkSize,\n contentKeyEpoch,\n contentRecordId,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n iv: bytesToBase64(baseIv),\n metadataHash,\n nonceDomainHash,\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n }),\n metadataHash,\n nonceDomainHash,\n organizationId: input.organizationId,\n partCount,\n plaintextByteLength,\n recordKey,\n };\n}\n\nasync function encryptBlobChunk(\n context: BlobEncryptionContext,\n chunkIndex: number,\n plaintext: BlobBytes,\n): Promise<BlobBytes> {\n return new Uint8Array(\n await crypto.subtle.encrypt(\n {\n name: \"AES-GCM\",\n iv: deriveBlobChunkIv(context.baseIv, chunkIndex),\n additionalData: contentRecordAdditionalDataBytes({\n blobId: context.blobId,\n chunkCount: context.partCount,\n chunkIndex,\n chunkPlaintextByteLength: plaintext.byteLength,\n chunkSize: context.chunkSize,\n contentKeyEpoch: context.contentKeyEpoch,\n contentRecordId: context.contentRecordId,\n metadataHash: context.metadataHash,\n nonceDomainHash: context.nonceDomainHash,\n organizationId: context.organizationId,\n plaintextByteLength: context.plaintextByteLength,\n }),\n },\n context.recordKey,\n plaintext,\n ),\n );\n}\n\nasync function runEncryptionHashPass(\n source: BlobByteSource,\n snapshot: BlobSourceSnapshot,\n context: BlobEncryptionContext,\n): Promise<string> {\n const objectHash = createIncrementalSha256();\n objectHash.update(context.headerBytes);\n for (let chunkIndex = 0; chunkIndex < context.partCount; chunkIndex += 1) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n objectHash.update(await encryptBlobChunk(context, chunkIndex, plaintext));\n }\n return bytesToHex(objectHash.digest());\n}\n\nfunction createBlobEncryptionPlan(input: {\n readonly context: BlobEncryptionContext;\n readonly sha256: string;\n readonly snapshot: BlobSourceSnapshot;\n readonly source: BlobByteSource;\n}): BlobEncryptionPlan {\n const { context, sha256, snapshot, source } = input;\n const byteLength = computeBlobEncryptedByteLength({\n chunkCount: context.partCount,\n headerByteLength: context.headerBytes.byteLength,\n plaintextByteLength: context.plaintextByteLength,\n });\n return {\n byteLength,\n chunkSize: context.chunkSize,\n metadataHash: context.metadataHash,\n plaintextByteLength: context.plaintextByteLength,\n plaintextSha256: snapshot.sha256,\n sha256,\n ...createEncryptedUploadParts({\n byteLength,\n chunkSize: context.chunkSize + AES_GCM_TAG_BYTES,\n header: context.headerBytes,\n async encryptChunk(chunkIndex) {\n const plaintext = await readVerifiedBlobSourceChunk({\n chunkIndex,\n snapshot,\n source,\n });\n return encryptBlobChunk(context, chunkIndex, plaintext);\n },\n }),\n };\n}\n\nexport async function prepareBlobEncryption(\n input: PrepareBlobEncryptionInput,\n): Promise<BlobEncryptionPlan> {\n if (input.contentKey.byteLength !== AES_256_KEY_BYTES) {\n throw new Error(\"Blob content key must be 32 bytes\");\n }\n if (input.nonceSeed) {\n assertAesGcmIv(input.nonceSeed, \"Blob nonce seed is invalid\");\n }\n const chunkSize = normalizeBlobChunkSize(\n input.chunkSize ?? DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n );\n const snapshot =\n input.sourceSnapshot ?? (await inspectBlobSource(input.source, chunkSize));\n const context = await createBlobEncryptionContext(input, snapshot);\n if (\n snapshot.byteLength !== context.plaintextByteLength ||\n snapshot.chunkSize !== context.chunkSize\n ) {\n throw new Error(\"Blob source snapshot does not match encryption layout\");\n }\n const sha256 = await runEncryptionHashPass(input.source, snapshot, context);\n return createBlobEncryptionPlan({\n context,\n sha256,\n snapshot,\n source: input.source,\n });\n}\n"]}
@@ -1,3 +1,3 @@
1
- export { blobContentMetadataHash, contentRecordAdditionalDataBytes, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
1
+ export { contentRecordAdditionalDataBytes, deriveBlobContentRecordKey, } from "./blobRecordCrypto.js";
2
2
  export { DEFAULT_BLOB_CHUNK_SIZE_BYTES, prepareBlobEncryption, } from "./chunkedBlobCrypto.js";
3
3
  //# sourceMappingURL=crypto.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n blobContentMetadataHash,\n contentRecordAdditionalDataBytes,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nexport {\n DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n prepareBlobEncryption,\n} from \"./chunkedBlobCrypto\";\nexport type { BlobEncryptionPlan } from \"./types\";\n"]}
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["crypto.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gCAAgC,EAChC,0BAA0B,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,6BAA6B,EAC7B,qBAAqB,GACtB,MAAM,qBAAqB,CAAC","sourcesContent":["export {\n contentRecordAdditionalDataBytes,\n deriveBlobContentRecordKey,\n} from \"./blobRecordCrypto\";\nexport {\n DEFAULT_BLOB_CHUNK_SIZE_BYTES,\n prepareBlobEncryption,\n} from \"./chunkedBlobCrypto\";\nexport type { BlobEncryptionPlan } from \"./types\";\n"]}
@@ -8,7 +8,6 @@ import type { ExecSql } from "../../../sqlite/sqlSchema.js";
8
8
  import type { DocumentCreateAuthor } from "../../shared/types.js";
9
9
  export declare const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN = "tearleads.blob.content-record-key-info";
10
10
  export declare const BLOB_CONTENT_RECORD_AAD_DOMAIN = "tearleads.blob.content-record-aad";
11
- export declare const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
12
11
  export declare const BLOB_CONTENT_RECORD_NONCE_DOMAIN = "tearleads.blob.content-record-nonce";
13
12
  export declare const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer>;
14
13
  export declare const TEXT_ENCODER: TextEncoder;
@@ -1,6 +1,5 @@
1
1
  export const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN = "tearleads.blob.content-record-key-info";
2
2
  export const BLOB_CONTENT_RECORD_AAD_DOMAIN = "tearleads.blob.content-record-aad";
3
- export const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
4
3
  export const BLOB_CONTENT_RECORD_NONCE_DOMAIN = "tearleads.blob.content-record-nonce";
5
4
  export const BLOB_CONTENT_RECORD_HKDF_SALT = new TextEncoder().encode("tearleads.blob.content-record-hkdf-salt");
6
5
  export const TEXT_ENCODER = new TextEncoder();
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GACzC,mCAAmC,CAAC;AACtC,MAAM,CAAC,MAAM,wCAAwC,GACnD,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,gCAAgC,GAC3C,qCAAqC,CAAC;AACxC,MAAM,CAAC,MAAM,6BAA6B,GACxC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC","sourcesContent":["import type {\n RequestFailureKind,\n UploadMultipartBlobPartBytesRequest,\n} from \"@tearleads/api-client\";\nimport type {\n BlobContentKeyTarget,\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n WriteHeader,\n} from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentBindRequest,\n BlobAttachmentDetachRequest,\n BlobContentKeyBundleRequest,\n CompleteMultipartBlobStageRequest,\n InitiateMultipartBlobStageRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n BlobAttachmentBindResponse,\n BlobAttachmentDetachResponse,\n BlobAttachmentSummary,\n CompleteMultipartBlobStageResponse,\n DocumentWriterProjectionResponse,\n InitiateMultipartBlobStageResponse,\n MultipartBlobStageStatusResponse,\n UploadMultipartBlobPartResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobByteSourceInput, BlobBytes } from \"../../../blobContracts\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../keyingProjectionVerification\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentCreateAuthor } from \"../../shared/types\";\n\nexport const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.blob.content-record-key-info\";\nexport const BLOB_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.blob.content-record-aad\";\nexport const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN =\n \"tearleads.blob.content-record-metadata\";\nexport const BLOB_CONTENT_RECORD_NONCE_DOMAIN =\n \"tearleads.blob.content-record-nonce\";\nexport const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.blob.content-record-hkdf-salt\");\nexport const TEXT_ENCODER = new TextEncoder();\n\nexport interface BlobAttachmentRequestOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n}\n\nexport interface BlobAttachmentApi {\n bindBlobAttachment(\n blobId: string,\n input: BlobAttachmentBindRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentBindResponse | null>;\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getRequestFailure?(input: {\n method: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n path: string;\n }): {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly status: number | null;\n } | null;\n completeMultipartBlobStage(\n stageId: string,\n input: CompleteMultipartBlobStageRequest,\n ): Promise<CompleteMultipartBlobStageResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getMultipartBlobStage(\n stageId: string,\n ): Promise<MultipartBlobStageStatusResponse | null>;\n initiateMultipartBlobStage(\n input: InitiateMultipartBlobStageRequest,\n ): Promise<InitiateMultipartBlobStageResponse | null>;\n uploadMultipartBlobPartBytes(\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n ): Promise<UploadMultipartBlobPartResponse | null>;\n}\n\nexport interface BlobAttachmentDetachApi {\n detachBlobAttachment(\n blobId: string,\n bindingId: string,\n input: BlobAttachmentDetachRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentDetachResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n}\n\nexport type { BlobContentKeyTarget };\n\n/**\n * Aliases of the crypto parser's own types. The parser is shared with the API,\n * so restating these shapes here would let the two drift apart silently.\n */\nexport type BlobEncryptedBytesRecord = BlobEnvelopeRecord;\n\n/**\n * The envelope as the header establishes it. Streaming readers hold this\n * before any ciphertext is read, so it has no `chunks`: an empty array there\n * would read as an empty object rather than an unread one.\n */\nexport type BlobEncryptedBytesHeader = BlobEnvelopeHeaderRecord;\n\nexport type BlobEncryptedChunk = BlobEnvelopeRecord[\"chunks\"][number];\n\nexport interface BlobSourceSnapshot {\n readonly byteLength: number;\n readonly chunkSha256: readonly string[];\n readonly chunkSize: number;\n readonly sha256: string;\n}\n\nexport interface BlobEncryptionPlan {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly metadataHash: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly plaintextSha256: string;\n readonly sha256: string;\n encryptPart(index: number): Promise<BlobBytes>;\n getPartByteLength(index: number): number;\n}\n\nexport interface DocumentManifestIdentity {\n documentId: string;\n manifestHash: string;\n organizationId: string;\n}\n\nexport interface BlobAttachmentMaterial {\n blobAccessManifestHash: string;\n contentKeyBundle: BlobContentKeyBundleRequest;\n encrypted: BlobEncryptionPlan;\n manifestIdentity: DocumentManifestIdentity;\n targetHash: string;\n targets: BlobContentKeyTarget[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\n/**\n * Progress for an in-flight multipart blob upload.\n */\nexport interface MultipartUploadProgress {\n readonly bytesTotal: number;\n readonly bytesUploaded: number;\n readonly partsCompleted: number;\n readonly partsTotal: number;\n}\n\nexport type MultipartUploadProgressListener = (\n progress: MultipartUploadProgress,\n) => void;\n\n/**\n * Fires once a multipart stage has been opened (or resumed), with its id and\n * plaintext chunk size. Callers persist both so a later attempt can reproduce\n * the same encrypted parts and resume instead of orphaning the partial upload.\n */\nexport type MultipartStageResolvedListener = (input: {\n readonly partSize: number;\n readonly stageId: string;\n}) => void | Promise<void>;\n\nexport interface UploadDocumentAttachmentInput {\n apiClient: BlobAttachmentApi;\n author: DocumentCreateAuthor;\n blobId?: string | undefined;\n bindingId?: string | undefined;\n bytes: BlobByteSourceInput;\n contentKey?: Uint8Array | undefined;\n contentKeyEpoch?: number | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n expectedBindingId: string | null;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n // A persisted nonce seed. The plaintext digest derives the envelope IV from\n // it, then each chunk derives a unique IV. Same-source retries reproduce the\n // encrypted parts; changed bytes cannot reuse their AES-GCM nonces.\n nonceSeed?: Uint8Array | undefined;\n multipart?: MultipartBlobUploadOptions | undefined;\n onMultipartProgress?: MultipartUploadProgressListener | undefined;\n onStageResolved?: MultipartStageResolvedListener | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface MultipartBlobUploadOptions {\n /** Exactly 5 MiB per plaintext encryption chunk; transport parts include envelope overhead. */\n readonly partSize: number;\n // The id of a previously opened stage to resume. When set, the upload refetches\n // that stage and skips parts the server already has instead of opening a new\n // one; if the stage is gone (expired), it falls back to a fresh stage.\n readonly resumeStageId?: string | undefined;\n readonly uploadConcurrency?: number | undefined;\n}\n\nexport interface UploadDocumentAttachmentResult {\n blobId: string;\n bindingId: string;\n request: BlobAttachmentBindRequest;\n response: BlobAttachmentBindResponse;\n sha256: string;\n byteLength: number;\n writeHeader: WriteHeader;\n writeHeaderHash: string;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DetachDocumentAttachmentInput {\n apiClient: BlobAttachmentDetachApi;\n author: DocumentCreateAuthor;\n bindingId: string;\n blobId: string;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DetachDocumentAttachmentResult {\n request: BlobAttachmentDetachRequest;\n response: BlobAttachmentDetachResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DecryptDocumentAttachmentBlobInput {\n binding: BlobAttachmentSummary;\n encryptedBytes: BlobBytes;\n expectedDocumentId: string;\n expectedSlotId: string;\n execSql: ExecSql;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"AAmCA,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wCAAwC,CAAC;AAC3C,MAAM,CAAC,MAAM,8BAA8B,GACzC,mCAAmC,CAAC;AACtC,MAAM,CAAC,MAAM,gCAAgC,GAC3C,qCAAqC,CAAC;AACxC,MAAM,CAAC,MAAM,6BAA6B,GACxC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,yCAAyC,CAAC,CAAC;AACtE,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC","sourcesContent":["import type {\n RequestFailureKind,\n UploadMultipartBlobPartBytesRequest,\n} from \"@tearleads/api-client\";\nimport type {\n BlobContentKeyTarget,\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n WriteHeader,\n} from \"@tearleads/crypto\";\nimport type {\n BlobAttachmentBindRequest,\n BlobAttachmentDetachRequest,\n BlobContentKeyBundleRequest,\n CompleteMultipartBlobStageRequest,\n InitiateMultipartBlobStageRequest,\n} from \"@tearleads/validators/request\";\nimport type {\n BlobAttachmentBindResponse,\n BlobAttachmentDetachResponse,\n BlobAttachmentSummary,\n CompleteMultipartBlobStageResponse,\n DocumentWriterProjectionResponse,\n InitiateMultipartBlobStageResponse,\n MultipartBlobStageStatusResponse,\n UploadMultipartBlobPartResponse,\n} from \"@tearleads/validators/response\";\nimport type { BlobByteSourceInput, BlobBytes } from \"../../../blobContracts\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../../keyingProjectionVerification\";\nimport type { ExecSql } from \"../../../sqlite/sqlSchema\";\nimport type { DocumentCreateAuthor } from \"../../shared/types\";\n\nexport const BLOB_CONTENT_RECORD_KEY_INFO_DOMAIN =\n \"tearleads.blob.content-record-key-info\";\nexport const BLOB_CONTENT_RECORD_AAD_DOMAIN =\n \"tearleads.blob.content-record-aad\";\nexport const BLOB_CONTENT_RECORD_NONCE_DOMAIN =\n \"tearleads.blob.content-record-nonce\";\nexport const BLOB_CONTENT_RECORD_HKDF_SALT: Uint8Array<ArrayBuffer> =\n new TextEncoder().encode(\"tearleads.blob.content-record-hkdf-salt\");\nexport const TEXT_ENCODER = new TextEncoder();\n\nexport interface BlobAttachmentRequestOptions {\n readonly expectedPaymentRequiredOrganizationId?: string | undefined;\n}\n\nexport interface BlobAttachmentApi {\n bindBlobAttachment(\n blobId: string,\n input: BlobAttachmentBindRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentBindResponse | null>;\n clearWriterProjectionCaches?(): void;\n evictDocumentWriterProjection?(documentId: string): void;\n getRequestFailure?(input: {\n method: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n path: string;\n }): {\n readonly code?: string | undefined;\n readonly kind: RequestFailureKind;\n readonly message: string;\n readonly status: number | null;\n } | null;\n completeMultipartBlobStage(\n stageId: string,\n input: CompleteMultipartBlobStageRequest,\n ): Promise<CompleteMultipartBlobStageResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n getMultipartBlobStage(\n stageId: string,\n ): Promise<MultipartBlobStageStatusResponse | null>;\n initiateMultipartBlobStage(\n input: InitiateMultipartBlobStageRequest,\n ): Promise<InitiateMultipartBlobStageResponse | null>;\n uploadMultipartBlobPartBytes(\n stageId: string,\n partNumber: number,\n input: UploadMultipartBlobPartBytesRequest,\n ): Promise<UploadMultipartBlobPartResponse | null>;\n}\n\nexport interface BlobAttachmentDetachApi {\n detachBlobAttachment(\n blobId: string,\n bindingId: string,\n input: BlobAttachmentDetachRequest,\n options?: BlobAttachmentRequestOptions,\n ): Promise<BlobAttachmentDetachResponse | null>;\n getDocumentWriterProjection(\n documentId: string,\n ): Promise<DocumentWriterProjectionResponse | null>;\n}\n\nexport type { BlobContentKeyTarget };\n\n/**\n * Aliases of the crypto parser's own types. The parser is shared with the API,\n * so restating these shapes here would let the two drift apart silently.\n */\nexport type BlobEncryptedBytesRecord = BlobEnvelopeRecord;\n\n/**\n * The envelope as the header establishes it. Streaming readers hold this\n * before any ciphertext is read, so it has no `chunks`: an empty array there\n * would read as an empty object rather than an unread one.\n */\nexport type BlobEncryptedBytesHeader = BlobEnvelopeHeaderRecord;\n\nexport type BlobEncryptedChunk = BlobEnvelopeRecord[\"chunks\"][number];\n\nexport interface BlobSourceSnapshot {\n readonly byteLength: number;\n readonly chunkSha256: readonly string[];\n readonly chunkSize: number;\n readonly sha256: string;\n}\n\nexport interface BlobEncryptionPlan {\n readonly byteLength: number;\n readonly chunkSize: number;\n readonly metadataHash: string;\n readonly partCount: number;\n readonly plaintextByteLength: number;\n readonly plaintextSha256: string;\n readonly sha256: string;\n encryptPart(index: number): Promise<BlobBytes>;\n getPartByteLength(index: number): number;\n}\n\nexport interface DocumentManifestIdentity {\n documentId: string;\n manifestHash: string;\n organizationId: string;\n}\n\nexport interface BlobAttachmentMaterial {\n blobAccessManifestHash: string;\n contentKeyBundle: BlobContentKeyBundleRequest;\n encrypted: BlobEncryptionPlan;\n manifestIdentity: DocumentManifestIdentity;\n targetHash: string;\n targets: BlobContentKeyTarget[];\n writerProjection: DocumentWriterProjectionResponse;\n}\n\n/**\n * Progress for an in-flight multipart blob upload.\n */\nexport interface MultipartUploadProgress {\n readonly bytesTotal: number;\n readonly bytesUploaded: number;\n readonly partsCompleted: number;\n readonly partsTotal: number;\n}\n\nexport type MultipartUploadProgressListener = (\n progress: MultipartUploadProgress,\n) => void;\n\n/**\n * Fires once a multipart stage has been opened (or resumed), with its id and\n * plaintext chunk size. Callers persist both so a later attempt can reproduce\n * the same encrypted parts and resume instead of orphaning the partial upload.\n */\nexport type MultipartStageResolvedListener = (input: {\n readonly partSize: number;\n readonly stageId: string;\n}) => void | Promise<void>;\n\nexport interface UploadDocumentAttachmentInput {\n apiClient: BlobAttachmentApi;\n author: DocumentCreateAuthor;\n blobId?: string | undefined;\n bindingId?: string | undefined;\n bytes: BlobByteSourceInput;\n contentKey?: Uint8Array | undefined;\n contentKeyEpoch?: number | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n expectedBindingId: string | null;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n // A persisted nonce seed. The plaintext digest derives the envelope IV from\n // it, then each chunk derives a unique IV. Same-source retries reproduce the\n // encrypted parts; changed bytes cannot reuse their AES-GCM nonces.\n nonceSeed?: Uint8Array | undefined;\n multipart?: MultipartBlobUploadOptions | undefined;\n onMultipartProgress?: MultipartUploadProgressListener | undefined;\n onStageResolved?: MultipartStageResolvedListener | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface MultipartBlobUploadOptions {\n /** Exactly 5 MiB per plaintext encryption chunk; transport parts include envelope overhead. */\n readonly partSize: number;\n // The id of a previously opened stage to resume. When set, the upload refetches\n // that stage and skips parts the server already has instead of opening a new\n // one; if the stage is gone (expired), it falls back to a fresh stage.\n readonly resumeStageId?: string | undefined;\n readonly uploadConcurrency?: number | undefined;\n}\n\nexport interface UploadDocumentAttachmentResult {\n blobId: string;\n bindingId: string;\n request: BlobAttachmentBindRequest;\n response: BlobAttachmentBindResponse;\n sha256: string;\n byteLength: number;\n writeHeader: WriteHeader;\n writeHeaderHash: string;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DetachDocumentAttachmentInput {\n apiClient: BlobAttachmentDetachApi;\n author: DocumentCreateAuthor;\n bindingId: string;\n blobId: string;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n isRemoteSyncBlocked?: ((organizationId: string) => boolean) | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt?: string | undefined;\n slotId: string;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection?: DocumentWriterProjectionResponse | undefined;\n}\n\nexport interface DetachDocumentAttachmentResult {\n request: BlobAttachmentDetachRequest;\n response: BlobAttachmentDetachResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}\n\nexport interface DecryptDocumentAttachmentBlobInput {\n binding: BlobAttachmentSummary;\n encryptedBytes: BlobBytes;\n expectedDocumentId: string;\n expectedSlotId: string;\n execSql: ExecSql;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n targetSecretKey: Uint8Array;\n stillCurrent?: (() => boolean) | undefined;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n writerProjection: DocumentWriterProjectionResponse;\n}\n"]}
@@ -6,14 +6,50 @@ export async function applyStoredAttachmentRemoval(input) {
6
6
  const { localId, removal, tx } = input;
7
7
  const pendingMatch = and(eq(documentPendingAttachments.localId, localId), eq(documentPendingAttachments.slotId, removal.slotId), eq(documentPendingAttachments.storageKey, removal.storageKey));
8
8
  const localMatch = and(eq(documentAttachmentBlobProjection.localId, localId), eq(documentAttachmentBlobProjection.slotId, removal.slotId), eq(documentAttachmentBlobProjection.storageKey, removal.storageKey));
9
- await tx.delete(documentPendingAttachments).where(pendingMatch).run();
10
- // A pending-only slot leaves no detached marker to queue its bytes later.
9
+ const [pending] = await tx
10
+ .delete(documentPendingAttachments)
11
+ .where(pendingMatch)
12
+ .returning();
13
+ // A pending upload without a remote identity needs no detach marker.
11
14
  // Reclaim keeps any key that a marker or another row still references.
12
15
  await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);
13
16
  if (removal.mode === "detach") {
17
+ const detachedAt = new Date().toISOString();
18
+ // The bind may have committed before its response (or settlement) was lost.
19
+ // Preserve its identity until sync confirms that the binding is detached.
20
+ if (pending?.uploadBlobId) {
21
+ const [marker] = await tx
22
+ .insert(documentAttachmentBlobProjection)
23
+ .values({
24
+ localId,
25
+ slotId: removal.slotId,
26
+ storageKey: removal.storageKey,
27
+ blobId: pending.uploadBlobId,
28
+ byteLength: pending.byteLength,
29
+ contentSha256: pending.contentSha256,
30
+ mimeType: pending.mimeType,
31
+ detachedAt,
32
+ updatedAt: detachedAt,
33
+ })
34
+ .onConflictDoUpdate({
35
+ target: [
36
+ documentAttachmentBlobProjection.localId,
37
+ documentAttachmentBlobProjection.slotId,
38
+ ],
39
+ set: {
40
+ blobId: pending.uploadBlobId,
41
+ detachedAt,
42
+ updatedAt: detachedAt,
43
+ },
44
+ setWhere: eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),
45
+ })
46
+ .returning();
47
+ if (!marker)
48
+ throw new Error("Attachment slot changed during removal");
49
+ }
14
50
  await tx
15
51
  .update(documentAttachmentBlobProjection)
16
- .set({ detachedAt: new Date().toISOString() })
52
+ .set({ detachedAt })
17
53
  .where(and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)))
18
54
  .run();
19
55
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n await tx.delete(documentPendingAttachments).where(pendingMatch).run();\n // A pending-only slot leaves no detached marker to queue its bytes later.\n // Reclaim keeps any key that a marker or another row still references.\n await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);\n if (removal.mode === \"detach\") {\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt: new Date().toISOString() })\n .where(\n and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)),\n )\n .run();\n return;\n }\n await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();\n}\n\nfunction assertAttachmentScope(\n localId: string,\n staging: AttachmentStaging,\n): void {\n const everyAttachmentMatchesScope = [\n ...staging.localAttachments,\n ...staging.pendingAttachments,\n ].every((attachment) => attachment.localId === localId);\n if (!everyAttachmentMatchesScope) {\n throw new Error(\"Attachment staging rows must share one document scope\");\n }\n}\n\nexport async function upsertStoredAttachmentStagingRows(input: {\n createdAt: string;\n localId: string;\n staging: AttachmentStaging;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { createdAt, localId, staging, tx } = input;\n assertAttachmentScope(localId, staging);\n\n for (const attachment of staging.pendingAttachments) {\n const row = buildPendingAttachmentRow(attachment, createdAt);\n const [existing] = await tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentPendingAttachments)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n mimeType: row.mimeType,\n name: row.name,\n storageKey: row.storageKey,\n uploadBlobId: row.uploadBlobId,\n uploadContentKey: row.uploadContentKey,\n uploadContentKeyEpoch: row.uploadContentKeyEpoch,\n uploadIv: row.uploadIv,\n uploadPartSize: row.uploadPartSize,\n uploadPlaintextSha256: row.uploadPlaintextSha256,\n uploadStageId: row.uploadStageId,\n },\n })\n .run();\n }\n\n for (const attachment of staging.localAttachments) {\n const row = { ...attachment, updatedAt: createdAt };\n const [existing] = await tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentAttachmentBlobProjection)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: row.blobId,\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n detachedAt: row.detachedAt,\n mimeType: row.mimeType,\n storageKey: row.storageKey,\n updatedAt: row.updatedAt,\n },\n })\n .run();\n }\n}\n"]}
1
+ {"version":3,"file":"attachmentStagingPersistence.js","sourceRoot":"","sources":["attachmentStagingPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, isNull } from \"drizzle-orm\";\nimport {\n documentAttachmentBlobProjection,\n documentPendingAttachments,\n} from \"../../../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../../../sqlite/sqlitePersistenceRuntime\";\nimport type { DocumentsPersistence } from \"../types\";\nimport { buildPendingAttachmentRow } from \"./attachmentRows\";\nimport { queueDocumentAttachmentStorageKeys } from \"./orphanSideRows\";\n\ntype AttachmentStaging = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentStaging\"]\n>;\ntype AttachmentRemoval = NonNullable<\n Parameters<\n DocumentsPersistence[\"commitDocumentMutation\"]\n >[1][\"attachmentRemoval\"]\n>;\n\nexport async function applyStoredAttachmentRemoval(input: {\n localId: string;\n removal: AttachmentRemoval;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { localId, removal, tx } = input;\n const pendingMatch = and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, removal.slotId),\n eq(documentPendingAttachments.storageKey, removal.storageKey),\n );\n const localMatch = and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, removal.slotId),\n eq(documentAttachmentBlobProjection.storageKey, removal.storageKey),\n );\n const [pending] = await tx\n .delete(documentPendingAttachments)\n .where(pendingMatch)\n .returning();\n // A pending upload without a remote identity needs no detach marker.\n // Reclaim keeps any key that a marker or another row still references.\n await queueDocumentAttachmentStorageKeys(tx, [removal.storageKey]);\n if (removal.mode === \"detach\") {\n const detachedAt = new Date().toISOString();\n // The bind may have committed before its response (or settlement) was lost.\n // Preserve its identity until sync confirms that the binding is detached.\n if (pending?.uploadBlobId) {\n const [marker] = await tx\n .insert(documentAttachmentBlobProjection)\n .values({\n localId,\n slotId: removal.slotId,\n storageKey: removal.storageKey,\n blobId: pending.uploadBlobId,\n byteLength: pending.byteLength,\n contentSha256: pending.contentSha256,\n mimeType: pending.mimeType,\n detachedAt,\n updatedAt: detachedAt,\n })\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: pending.uploadBlobId,\n detachedAt,\n updatedAt: detachedAt,\n },\n setWhere: eq(\n documentAttachmentBlobProjection.storageKey,\n removal.storageKey,\n ),\n })\n .returning();\n if (!marker) throw new Error(\"Attachment slot changed during removal\");\n }\n await tx\n .update(documentAttachmentBlobProjection)\n .set({ detachedAt })\n .where(\n and(localMatch, isNull(documentAttachmentBlobProjection.detachedAt)),\n )\n .run();\n return;\n }\n await tx.delete(documentAttachmentBlobProjection).where(localMatch).run();\n}\n\nfunction assertAttachmentScope(\n localId: string,\n staging: AttachmentStaging,\n): void {\n const everyAttachmentMatchesScope = [\n ...staging.localAttachments,\n ...staging.pendingAttachments,\n ].every((attachment) => attachment.localId === localId);\n if (!everyAttachmentMatchesScope) {\n throw new Error(\"Attachment staging rows must share one document scope\");\n }\n}\n\nexport async function upsertStoredAttachmentStagingRows(input: {\n createdAt: string;\n localId: string;\n staging: AttachmentStaging;\n tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const { createdAt, localId, staging, tx } = input;\n assertAttachmentScope(localId, staging);\n\n for (const attachment of staging.pendingAttachments) {\n const row = buildPendingAttachmentRow(attachment, createdAt);\n const [existing] = await tx\n .select({ storageKey: documentPendingAttachments.storageKey })\n .from(documentPendingAttachments)\n .where(\n and(\n eq(documentPendingAttachments.localId, localId),\n eq(documentPendingAttachments.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentPendingAttachments)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentPendingAttachments.localId,\n documentPendingAttachments.slotId,\n ],\n set: {\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n mimeType: row.mimeType,\n name: row.name,\n storageKey: row.storageKey,\n uploadBlobId: row.uploadBlobId,\n uploadContentKey: row.uploadContentKey,\n uploadContentKeyEpoch: row.uploadContentKeyEpoch,\n uploadIv: row.uploadIv,\n uploadPartSize: row.uploadPartSize,\n uploadPlaintextSha256: row.uploadPlaintextSha256,\n uploadStageId: row.uploadStageId,\n },\n })\n .run();\n }\n\n for (const attachment of staging.localAttachments) {\n const row = { ...attachment, updatedAt: createdAt };\n const [existing] = await tx\n .select({ storageKey: documentAttachmentBlobProjection.storageKey })\n .from(documentAttachmentBlobProjection)\n .where(\n and(\n eq(documentAttachmentBlobProjection.localId, localId),\n eq(documentAttachmentBlobProjection.slotId, attachment.slotId),\n ),\n )\n .limit(1);\n if (existing && existing.storageKey !== row.storageKey) {\n await queueDocumentAttachmentStorageKeys(tx, [existing.storageKey]);\n }\n await tx\n .insert(documentAttachmentBlobProjection)\n .values(row)\n .onConflictDoUpdate({\n target: [\n documentAttachmentBlobProjection.localId,\n documentAttachmentBlobProjection.slotId,\n ],\n set: {\n blobId: row.blobId,\n byteLength: row.byteLength,\n contentSha256: row.contentSha256,\n detachedAt: row.detachedAt,\n mimeType: row.mimeType,\n storageKey: row.storageKey,\n updatedAt: row.updatedAt,\n },\n })\n .run();\n }\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export declare function blobContentMetadataHash(input: {
2
+ blobId: string;
3
+ byteLength: number;
4
+ chunkCount: number;
5
+ chunkSize: number;
6
+ contentKeyEpoch: number;
7
+ }): Promise<string>;
@@ -0,0 +1,21 @@
1
+ import { BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, } from "./blobEnvelopeV2.js";
2
+ import { toFingerprint } from "./fingerprint.js";
3
+ import { serializeKeyingCanonicalJson } from "./keying/canonical.js";
4
+ const TEXT_ENCODER = new TextEncoder();
5
+ const BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN = "tearleads.blob.content-record-metadata";
6
+ export async function blobContentMetadataHash(input) {
7
+ return toFingerprint(TEXT_ENCODER.encode(serializeKeyingCanonicalJson({
8
+ domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,
9
+ payload: {
10
+ version: BLOB_ENCRYPTED_BYTES_VERSION,
11
+ recordKind: "blob_bytes",
12
+ blobId: input.blobId,
13
+ byteLength: input.byteLength,
14
+ chunkCount: input.chunkCount,
15
+ chunkSize: input.chunkSize,
16
+ contentKeyEpoch: input.contentKeyEpoch,
17
+ format: BLOB_ENCRYPTED_BYTES_FORMAT,
18
+ },
19
+ })));
20
+ }
21
+ //# sourceMappingURL=blobContentMetadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blobContentMetadata.js","sourceRoot":"","sources":["blobContentMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,2BAA2B,EAC3B,4BAA4B,GAC7B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,4BAA4B,EAAE,MAAM,oBAAoB,CAAC;AAElE,MAAM,YAAY,GAAG,IAAI,WAAW,EAAE,CAAC;AACvC,MAAM,wCAAwC,GAC5C,wCAAwC,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,KAM7C;IACC,OAAO,aAAa,CAClB,YAAY,CAAC,MAAM,CACjB,4BAA4B,CAAC;QAC3B,MAAM,EAAE,wCAAwC;QAChD,OAAO,EAAE;YACP,OAAO,EAAE,4BAA4B;YACrC,UAAU,EAAE,YAAY;YACxB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,MAAM,EAAE,2BAA2B;SACpC;KACF,CAAC,CACH,CACF,CAAC;AACJ,CAAC","sourcesContent":["import {\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n} from \"./blobEnvelopeV2\";\nimport { toFingerprint } from \"./fingerprint\";\nimport { serializeKeyingCanonicalJson } from \"./keying/canonical\";\n\nconst TEXT_ENCODER = new TextEncoder();\nconst BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN =\n \"tearleads.blob.content-record-metadata\";\n\nexport async function blobContentMetadataHash(input: {\n blobId: string;\n byteLength: number;\n chunkCount: number;\n chunkSize: number;\n contentKeyEpoch: number;\n}): Promise<string> {\n return toFingerprint(\n TEXT_ENCODER.encode(\n serializeKeyingCanonicalJson({\n domain: BLOB_CONTENT_RECORD_METADATA_HASH_DOMAIN,\n payload: {\n version: BLOB_ENCRYPTED_BYTES_VERSION,\n recordKind: \"blob_bytes\",\n blobId: input.blobId,\n byteLength: input.byteLength,\n chunkCount: input.chunkCount,\n chunkSize: input.chunkSize,\n contentKeyEpoch: input.contentKeyEpoch,\n format: BLOB_ENCRYPTED_BYTES_FORMAT,\n },\n }),\n ),\n );\n}\n"]}
@@ -1,3 +1,4 @@
1
+ export { blobContentMetadataHash } from "./blobContentMetadata.js";
1
2
  export { BlobEnvelopeError } from "./blobEnvelopeError.js";
2
3
  export type { BlobEnvelopeHeaderRecord, BlobEnvelopeRecord, } from "./blobEnvelopeReader.js";
3
4
  export { MAX_BLOB_ENVELOPE_HEADER_BYTES, parseBlobEnvelopeV2, parseBlobEnvelopeV2Header, readBlobEnvelopeHeaderByteLength, } from "./blobEnvelopeReader.js";
@@ -1,3 +1,4 @@
1
+ export { blobContentMetadataHash } from "./blobContentMetadata.js";
1
2
  export { BlobEnvelopeError } from "./blobEnvelopeError.js";
2
3
  export { MAX_BLOB_ENVELOPE_HEADER_BYTES, parseBlobEnvelopeV2, parseBlobEnvelopeV2Header, readBlobEnvelopeHeaderByteLength, } from "./blobEnvelopeReader.js";
3
4
  export { BLOB_CHUNK_SIZE_BYTES, BLOB_ENCRYPTED_BYTES_FORMAT, BLOB_ENCRYPTED_BYTES_VERSION, BLOB_ENVELOPE_MAGIC_BYTES, BLOB_ENVELOPE_PREFIX_BYTES, blobChunkPlaintextByteLength, computeBlobChunkCount, computeBlobEncryptedByteLength, deriveBlobChunkIv, encodeBlobEnvelopeV2Header, joinBlobPartBytes, MAX_BLOB_CHUNK_COUNT, normalizeBlobChunkSize, } from "./blobEnvelopeV2.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAKxD,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,EACnB,yBAAyB,EACzB,gCAAgC,GACjC,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EACL,qBAAqB,EACrB,2BAA2B,EAC3B,4BAA4B,EAC5B,yBAAyB,EACzB,0BAA0B,EAC1B,4BAA4B,EAC5B,qBAAqB,EACrB,8BAA8B,EAC9B,iBAAiB,EACjB,0BAA0B,EAC1B,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EACL,oBAAoB,EACpB,yBAAyB,EACzB,yBAAyB,EACzB,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AAKrB,OAAO,EACL,uBAAuB,EACvB,wBAAwB,EACxB,gBAAgB,EAChB,cAAc,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,sCAAsC,CAAC;AAE5E,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,2BAA2B,EAC3B,2BAA2B,EAC3B,2BAA2B,EAC3B,qBAAqB,GACtB,MAAM,iCAAiC,CAAC;AAEzC,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EACL,oBAAoB,EACpB,0BAA0B,EAC1B,oBAAoB,EAGpB,iBAAiB,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,EACL,wBAAwB,EACxB,mCAAmC,EACnC,sCAAsC,EACtC,qCAAqC,EACrC,kCAAkC,EAClC,kCAAkC,EAClC,0BAA0B,EAE1B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACL,uBAAuB,GAExB,MAAM,qBAAqB,CAAC;AAuI7B,OAAO,EACL,qCAAqC,EACrC,oCAAoC,EACpC,8BAA8B,EAC9B,qCAAqC,EACrC,2BAA2B,EAC3B,iCAAiC,EACjC,oCAAoC,EACpC,kCAAkC,EAClC,gCAAgC,EAChC,gCAAgC,EAChC,+BAA+B,EAC/B,oCAAoC,EACpC,6BAA6B,EAC7B,+BAA+B,EAC/B,0BAA0B,EAC1B,sBAAsB,EACtB,yBAAyB,EACzB,6BAA6B,EAC7B,+BAA+B,EAC/B,mCAAmC,EACnC,oCAAoC,EACpC,+BAA+B,EAC/B,8BAA8B,EAC9B,6BAA6B,EAC7B,wCAAwC,EACxC,sCAAsC,EACtC,4BAA4B,EAC5B,mCAAmC,EACnC,mCAAmC,EACnC,0CAA0C,EAC1C,wCAAwC,EACxC,yCAAyC,EACzC,+BAA+B,EAC/B,mCAAmC,EACnC,oCAAoC,EACpC,uBAAuB,EACvB,2BAA2B,EAC3B,6BAA6B,EAC7B,sBAAsB,EACtB,mCAAmC,EACnC,gCAAgC,EAChC,oCAAoC,EACpC,kCAAkC,EAClC,gCAAgC,EAChC,+BAA+B,EAC/B,oBAAoB,EACpB,6BAA6B,EAC7B,kCAAkC,EAClC,mCAAmC,EACnC,wBAAwB,EACxB,6BAA6B,EAC7B,kCAAkC,EAClC,6BAA6B,EAC7B,sCAAsC,EACtC,oCAAoC,EACpC,0CAA0C,EAC1C,6BAA6B,EAC7B,wBAAwB,EACxB,wBAAwB,EACxB,yBAAyB,EACzB,uBAAuB,EACvB,uBAAuB,EACvB,8BAA8B,EAC9B,uBAAuB,EACvB,0BAA0B,EAC1B,mCAAmC,EACnC,6BAA6B,EAC7B,8BAA8B,EAC9B,mCAAmC,EACnC,mCAAmC,EACnC,2BAA2B,EAC3B,kCAAkC,EAClC,iCAAiC,EACjC,4BAA4B,EAC5B,sCAAsC,EACtC,gCAAgC,EAChC,qCAAqC,EACrC,uBAAuB,EACvB,kCAAkC,EAClC,uCAAuC,EACvC,yBAAyB,EACzB,+BAA+B,EAC/B,+BAA+B,EAC/B,mCAAmC,EACnC,wCAAwC,EACxC,6CAA6C,EAC7C,iCAAiC,EACjC,qCAAqC,EACrC,uBAAuB,EACvB,kCAAkC,EAClC,iDAAiD,EACjD,4BAA4B,EAC5B,eAAe,EACf,wBAAwB,EACxB,eAAe,EACf,4BAA4B,EAC5B,mCAAmC,EACnC,oBAAoB,EACpB,mCAAmC,EACnC,4BAA4B,EAC5B,4BAA4B,EAC5B,2BAA2B,EAC3B,6BAA6B,EAC7B,8BAA8B,EAC9B,uBAAuB,EACvB,yBAAyB,EACzB,6BAA6B,EAC7B,6BAA6B,EAC7B,wBAAwB,EACxB,6BAA6B,EAC7B,2BAA2B,EAC3B,yCAAyC,EACzC,+BAA+B,EAC/B,4BAA4B,EAC5B,oCAAoC,EACpC,qCAAqC,EACrC,sCAAsC,EACtC,6BAA6B,EAC7B,uBAAuB,EACvB,gCAAgC,EAChC,kCAAkC,EAClC,gCAAgC,EAChC,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,2CAA2C,EAC3C,mCAAmC,EACnC,wCAAwC,EAExC,0CAA0C,GAC3C,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,kCAAkC,EAClC,iCAAiC,GAClC,MAAM,4BAA4B,CAAC;AACpC,OAAO,EACL,mCAAmC,EACnC,sCAAsC,EACtC,sCAAsC,GACvC,MAAM,4BAA4B,CAAC;AAEpC,OAAO,EACL,8BAA8B,EAC9B,mCAAmC,GACpC,MAAM,kCAAkC,CAAC;AAiB1C,OAAO,EACL,+BAA+B,EAC/B,8BAA8B,EAC9B,8BAA8B,EAC9B,yBAAyB,EACzB,0CAA0C,EAC1C,gCAAgC,EAChC,+BAA+B,EAC/B,yBAAyB,EACzB,mCAAmC,EACnC,8BAA8B,EAC9B,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,wBAAwB,EAExB,qBAAqB,EAErB,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,yBAAyB,EACzB,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AACtC,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,cAAc,EACd,cAAc,GACf,MAAM,aAAa,CAAC","sourcesContent":["export { BlobEnvelopeError } from \"./blobEnvelopeError\";\nexport type {\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n} from \"./blobEnvelopeReader\";\nexport {\n MAX_BLOB_ENVELOPE_HEADER_BYTES,\n parseBlobEnvelopeV2,\n parseBlobEnvelopeV2Header,\n readBlobEnvelopeHeaderByteLength,\n} from \"./blobEnvelopeReader\";\nexport type { BlobEnvelopeV2Header } from \"./blobEnvelopeV2\";\nexport {\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n BLOB_ENVELOPE_MAGIC_BYTES,\n BLOB_ENVELOPE_PREFIX_BYTES,\n blobChunkPlaintextByteLength,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n joinBlobPartBytes,\n MAX_BLOB_CHUNK_COUNT,\n normalizeBlobChunkSize,\n} from \"./blobEnvelopeV2\";\nexport { compareCanonicalStrings } from \"./canonicalOrdering\";\nexport {\n AUTH_CHALLENGE_BYTES,\n AUTH_CHALLENGE_HEX_LENGTH,\n authChallengeSigningBytes,\n CHALLENGE_TTL_SECONDS,\n canonicalAuthOrigin,\n generateChallenge,\n} from \"./challenge\";\nexport type {\n ContentKeyEnvelopeKind,\n ContentKeyWrapBinding,\n} from \"./contentKeyEnvelope\";\nexport {\n ContentKeyEnvelopeError,\n decodeContentKeyEnvelope,\n unwrapContentKey,\n wrapContentKey,\n} from \"./contentKeyEnvelope\";\nexport { decryptAsRecipient } from \"./encapsulation/decryptAsRecipient\";\nexport { encryptForRecipients } from \"./encapsulation/encryptForRecipients\";\nexport type { EncapsulationKeyPair } from \"./encapsulation/generateKeyPair\";\nexport {\n generateKemKeyPair,\n generateKemSeedAndKeyPair,\n ML_KEM1024_CIPHERTEXT_BYTES,\n ML_KEM1024_PUBLIC_KEY_BYTES,\n ML_KEM1024_SECRET_KEY_BYTES,\n ML_KEM1024_SEED_BYTES,\n} from \"./encapsulation/generateKeyPair\";\nexport type { EncryptedEnvelope, RecipientEntry } from \"./encapsulation/types\";\nexport { unwrapDek } from \"./encapsulation/unwrapDek\";\nexport { wrapDekForRecipients } from \"./encapsulation/wrapDek\";\nexport { toFingerprint } from \"./fingerprint\";\nexport {\n decryptGroupMetadata,\n encodeBuiltinGroupMetadata,\n encryptGroupMetadata,\n type GroupMetadata,\n type GroupMetadataKey,\n readGroupMetadata,\n} from \"./groupMetadata\";\nexport { bytesToHex, hexToBytes } from \"./hex\";\nexport {\n createIdentitySeedPhrase,\n createIdentitySeedPhraseFromEntropy,\n generateIdentityKeyPairsFromSeedPhrase,\n IDENTITY_SEED_PHRASE_DERIVATION_SUITE,\n IDENTITY_SEED_PHRASE_ENTROPY_BYTES,\n IDENTITY_SEED_PHRASE_STRENGTH_BITS,\n IDENTITY_SEED_PHRASE_WORDS,\n type IdentitySeedPhraseKeyPairs,\n normalizeIdentitySeedPhrase,\n validateIdentitySeedPhrase,\n} from \"./identitySeedPhrase\";\nexport {\n createIncrementalSha256,\n type IncrementalSha256,\n} from \"./incrementalSha256\";\nexport type {\n AccessEvent,\n AccessEventType,\n AccessManifest,\n AccessManifestCheckpoint,\n AccessManifestTransparencyLeaf,\n AccessObjectKind,\n AnyVerifiedAccessManifest,\n AnyVerifiedPrincipalPolicy,\n AttachmentAccessEventBody,\n AttachmentBindAccessEventBody,\n AttachmentDetachAccessEventBody,\n BlobAccessManifest,\n BlobContentKeyTarget,\n ContainerAccessEventBody,\n ContainerAccessKeyState,\n ContainerAccessLevel,\n ContainerAccessManifest,\n ContainerAccessManifestState,\n ContainerAccessStructural,\n ContainerCreateAccessEventBody,\n ContainerDirectGrant,\n ContainerGrantAccessEventBody,\n ContainerGrantPrincipalHead,\n ContainerGrantSubjectType,\n ContainerKekKeyring,\n ContainerKekKeyringEntry,\n ContainerKekPredecessorBridge,\n ContainerKekRecipientTarget,\n ContainerKekTarget,\n ContainerKeyEpoch,\n ContainerKeyWrap,\n ContainerMoveAccessEventBody,\n ContainerReciteAccessEventBody,\n ContainerRekeyAccessEventBody,\n ContainerRevokeAccessEventBody,\n ContainerUserRecipientKey,\n ContentObjectKind,\n ContentRecordEncryptionSuite,\n ContentRecordNonceDomain,\n DeriveBlobKekTargetsInput,\n DeriveContainerKekRecipientTargetsInput,\n DeriveDocumentKekTargetsInput,\n DocumentAccessEventBody,\n DocumentContentKeyTarget,\n DocumentContentRecordMetadataInput,\n DocumentLinkAccessEventBody,\n DocumentLinkSetManifestState,\n DocumentLinkSetStructural,\n DocumentPurgeAccessEventBody,\n DocumentUnlinkAccessEventBody,\n IdentityStateCheckpoint,\n IdentityStateHead,\n IdentityStateTransparencyLeaf,\n KekRecipientKind,\n KeyingCanonicalJson,\n KeyingHashDomain,\n KeyingLocalCheckpointStore,\n KeyingVerificationCode,\n KeyingVerificationResult,\n ManagedPrincipalKind,\n PrincipalHistoryIndexNode,\n PrincipalPolicyAuthorization,\n PrincipalPolicyBundle,\n PrincipalPolicyCheckpoint,\n PrincipalPolicyCurrent,\n PrincipalPolicyExternalAuthority,\n PrincipalPolicyExternalAuthorityState,\n PrincipalPolicyHistoryInput,\n PrincipalPolicyHistoryPage,\n PrincipalPolicyHistoryProgressOptions,\n PrincipalPolicyHistoryReferenceProof,\n PrincipalPolicyHistoryVerifier,\n PrincipalPolicyMemberEnvelopes,\n PrincipalPolicyPayload,\n PrincipalPolicySignedState,\n PrincipalPolicySignerPublicKey,\n PrincipalPolicySnapshot,\n PrincipalPolicyStateChainEntry,\n PrincipalPolicyTransitionMismatch,\n PrincipalPolicyTransitionMismatchCode,\n PrincipalPolicyTransparencyLeaf,\n ReferencedPrincipalHead,\n SignedTransparencyTreeHead,\n TransparencyConsistencyProof,\n TransparencyInclusionProof,\n TransparencyLeaf,\n TransparencyLeafKind,\n TransparencyTreeCheckpoint,\n UnsignedAccessEvent,\n UnsignedTransparencyTreeHead,\n UnsignedWriteHeader,\n VerifiedAccessEvent,\n VerifiedAccessManifest,\n VerifiedAccessManifestCheckpointEvidence,\n VerifiedAccessManifestSnapshot,\n VerifiedAttachmentBinding,\n VerifiedAttachmentDetach,\n VerifiedBlobKekTargets,\n VerifiedContainerAccessManifest,\n VerifiedContainerKekState,\n VerifiedContainerParentEdge,\n VerifiedDocumentKekTargets,\n VerifiedDocumentLinkSetManifest,\n VerifiedDocumentLinkSetSnapshot,\n VerifiedDocumentLinkSetStateEvidence,\n VerifiedIdentityState,\n VerifiedPrincipalPolicy,\n VerifiedPrincipalPolicyCurrent,\n VerifiedPrincipalPolicyHistory,\n VerifiedPrincipalPolicySelection,\n VerifiedPrincipalPolicySnapshot,\n VerifiedTransparencyProof,\n VerifiedTransparencyTreeHead,\n VerifiedWriteHeader,\n VerifyAccessEventInput,\n VerifyAccessManifestInput,\n VerifyAttachmentBindingEventInput,\n VerifyAttachmentDetachEventInput,\n VerifyContainerAccessManifestInput,\n VerifyContainerKekStateInput,\n VerifyContainerParentEdgeInput,\n VerifyDocumentLinkSetManifestInput,\n VerifyDocumentPurgeEventInput,\n VerifyIdentityStateCheckpointInput,\n VerifyPrincipalPolicyBundleInput,\n VerifyPrincipalPolicySnapshotInput,\n VerifySignedTransparencyTreeHeadInput,\n VerifyTransparencyConsistencyProofInput,\n VerifyTransparencyInclusionProofInput,\n VerifyTransparencyProofInput,\n VerifyWriteHeaderInput,\n WriteHeader,\n} from \"./keying\";\nexport {\n ACCESS_MANIFEST_VERIFICATION_REVISION,\n accessManifestCheckpointFromManifest,\n accessManifestTransparencyLeaf,\n assertSealedContainerKekKeyringLength,\n BLOB_CONTENT_KEY_WRAP_SUITE,\n CONTAINER_KEK_KEYRING_ENTRY_BYTES,\n CONTAINER_KEK_KEYRING_FORMAT_VERSION,\n CONTAINER_KEK_KEYRING_HEADER_BYTES,\n CONTAINER_KEK_KEYRING_SEAL_SUITE,\n CONTAINER_KEK_MATERIAL_ID_PREFIX,\n CONTAINER_KEK_PARENT_WRAP_SUITE,\n CONTAINER_KEK_PREDECESSOR_WRAP_SUITE,\n CONTAINER_KEK_USER_WRAP_SUITE,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeAccessEventBodyHash,\n computeAccessEventHash,\n computeAccessManifestHash,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContainerAccessKeyTargetHash,\n computeContainerAccessStructuralHash,\n computeContainerDirectGrantRoot,\n computeContainerKekKeyringHash,\n computeContainerKekMaterialId,\n computeContainerKekPredecessorBridgeHash,\n computeContainerKekRecipientTargetHash,\n computeContainerKeyEpochHash,\n computeContentRecordNonceDomainHash,\n computeDocumentContentKeyTargetHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n computeDocumentContentRecordPlaintextHash,\n computeDocumentLinkSetGrantRoot,\n computeDocumentLinkSetKeyTargetHash,\n computeDocumentLinkSetStructuralHash,\n computeKeyingDomainHash,\n computeTransparencyLeafHash,\n computeTransparencyMerkleRoot,\n computeWriteHeaderHash,\n createContainerKekPredecessorBridge,\n createPrincipalHistoryIndexProof,\n createPrincipalPolicyHistoryVerifier,\n createTransparencyConsistencyProof,\n createTransparencyInclusionProof,\n DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n deriveBlobKekTargets,\n deriveContainerAccessManifest,\n deriveContainerKekRecipientTargets,\n deriveContainerKekWrappingPublicKey,\n deriveDocumentKekTargets,\n deriveDocumentLinkSetManifest,\n derivePrincipalRecipientKeyEpochId,\n documentContentRecordMetadata,\n expectedSealedContainerKekKeyringBytes,\n getPrincipalPolicyTransitionMismatch,\n getPrincipalPolicyTransitionMismatchReason,\n identityStateTransparencyLeaf,\n isContainerKekMaterialId,\n isKeyingVerificationCode,\n KEYING_VERIFICATION_CODES,\n KeyingVerificationError,\n MAX_CONTAINER_KEY_EPOCH,\n MAX_CONTAINER_RECITATION_EPOCH,\n makeVerifiedAccessEvent,\n makeVerifiedBlobKekTargets,\n makeVerifiedContainerAccessManifest,\n makeVerifiedContainerKekState,\n makeVerifiedDocumentKekTargets,\n makeVerifiedDocumentLinkSetManifest,\n makeVerifiedDocumentLinkSetSnapshot,\n makeVerifiedPrincipalPolicy,\n normalizeAttachmentAccessEventBody,\n normalizeContainerAccessEventBody,\n normalizeContainerKekKeyring,\n normalizeContainerKekPredecessorBridge,\n normalizeDocumentAccessEventBody,\n normalizeDocumentPurgeAccessEventBody,\n openContainerKekKeyring,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n PRINCIPAL_HISTORY_VERIFICATION_REVISION,\n principalHistoryIndexLeaf,\n principalPolicyMatchesReference,\n principalPolicyTransparencyLeaf,\n resolveContainerPathUserAccessLevel,\n resolveContainerStatePathUserAccessLevel,\n resolveHistoricalContainerPathUserAccessLevel,\n resolvePrincipalHistoryIndexProof,\n restorePrincipalPolicyHistoryVerifier,\n sealContainerKekKeyring,\n selectPrincipalPolicyAuthorization,\n selectPrincipalPolicyCurrentPredecessorReferences,\n serializeKeyingCanonicalJson,\n signAccessEvent,\n signTransparencyTreeHead,\n signWriteHeader,\n unwrapContainerKekParentWrap,\n unwrapContainerKekPredecessorBridge,\n verifyAccessManifest,\n verifyAccessManifestLocalCheckpoint,\n verifyAccessManifestSnapshot,\n verifyAttachmentBindingEvent,\n verifyAttachmentDetachEvent,\n verifyContainerAccessManifest,\n verifyContainerKekKeyringEntry,\n verifyContainerKekState,\n verifyContainerParentEdge,\n verifyDocumentLinkSetManifest,\n verifyDocumentLinkSetSnapshot,\n verifyDocumentPurgeEvent,\n verifyIdentityStateCheckpoint,\n verifyPrincipalPolicyBundle,\n verifyPrincipalPolicyBundleAgainstHistory,\n verifyPrincipalPolicyCheckpoint,\n verifyPrincipalPolicyCurrent,\n verifyPrincipalPolicyCurrentMutation,\n verifyPrincipalPolicyCurrentSuccessor,\n verifyPrincipalPolicyHistoryReferences,\n verifyPrincipalPolicySnapshot,\n verifySignedAccessEvent,\n verifySignedTransparencyTreeHead,\n verifyTransparencyConsistencyProof,\n verifyTransparencyInclusionProof,\n verifyTransparencyProof,\n verifyWriteHeader,\n} from \"./keying\";\nexport {\n organizationReplacementAuthorizationPayload,\n organizationReplacementSigningBytes,\n signOrganizationReplacementAuthorization,\n type VerifiedOrganizationReplacementAuthorization,\n verifyOrganizationReplacementAuthorization,\n} from \"./organizationReplacement\";\nexport {\n computePrincipalContainerGrantRoot,\n normalizePrincipalContainerGrants,\n} from \"./principalContainerGrants\";\nexport {\n computePrincipalMemberEnvelopesRoot,\n normalizePrincipalStateMemberEnvelopes,\n PrincipalMemberEnvelopeValidationError,\n} from \"./principalMemberEnvelopes\";\nexport type { PrincipalPolicyValidationErrorCode } from \"./principalPolicyValidationError\";\nexport {\n PrincipalPolicyValidationError,\n throwPrincipalPolicyValidationError,\n} from \"./principalPolicyValidationError\";\nexport type {\n ManagedRecipientPrincipalType,\n PrincipalContainerGrant,\n PrincipalContainerGrantAccessLevel,\n PrincipalProjectionMember,\n PrincipalProjectionRole,\n PrincipalStateExternalAuthority,\n PrincipalStateHeaderInput,\n PrincipalStateMember,\n PrincipalStateMemberEnvelope,\n PrincipalStateMembershipMode,\n PrincipalStatePayloadCipherSuite,\n PrincipalStateSigningInput,\n SignedPrincipalState,\n UnsignedPrincipalState,\n} from \"./principalState\";\nexport {\n buildPrincipalStateSigningInput,\n computePrincipalMembershipRoot,\n computePrincipalProjectionRoot,\n computePrincipalStateHash,\n computePrincipalStatePayloadCiphertextHash,\n derivePrincipalProjectionMembers,\n isManagedRecipientPrincipalType,\n isPrincipalProjectionRole,\n normalizePrincipalProjectionMembers,\n normalizePrincipalStateMembers,\n serializeUnsignedPrincipalState,\n signPrincipalState,\n} from \"./principalState\";\nexport {\n verifySignedPrincipalState,\n verifySignedPrincipalStateResult,\n} from \"./principalStateVerification\";\nexport {\n captureProjectionHistory,\n type HistoryProjection,\n omitProjectionHistory,\n type RetainedProjectionHistory,\n restoreProjectionHistory,\n} from \"./projectionHistory\";\nexport type { SigningKeyPair } from \"./signing/generateKeyPair\";\nexport {\n generateSigningKeyPair,\n generateSigningSeedAndKeyPair,\n ML_DSA87_PUBLIC_KEY_BYTES,\n ML_DSA87_SECRET_KEY_BYTES,\n ML_DSA87_SIGNATURE_BYTES,\n} from \"./signing/generateKeyPair\";\nexport { sign } from \"./signing/sign\";\nexport { verify } from \"./signing/verify\";\nexport type { SymmetricCiphertext } from \"./symmetric\";\nexport {\n AES_256_KEY_BYTES,\n AES_GCM_IV_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n createAesGcmIv,\n decryptWithDek,\n encryptWithDek,\n} from \"./symmetric\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"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","sourcesContent":["export { blobContentMetadataHash } from \"./blobContentMetadata\";\nexport { BlobEnvelopeError } from \"./blobEnvelopeError\";\nexport type {\n BlobEnvelopeHeaderRecord,\n BlobEnvelopeRecord,\n} from \"./blobEnvelopeReader\";\nexport {\n MAX_BLOB_ENVELOPE_HEADER_BYTES,\n parseBlobEnvelopeV2,\n parseBlobEnvelopeV2Header,\n readBlobEnvelopeHeaderByteLength,\n} from \"./blobEnvelopeReader\";\nexport type { BlobEnvelopeV2Header } from \"./blobEnvelopeV2\";\nexport {\n BLOB_CHUNK_SIZE_BYTES,\n BLOB_ENCRYPTED_BYTES_FORMAT,\n BLOB_ENCRYPTED_BYTES_VERSION,\n BLOB_ENVELOPE_MAGIC_BYTES,\n BLOB_ENVELOPE_PREFIX_BYTES,\n blobChunkPlaintextByteLength,\n computeBlobChunkCount,\n computeBlobEncryptedByteLength,\n deriveBlobChunkIv,\n encodeBlobEnvelopeV2Header,\n joinBlobPartBytes,\n MAX_BLOB_CHUNK_COUNT,\n normalizeBlobChunkSize,\n} from \"./blobEnvelopeV2\";\nexport { compareCanonicalStrings } from \"./canonicalOrdering\";\nexport {\n AUTH_CHALLENGE_BYTES,\n AUTH_CHALLENGE_HEX_LENGTH,\n authChallengeSigningBytes,\n CHALLENGE_TTL_SECONDS,\n canonicalAuthOrigin,\n generateChallenge,\n} from \"./challenge\";\nexport type {\n ContentKeyEnvelopeKind,\n ContentKeyWrapBinding,\n} from \"./contentKeyEnvelope\";\nexport {\n ContentKeyEnvelopeError,\n decodeContentKeyEnvelope,\n unwrapContentKey,\n wrapContentKey,\n} from \"./contentKeyEnvelope\";\nexport { decryptAsRecipient } from \"./encapsulation/decryptAsRecipient\";\nexport { encryptForRecipients } from \"./encapsulation/encryptForRecipients\";\nexport type { EncapsulationKeyPair } from \"./encapsulation/generateKeyPair\";\nexport {\n generateKemKeyPair,\n generateKemSeedAndKeyPair,\n ML_KEM1024_CIPHERTEXT_BYTES,\n ML_KEM1024_PUBLIC_KEY_BYTES,\n ML_KEM1024_SECRET_KEY_BYTES,\n ML_KEM1024_SEED_BYTES,\n} from \"./encapsulation/generateKeyPair\";\nexport type { EncryptedEnvelope, RecipientEntry } from \"./encapsulation/types\";\nexport { unwrapDek } from \"./encapsulation/unwrapDek\";\nexport { wrapDekForRecipients } from \"./encapsulation/wrapDek\";\nexport { toFingerprint } from \"./fingerprint\";\nexport {\n decryptGroupMetadata,\n encodeBuiltinGroupMetadata,\n encryptGroupMetadata,\n type GroupMetadata,\n type GroupMetadataKey,\n readGroupMetadata,\n} from \"./groupMetadata\";\nexport { bytesToHex, hexToBytes } from \"./hex\";\nexport {\n createIdentitySeedPhrase,\n createIdentitySeedPhraseFromEntropy,\n generateIdentityKeyPairsFromSeedPhrase,\n IDENTITY_SEED_PHRASE_DERIVATION_SUITE,\n IDENTITY_SEED_PHRASE_ENTROPY_BYTES,\n IDENTITY_SEED_PHRASE_STRENGTH_BITS,\n IDENTITY_SEED_PHRASE_WORDS,\n type IdentitySeedPhraseKeyPairs,\n normalizeIdentitySeedPhrase,\n validateIdentitySeedPhrase,\n} from \"./identitySeedPhrase\";\nexport {\n createIncrementalSha256,\n type IncrementalSha256,\n} from \"./incrementalSha256\";\nexport type {\n AccessEvent,\n AccessEventType,\n AccessManifest,\n AccessManifestCheckpoint,\n AccessManifestTransparencyLeaf,\n AccessObjectKind,\n AnyVerifiedAccessManifest,\n AnyVerifiedPrincipalPolicy,\n AttachmentAccessEventBody,\n AttachmentBindAccessEventBody,\n AttachmentDetachAccessEventBody,\n BlobAccessManifest,\n BlobContentKeyTarget,\n ContainerAccessEventBody,\n ContainerAccessKeyState,\n ContainerAccessLevel,\n ContainerAccessManifest,\n ContainerAccessManifestState,\n ContainerAccessStructural,\n ContainerCreateAccessEventBody,\n ContainerDirectGrant,\n ContainerGrantAccessEventBody,\n ContainerGrantPrincipalHead,\n ContainerGrantSubjectType,\n ContainerKekKeyring,\n ContainerKekKeyringEntry,\n ContainerKekPredecessorBridge,\n ContainerKekRecipientTarget,\n ContainerKekTarget,\n ContainerKeyEpoch,\n ContainerKeyWrap,\n ContainerMoveAccessEventBody,\n ContainerReciteAccessEventBody,\n ContainerRekeyAccessEventBody,\n ContainerRevokeAccessEventBody,\n ContainerUserRecipientKey,\n ContentObjectKind,\n ContentRecordEncryptionSuite,\n ContentRecordNonceDomain,\n DeriveBlobKekTargetsInput,\n DeriveContainerKekRecipientTargetsInput,\n DeriveDocumentKekTargetsInput,\n DocumentAccessEventBody,\n DocumentContentKeyTarget,\n DocumentContentRecordMetadataInput,\n DocumentLinkAccessEventBody,\n DocumentLinkSetManifestState,\n DocumentLinkSetStructural,\n DocumentPurgeAccessEventBody,\n DocumentUnlinkAccessEventBody,\n IdentityStateCheckpoint,\n IdentityStateHead,\n IdentityStateTransparencyLeaf,\n KekRecipientKind,\n KeyingCanonicalJson,\n KeyingHashDomain,\n KeyingLocalCheckpointStore,\n KeyingVerificationCode,\n KeyingVerificationResult,\n ManagedPrincipalKind,\n PrincipalHistoryIndexNode,\n PrincipalPolicyAuthorization,\n PrincipalPolicyBundle,\n PrincipalPolicyCheckpoint,\n PrincipalPolicyCurrent,\n PrincipalPolicyExternalAuthority,\n PrincipalPolicyExternalAuthorityState,\n PrincipalPolicyHistoryInput,\n PrincipalPolicyHistoryPage,\n PrincipalPolicyHistoryProgressOptions,\n PrincipalPolicyHistoryReferenceProof,\n PrincipalPolicyHistoryVerifier,\n PrincipalPolicyMemberEnvelopes,\n PrincipalPolicyPayload,\n PrincipalPolicySignedState,\n PrincipalPolicySignerPublicKey,\n PrincipalPolicySnapshot,\n PrincipalPolicyStateChainEntry,\n PrincipalPolicyTransitionMismatch,\n PrincipalPolicyTransitionMismatchCode,\n PrincipalPolicyTransparencyLeaf,\n ReferencedPrincipalHead,\n SignedTransparencyTreeHead,\n TransparencyConsistencyProof,\n TransparencyInclusionProof,\n TransparencyLeaf,\n TransparencyLeafKind,\n TransparencyTreeCheckpoint,\n UnsignedAccessEvent,\n UnsignedTransparencyTreeHead,\n UnsignedWriteHeader,\n VerifiedAccessEvent,\n VerifiedAccessManifest,\n VerifiedAccessManifestCheckpointEvidence,\n VerifiedAccessManifestSnapshot,\n VerifiedAttachmentBinding,\n VerifiedAttachmentDetach,\n VerifiedBlobKekTargets,\n VerifiedContainerAccessManifest,\n VerifiedContainerKekState,\n VerifiedContainerParentEdge,\n VerifiedDocumentKekTargets,\n VerifiedDocumentLinkSetManifest,\n VerifiedDocumentLinkSetSnapshot,\n VerifiedDocumentLinkSetStateEvidence,\n VerifiedIdentityState,\n VerifiedPrincipalPolicy,\n VerifiedPrincipalPolicyCurrent,\n VerifiedPrincipalPolicyHistory,\n VerifiedPrincipalPolicySelection,\n VerifiedPrincipalPolicySnapshot,\n VerifiedTransparencyProof,\n VerifiedTransparencyTreeHead,\n VerifiedWriteHeader,\n VerifyAccessEventInput,\n VerifyAccessManifestInput,\n VerifyAttachmentBindingEventInput,\n VerifyAttachmentDetachEventInput,\n VerifyContainerAccessManifestInput,\n VerifyContainerKekStateInput,\n VerifyContainerParentEdgeInput,\n VerifyDocumentLinkSetManifestInput,\n VerifyDocumentPurgeEventInput,\n VerifyIdentityStateCheckpointInput,\n VerifyPrincipalPolicyBundleInput,\n VerifyPrincipalPolicySnapshotInput,\n VerifySignedTransparencyTreeHeadInput,\n VerifyTransparencyConsistencyProofInput,\n VerifyTransparencyInclusionProofInput,\n VerifyTransparencyProofInput,\n VerifyWriteHeaderInput,\n WriteHeader,\n} from \"./keying\";\nexport {\n ACCESS_MANIFEST_VERIFICATION_REVISION,\n accessManifestCheckpointFromManifest,\n accessManifestTransparencyLeaf,\n assertSealedContainerKekKeyringLength,\n BLOB_CONTENT_KEY_WRAP_SUITE,\n CONTAINER_KEK_KEYRING_ENTRY_BYTES,\n CONTAINER_KEK_KEYRING_FORMAT_VERSION,\n CONTAINER_KEK_KEYRING_HEADER_BYTES,\n CONTAINER_KEK_KEYRING_SEAL_SUITE,\n CONTAINER_KEK_MATERIAL_ID_PREFIX,\n CONTAINER_KEK_PARENT_WRAP_SUITE,\n CONTAINER_KEK_PREDECESSOR_WRAP_SUITE,\n CONTAINER_KEK_USER_WRAP_SUITE,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeAccessEventBodyHash,\n computeAccessEventHash,\n computeAccessManifestHash,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContainerAccessKeyTargetHash,\n computeContainerAccessStructuralHash,\n computeContainerDirectGrantRoot,\n computeContainerKekKeyringHash,\n computeContainerKekMaterialId,\n computeContainerKekPredecessorBridgeHash,\n computeContainerKekRecipientTargetHash,\n computeContainerKeyEpochHash,\n computeContentRecordNonceDomainHash,\n computeDocumentContentKeyTargetHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n computeDocumentContentRecordPlaintextHash,\n computeDocumentLinkSetGrantRoot,\n computeDocumentLinkSetKeyTargetHash,\n computeDocumentLinkSetStructuralHash,\n computeKeyingDomainHash,\n computeTransparencyLeafHash,\n computeTransparencyMerkleRoot,\n computeWriteHeaderHash,\n createContainerKekPredecessorBridge,\n createPrincipalHistoryIndexProof,\n createPrincipalPolicyHistoryVerifier,\n createTransparencyConsistencyProof,\n createTransparencyInclusionProof,\n DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n deriveBlobKekTargets,\n deriveContainerAccessManifest,\n deriveContainerKekRecipientTargets,\n deriveContainerKekWrappingPublicKey,\n deriveDocumentKekTargets,\n deriveDocumentLinkSetManifest,\n derivePrincipalRecipientKeyEpochId,\n documentContentRecordMetadata,\n expectedSealedContainerKekKeyringBytes,\n getPrincipalPolicyTransitionMismatch,\n getPrincipalPolicyTransitionMismatchReason,\n identityStateTransparencyLeaf,\n isContainerKekMaterialId,\n isKeyingVerificationCode,\n KEYING_VERIFICATION_CODES,\n KeyingVerificationError,\n MAX_CONTAINER_KEY_EPOCH,\n MAX_CONTAINER_RECITATION_EPOCH,\n makeVerifiedAccessEvent,\n makeVerifiedBlobKekTargets,\n makeVerifiedContainerAccessManifest,\n makeVerifiedContainerKekState,\n makeVerifiedDocumentKekTargets,\n makeVerifiedDocumentLinkSetManifest,\n makeVerifiedDocumentLinkSetSnapshot,\n makeVerifiedPrincipalPolicy,\n normalizeAttachmentAccessEventBody,\n normalizeContainerAccessEventBody,\n normalizeContainerKekKeyring,\n normalizeContainerKekPredecessorBridge,\n normalizeDocumentAccessEventBody,\n normalizeDocumentPurgeAccessEventBody,\n openContainerKekKeyring,\n PRINCIPAL_HISTORY_PAGE_ENTRY_LIMIT,\n PRINCIPAL_HISTORY_VERIFICATION_REVISION,\n principalHistoryIndexLeaf,\n principalPolicyMatchesReference,\n principalPolicyTransparencyLeaf,\n resolveContainerPathUserAccessLevel,\n resolveContainerStatePathUserAccessLevel,\n resolveHistoricalContainerPathUserAccessLevel,\n resolvePrincipalHistoryIndexProof,\n restorePrincipalPolicyHistoryVerifier,\n sealContainerKekKeyring,\n selectPrincipalPolicyAuthorization,\n selectPrincipalPolicyCurrentPredecessorReferences,\n serializeKeyingCanonicalJson,\n signAccessEvent,\n signTransparencyTreeHead,\n signWriteHeader,\n unwrapContainerKekParentWrap,\n unwrapContainerKekPredecessorBridge,\n verifyAccessManifest,\n verifyAccessManifestLocalCheckpoint,\n verifyAccessManifestSnapshot,\n verifyAttachmentBindingEvent,\n verifyAttachmentDetachEvent,\n verifyContainerAccessManifest,\n verifyContainerKekKeyringEntry,\n verifyContainerKekState,\n verifyContainerParentEdge,\n verifyDocumentLinkSetManifest,\n verifyDocumentLinkSetSnapshot,\n verifyDocumentPurgeEvent,\n verifyIdentityStateCheckpoint,\n verifyPrincipalPolicyBundle,\n verifyPrincipalPolicyBundleAgainstHistory,\n verifyPrincipalPolicyCheckpoint,\n verifyPrincipalPolicyCurrent,\n verifyPrincipalPolicyCurrentMutation,\n verifyPrincipalPolicyCurrentSuccessor,\n verifyPrincipalPolicyHistoryReferences,\n verifyPrincipalPolicySnapshot,\n verifySignedAccessEvent,\n verifySignedTransparencyTreeHead,\n verifyTransparencyConsistencyProof,\n verifyTransparencyInclusionProof,\n verifyTransparencyProof,\n verifyWriteHeader,\n} from \"./keying\";\nexport {\n organizationReplacementAuthorizationPayload,\n organizationReplacementSigningBytes,\n signOrganizationReplacementAuthorization,\n type VerifiedOrganizationReplacementAuthorization,\n verifyOrganizationReplacementAuthorization,\n} from \"./organizationReplacement\";\nexport {\n computePrincipalContainerGrantRoot,\n normalizePrincipalContainerGrants,\n} from \"./principalContainerGrants\";\nexport {\n computePrincipalMemberEnvelopesRoot,\n normalizePrincipalStateMemberEnvelopes,\n PrincipalMemberEnvelopeValidationError,\n} from \"./principalMemberEnvelopes\";\nexport type { PrincipalPolicyValidationErrorCode } from \"./principalPolicyValidationError\";\nexport {\n PrincipalPolicyValidationError,\n throwPrincipalPolicyValidationError,\n} from \"./principalPolicyValidationError\";\nexport type {\n ManagedRecipientPrincipalType,\n PrincipalContainerGrant,\n PrincipalContainerGrantAccessLevel,\n PrincipalProjectionMember,\n PrincipalProjectionRole,\n PrincipalStateExternalAuthority,\n PrincipalStateHeaderInput,\n PrincipalStateMember,\n PrincipalStateMemberEnvelope,\n PrincipalStateMembershipMode,\n PrincipalStatePayloadCipherSuite,\n PrincipalStateSigningInput,\n SignedPrincipalState,\n UnsignedPrincipalState,\n} from \"./principalState\";\nexport {\n buildPrincipalStateSigningInput,\n computePrincipalMembershipRoot,\n computePrincipalProjectionRoot,\n computePrincipalStateHash,\n computePrincipalStatePayloadCiphertextHash,\n derivePrincipalProjectionMembers,\n isManagedRecipientPrincipalType,\n isPrincipalProjectionRole,\n normalizePrincipalProjectionMembers,\n normalizePrincipalStateMembers,\n serializeUnsignedPrincipalState,\n signPrincipalState,\n} from \"./principalState\";\nexport {\n verifySignedPrincipalState,\n verifySignedPrincipalStateResult,\n} from \"./principalStateVerification\";\nexport {\n captureProjectionHistory,\n type HistoryProjection,\n omitProjectionHistory,\n type RetainedProjectionHistory,\n restoreProjectionHistory,\n} from \"./projectionHistory\";\nexport type { SigningKeyPair } from \"./signing/generateKeyPair\";\nexport {\n generateSigningKeyPair,\n generateSigningSeedAndKeyPair,\n ML_DSA87_PUBLIC_KEY_BYTES,\n ML_DSA87_SECRET_KEY_BYTES,\n ML_DSA87_SIGNATURE_BYTES,\n} from \"./signing/generateKeyPair\";\nexport { sign } from \"./signing/sign\";\nexport { verify } from \"./signing/verify\";\nexport type { SymmetricCiphertext } from \"./symmetric\";\nexport {\n AES_256_KEY_BYTES,\n AES_GCM_IV_BYTES,\n AES_GCM_TAG_BYTES,\n assertAesGcmIv,\n createAesGcmIv,\n decryptWithDek,\n encryptWithDek,\n} from \"./symmetric\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tearleads/client-sdk",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "React-free Tearleads client runtime: local SQLite persistence, keys, sync, encrypted blobs, and domain workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -1 +1 @@
1
- {"version":3,"file":"attachmentPersistence.js","sourceRoot":"","sources":["attachmentPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deleteLocalDocumentAttachment,\n deletePendingDocumentAttachment,\n type LocalAttachmentRecord,\n type PendingAttachmentRecord,\n saveLocalDocumentAttachments,\n savePendingDocumentAttachment,\n} from \"../../../workflows/documents\";\nimport { withLocalAttachmentDetachState } from \"./attachmentDetachState\";\nimport { withGenerationGuardedMutation } from \"./persistence\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function deletePendingAttachment(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n // After a REFUSED discard the reset store keeps its rows, and a stale pass\n // racing that reset must not delete state the refusal deliberately\n // preserved.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n deletePendingDocumentAttachment({\n execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n }),\n );\n}\n\nexport async function deleteLocalAttachmentRecord(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n currentDoc: DocumentState | null = state.doc,\n) {\n await deleteLocalDocumentAttachment({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n });\n\n if (state.attachmentStorageKeyBySlotId[slotId] === storageKey) {\n removeLocalAttachmentSlot(state, slotId);\n }\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport async function saveLocalAttachmentRecord(\n state: DocumentStoreState,\n attachment: LocalAttachmentRecord,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n await saveLocalAttachmentRecords(\n state,\n [attachment],\n currentDoc,\n expectedGeneration,\n );\n}\n\nexport async function saveLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n if (attachments.length === 0) {\n return;\n }\n\n // These rows are upserts, and a stale writer racing a teardown must never\n // re-insert what the teardown just removed.\n const saved = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n (execSql) =>\n saveLocalDocumentAttachments({\n attachments: withLocalAttachmentDetachState(attachments, currentDoc),\n execSql,\n persistence: state.persistence,\n }),\n );\n if (\n !saved ||\n (expectedGeneration && !isSyncGenerationCurrent(state, expectedGeneration))\n ) {\n return;\n }\n\n installLocalAttachmentRecords(state, attachments, currentDoc);\n}\n\n/** Publish the held copy of each record's slot to the store's slot maps. */\nexport function assignLocalAttachmentSlots(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n): void {\n const entries = <T>(pick: (attachment: LocalAttachmentRecord) => T) =>\n Object.fromEntries(\n attachments.map((attachment) => [attachment.slotId, pick(attachment)]),\n );\n state.attachmentBlobIdBySlotId = {\n ...state.attachmentBlobIdBySlotId,\n ...entries((attachment) => attachment.blobId),\n };\n state.attachmentContentSha256BySlotId = {\n ...state.attachmentContentSha256BySlotId,\n ...entries((attachment) => attachment.contentSha256),\n };\n state.attachmentStorageKeyBySlotId = {\n ...state.attachmentStorageKeyBySlotId,\n ...entries((attachment) => attachment.storageKey),\n };\n}\n\n/** Forget the held copy of one slot without touching its durable row. */\nexport function removeLocalAttachmentSlot(\n state: DocumentStoreState,\n slotId: string,\n): void {\n const { [slotId]: _blobId, ...blobIds } = state.attachmentBlobIdBySlotId;\n const { [slotId]: _contentSha256, ...contentSha256s } =\n state.attachmentContentSha256BySlotId;\n const { [slotId]: _storageKey, ...storageKeys } =\n state.attachmentStorageKeyBySlotId;\n state.attachmentBlobIdBySlotId = blobIds;\n state.attachmentContentSha256BySlotId = contentSha256s;\n state.attachmentStorageKeyBySlotId = storageKeys;\n}\n\nexport function installLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null,\n) {\n assignLocalAttachmentSlots(state, attachments);\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport function upsertPendingAttachments(\n state: DocumentStoreState,\n nextPendingAttachments: ReadonlyArray<PendingAttachmentRecord>,\n) {\n const nextSlotIds = new Set(\n nextPendingAttachments.map((pendingAttachment) => pendingAttachment.slotId),\n );\n state.pendingAttachments = [\n ...state.pendingAttachments.filter(\n (pendingAttachment) => !nextSlotIds.has(pendingAttachment.slotId),\n ),\n ...nextPendingAttachments,\n ];\n}\n\n/**\n * Persist a pending attachment's upload-resume identity (blob id, content key,\n * IV, and — once staged — the multipart stage id/part size) so a later attempt\n * reuses it instead of orphaning the stage. The caller publishes the saved\n * identity on the live pending record only after this write succeeds.\n */\nexport async function savePendingAttachmentUpload(\n state: DocumentStoreState,\n pendingAttachment: PendingAttachmentRecord,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<void> {\n // This row is an upsert, and a stale resume racing a teardown must never\n // re-insert the pending row the teardown just removed.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n savePendingDocumentAttachment({\n attachment: pendingAttachment,\n execSql,\n persistence: state.persistence,\n }),\n );\n}\n"]}
1
+ {"version":3,"file":"attachmentPersistence.js","sourceRoot":"","sources":["attachmentPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n deleteLocalDocumentAttachment,\n deletePendingDocumentAttachment,\n type LocalAttachmentRecord,\n type PendingAttachmentRecord,\n saveLocalDocumentAttachments,\n savePendingDocumentAttachment,\n} from \"../../../workflows/documents\";\nimport { withLocalAttachmentDetachState } from \"./attachmentDetachState\";\nimport { withGenerationGuardedMutation } from \"./persistence\";\nimport {\n type DocumentState,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport {\n type DocumentStoreSyncGeneration,\n isDocumentStoreSyncGenerationCurrent as isSyncGenerationCurrent,\n} from \"./syncGeneration\";\n\nexport async function deletePendingAttachment(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n // After a REFUSED discard the reset store keeps its rows, and a stale pass\n // racing that reset must not delete state the refusal deliberately\n // preserved.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n deletePendingDocumentAttachment({\n execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n }),\n );\n}\n\nexport async function deleteLocalAttachmentRecord(\n state: DocumentStoreState,\n slotId: string,\n storageKey: string,\n currentDoc: DocumentState | null = state.doc,\n) {\n await deleteLocalDocumentAttachment({\n execSql: state.runtime.infra.execSql,\n localId: state.localId,\n persistence: state.persistence,\n slotId,\n storageKey,\n });\n\n if (state.attachmentStorageKeyBySlotId[slotId] === storageKey) {\n removeLocalAttachmentSlot(state, slotId);\n }\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport async function saveLocalAttachmentRecord(\n state: DocumentStoreState,\n attachment: LocalAttachmentRecord,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n await saveLocalAttachmentRecords(\n state,\n [attachment],\n currentDoc,\n expectedGeneration,\n );\n}\n\nexport async function saveLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null = state.doc,\n expectedGeneration?: DocumentStoreSyncGeneration,\n) {\n if (attachments.length === 0) {\n return;\n }\n\n // These rows are upserts, and a stale writer racing a teardown must never\n // re-insert what the teardown just removed.\n const saved = await withGenerationGuardedMutation(\n state,\n expectedGeneration,\n (execSql) =>\n saveLocalDocumentAttachments({\n attachments: withLocalAttachmentDetachState(attachments, currentDoc),\n execSql,\n persistence: state.persistence,\n }),\n );\n if (\n !saved ||\n (expectedGeneration && !isSyncGenerationCurrent(state, expectedGeneration))\n ) {\n return;\n }\n\n installLocalAttachmentRecords(state, attachments, currentDoc);\n}\n\ntype AttachmentSlot = Pick<\n LocalAttachmentRecord,\n \"slotId\" | \"blobId\" | \"contentSha256\" | \"storageKey\"\n>;\n\n/** Publish the held copy of each record's slot to the store's slot maps. */\nexport function assignLocalAttachmentSlots(\n state: DocumentStoreState,\n attachments: ReadonlyArray<AttachmentSlot>,\n): void {\n const entries = <T>(pick: (attachment: AttachmentSlot) => T) =>\n Object.fromEntries(\n attachments.map((attachment) => [attachment.slotId, pick(attachment)]),\n );\n state.attachmentBlobIdBySlotId = {\n ...state.attachmentBlobIdBySlotId,\n ...entries((attachment) => attachment.blobId),\n };\n state.attachmentContentSha256BySlotId = {\n ...state.attachmentContentSha256BySlotId,\n ...entries((attachment) => attachment.contentSha256),\n };\n state.attachmentStorageKeyBySlotId = {\n ...state.attachmentStorageKeyBySlotId,\n ...entries((attachment) => attachment.storageKey),\n };\n}\n\n/** Forget the held copy of one slot without touching its durable row. */\nexport function removeLocalAttachmentSlot(\n state: DocumentStoreState,\n slotId: string,\n): void {\n const { [slotId]: _blobId, ...blobIds } = state.attachmentBlobIdBySlotId;\n const { [slotId]: _contentSha256, ...contentSha256s } =\n state.attachmentContentSha256BySlotId;\n const { [slotId]: _storageKey, ...storageKeys } =\n state.attachmentStorageKeyBySlotId;\n state.attachmentBlobIdBySlotId = blobIds;\n state.attachmentContentSha256BySlotId = contentSha256s;\n state.attachmentStorageKeyBySlotId = storageKeys;\n}\n\nexport function installLocalAttachmentRecords(\n state: DocumentStoreState,\n attachments: ReadonlyArray<LocalAttachmentRecord>,\n currentDoc: DocumentState | null,\n) {\n assignLocalAttachmentSlots(state, attachments);\n\n if (currentDoc && currentDoc === state.doc) {\n setReadySnapshot(\n state,\n currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n }\n}\n\nexport function upsertPendingAttachments(\n state: DocumentStoreState,\n nextPendingAttachments: ReadonlyArray<PendingAttachmentRecord>,\n) {\n const nextSlotIds = new Set(\n nextPendingAttachments.map((pendingAttachment) => pendingAttachment.slotId),\n );\n state.pendingAttachments = [\n ...state.pendingAttachments.filter(\n (pendingAttachment) => !nextSlotIds.has(pendingAttachment.slotId),\n ),\n ...nextPendingAttachments,\n ];\n}\n\n/**\n * Persist a pending attachment's upload-resume identity (blob id, content key,\n * IV, and — once staged — the multipart stage id/part size) so a later attempt\n * reuses it instead of orphaning the stage. The caller publishes the saved\n * identity on the live pending record only after this write succeeds.\n */\nexport async function savePendingAttachmentUpload(\n state: DocumentStoreState,\n pendingAttachment: PendingAttachmentRecord,\n expectedGeneration?: DocumentStoreSyncGeneration,\n): Promise<void> {\n // This row is an upsert, and a stale resume racing a teardown must never\n // re-insert the pending row the teardown just removed.\n await withGenerationGuardedMutation(state, expectedGeneration, (execSql) =>\n savePendingDocumentAttachment({\n attachment: pendingAttachment,\n execSql,\n persistence: state.persistence,\n }),\n );\n}\n"]}
@@ -4,7 +4,7 @@ import { createPendingUpdateFields } from "../../../data/documents/documentSync.
4
4
  import { requestDocumentStoreSync } from "../registry.js";
5
5
  import { buildAttachmentIntent, buildPendingAttachments, } from "./attachmentIntents.js";
6
6
  import { persistStagedAttachmentMutation, restoreFailedAttachmentMutation, } from "./attachmentMutationPersistence.js";
7
- import { removeLocalAttachmentSlot } from "./attachmentPersistence.js";
7
+ import { assignLocalAttachmentSlots, removeLocalAttachmentSlot, } from "./attachmentPersistence.js";
8
8
  import { deleteUnreferencedStagedAttachmentBytes, installPendingAttachmentRows, stagePendingAttachments, } from "./attachmentStaging.js";
9
9
  import { captureAttachmentWriteGeneration, isAttachmentWriteGenerationCurrent, shouldAbortAttachmentPersistFollowup, } from "./attachmentWriteGeneration.js";
10
10
  import { ensureDocumentStoreReady } from "./initialization.js";
@@ -176,9 +176,20 @@ function installCommittedAttachmentRemoval(input) {
176
176
  const { slotId, state, storageKey } = input;
177
177
  if (!storageKey)
178
178
  return;
179
+ const pending = state.pendingAttachments.find((attachment) => attachment.slotId === slotId && attachment.storageKey === storageKey);
180
+ if (input.syncedAttachment && pending?.upload) {
181
+ assignLocalAttachmentSlots(state, [
182
+ {
183
+ slotId,
184
+ storageKey,
185
+ blobId: pending.upload.blobId,
186
+ contentSha256: pending.contentSha256,
187
+ },
188
+ ]);
189
+ }
179
190
  state.pendingAttachments = state.pendingAttachments.filter((attachment) => attachment.slotId !== slotId || attachment.storageKey !== storageKey);
180
191
  // The committed removal queued this key. A reset can leave another document
181
- // uploading from the same copy, and a pending-only slot leaves no marker.
192
+ // uploading from the same copy; a detached marker keeps it until sync.
182
193
  void runDocumentOrphanMaintenance(state);
183
194
  if (input.syncedAttachment ||
184
195
  state.attachmentStorageKeyBySlotId[slotId] !== storageKey) {
@@ -1 +1 @@
1
- {"version":3,"file":"attachments.js","sourceRoot":"","sources":["attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n addDocumentAttachments,\n getDocumentAttachments,\n removeDocumentAttachment,\n} from \"../../../data/documents/documentContent\";\nimport { createPendingUpdateFields } from \"../../../data/documents/documentSync\";\nimport type { PendingAttachmentRecord } from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n buildAttachmentIntent,\n buildPendingAttachments,\n} from \"./attachmentIntents\";\nimport {\n persistStagedAttachmentMutation,\n restoreFailedAttachmentMutation,\n} from \"./attachmentMutationPersistence\";\nimport { removeLocalAttachmentSlot } from \"./attachmentPersistence\";\nimport {\n deleteUnreferencedStagedAttachmentBytes,\n installPendingAttachmentRows,\n stagePendingAttachments,\n} from \"./attachmentStaging\";\nimport {\n type AttachmentWriteGeneration,\n captureAttachmentWriteGeneration,\n isAttachmentWriteGenerationCurrent,\n shouldAbortAttachmentPersistFollowup,\n} from \"./attachmentWriteGeneration\";\nimport { ensureDocumentStoreReady } from \"./initialization\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n advancePendingBaseVersion,\n pendingDeltaSinceBase,\n persistDocument,\n} from \"./persistence\";\nimport { reportDocumentStoreWriteFailure } from \"./reportWriteFailure\";\nimport {\n canAttachFiles,\n canWriteDocument,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport { captureDocumentStoreSyncGeneration } from \"./syncGeneration\";\n\n// Fixed literal: a reported message must never carry file names or slot ids.\nconst ATTACHMENT_FAILURE_MESSAGE = \"Documents: attachment mutation failed\";\n\nfunction logAttachedFiles(state: DocumentStoreState, count: number) {\n state.runtime.util.log(\n state.runtime.state.online && state.runtime.auth.isAuthenticated\n ? `Attached ${count} file${count === 1 ? \"\" : \"s\"} to document ${state.localId}.`\n : `Stored ${count} attachment${count === 1 ? \"\" : \"s\"} locally for document ${state.localId}.`,\n );\n}\n\nasync function persistAttachedFiles(\n state: DocumentStoreState,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const { nextAttachments, nextPendingAttachments } =\n await buildPendingAttachments(state.localId, files);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, nextAttachments);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n // Captured at delta time: the enqueued row proves coverage up to exactly\n // this version, so it is the frontier the persist below may publish.\n const coveredVersion = encodeVersionVector(currentDoc);\n\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files,\n generation,\n pendingAttachments: nextPendingAttachments,\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // This persist changes attachments, not prose. setReadySnapshot always\n // re-derives attachments from the doc, but preserve the text/structured\n // fields so an attach that overlaps an in-flight keystroke does not republish\n // a stale doc read over the live optimistic editor value.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n logAttachedFiles(state, files.length);\n requestDocumentStoreSync(state);\n}\n\nasync function persistSlotAttachmentFile(\n state: DocumentStoreState,\n slotId: string,\n file: DocumentAttachmentUpload,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: slot attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const replacementAttachment = await buildAttachmentIntent(slotId, file);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, [replacementAttachment]);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n\n const storageKey = `${state.localId}-${slotId}-${crypto.randomUUID()}`;\n const pendingAttachment: PendingAttachmentRecord = {\n byteLength: replacementAttachment.byteLength,\n contentSha256: replacementAttachment.contentSha256,\n localId: state.localId,\n mimeType: replacementAttachment.mimeType,\n name: replacementAttachment.name,\n slotId,\n storageKey,\n };\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files: [file],\n generation,\n pendingAttachments: [pendingAttachment],\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // a slot replacement overlapping a keystroke must not regress the editor.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);\n requestDocumentStoreSync(state);\n}\n\nfunction installCommittedAttachmentRemoval(input: {\n currentDoc: NonNullable<DocumentStoreState[\"doc\"]>;\n slotId: string;\n state: DocumentStoreState;\n storageKey: string | undefined;\n syncedAttachment: boolean;\n}): void {\n const { slotId, state, storageKey } = input;\n if (!storageKey) return;\n state.pendingAttachments = state.pendingAttachments.filter(\n (attachment) =>\n attachment.slotId !== slotId || attachment.storageKey !== storageKey,\n );\n // The committed removal queued this key. A reset can leave another document\n // uploading from the same copy, and a pending-only slot leaves no marker.\n void runDocumentOrphanMaintenance(state);\n if (\n input.syncedAttachment ||\n state.attachmentStorageKeyBySlotId[slotId] !== storageKey\n ) {\n return;\n }\n removeLocalAttachmentSlot(state, slotId);\n setReadySnapshot(\n state,\n input.currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n}\n\nasync function persistRemovedAttachment(\n state: DocumentStoreState,\n slotId: string,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n if (!currentDoc) {\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const existingAttachment = getDocumentAttachments(currentDoc).find(\n (attachment) => attachment.slotId === slotId,\n );\n if (!existingAttachment) {\n return;\n }\n\n // After a reset, a store opened cold holds the slot only as a pending upload.\n const storageKey =\n state.attachmentStorageKeyBySlotId[slotId] ??\n state.pendingAttachments.find((attachment) => attachment.slotId === slotId)\n ?.storageKey;\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n removeDocumentAttachment(currentDoc, slotId);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n const pendingUpdate = createPendingUpdateFields(attachmentUpdate);\n const syncedAttachment = Boolean(state.record?.documentId);\n\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // removing an attachment while typing must not regress the editor.\n let persisted: Awaited<ReturnType<typeof persistDocument>>;\n try {\n persisted = await persistDocument(\n state,\n currentDoc,\n { snapshotEndVersion: coveredVersion },\n {\n ...(storageKey\n ? {\n attachmentRemoval: {\n mode: syncedAttachment\n ? (\"detach\" as const)\n : (\"delete\" as const),\n slotId,\n storageKey,\n },\n }\n : {}),\n ...(pendingUpdate ? { pendingUpdate } : {}),\n preserveSnapshotStructuredFields: true,\n preserveSnapshotText: true,\n },\n generation,\n );\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n // See persistAttachedFiles: only durable same-identity work re-arms sync.\n return;\n }\n installCommittedAttachmentRemoval({\n currentDoc,\n slotId,\n state,\n storageKey,\n syncedAttachment,\n });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(\n `Removed attachment ${existingAttachment.name} from document ${state.localId}.`,\n );\n requestDocumentStoreSync(state);\n}\n\nexport async function attachFilesToDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n) {\n if (files.length === 0) {\n return;\n }\n\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistAttachedFiles(state, files, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function removeAttachmentFromDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistRemovedAttachment(state, slotId, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function replaceAttachmentInDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n file: DocumentAttachmentUpload,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () =>\n persistSlotAttachmentFile(state, slotId, file, writeGeneration),\n )\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n"]}
1
+ {"version":3,"file":"attachments.js","sourceRoot":"","sources":["attachments.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n encodeVersionVector,\n exportFullHistorySnapshot,\n} from \"@tearleads/loro\";\nimport {\n addDocumentAttachments,\n getDocumentAttachments,\n removeDocumentAttachment,\n} from \"../../../data/documents/documentContent\";\nimport { createPendingUpdateFields } from \"../../../data/documents/documentSync\";\nimport type { PendingAttachmentRecord } from \"../../../workflows/documents\";\nimport { requestDocumentStoreSync } from \"../registry\";\nimport type { DocumentAttachmentUpload } from \"../types\";\nimport {\n buildAttachmentIntent,\n buildPendingAttachments,\n} from \"./attachmentIntents\";\nimport {\n persistStagedAttachmentMutation,\n restoreFailedAttachmentMutation,\n} from \"./attachmentMutationPersistence\";\nimport {\n assignLocalAttachmentSlots,\n removeLocalAttachmentSlot,\n} from \"./attachmentPersistence\";\nimport {\n deleteUnreferencedStagedAttachmentBytes,\n installPendingAttachmentRows,\n stagePendingAttachments,\n} from \"./attachmentStaging\";\nimport {\n type AttachmentWriteGeneration,\n captureAttachmentWriteGeneration,\n isAttachmentWriteGenerationCurrent,\n shouldAbortAttachmentPersistFollowup,\n} from \"./attachmentWriteGeneration\";\nimport { ensureDocumentStoreReady } from \"./initialization\";\nimport { runDocumentOrphanMaintenance } from \"./orphanMaintenance\";\nimport {\n advancePendingBaseVersion,\n pendingDeltaSinceBase,\n persistDocument,\n} from \"./persistence\";\nimport { reportDocumentStoreWriteFailure } from \"./reportWriteFailure\";\nimport {\n canAttachFiles,\n canWriteDocument,\n type DocumentStoreState,\n setReadySnapshot,\n} from \"./state\";\nimport { captureDocumentStoreSyncGeneration } from \"./syncGeneration\";\n\n// Fixed literal: a reported message must never carry file names or slot ids.\nconst ATTACHMENT_FAILURE_MESSAGE = \"Documents: attachment mutation failed\";\n\nfunction logAttachedFiles(state: DocumentStoreState, count: number) {\n state.runtime.util.log(\n state.runtime.state.online && state.runtime.auth.isAuthenticated\n ? `Attached ${count} file${count === 1 ? \"\" : \"s\"} to document ${state.localId}.`\n : `Stored ${count} attachment${count === 1 ? \"\" : \"s\"} locally for document ${state.localId}.`,\n );\n}\n\nasync function persistAttachedFiles(\n state: DocumentStoreState,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const { nextAttachments, nextPendingAttachments } =\n await buildPendingAttachments(state.localId, files);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, nextAttachments);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n // Captured at delta time: the enqueued row proves coverage up to exactly\n // this version, so it is the frontier the persist below may publish.\n const coveredVersion = encodeVersionVector(currentDoc);\n\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files,\n generation,\n pendingAttachments: nextPendingAttachments,\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // This persist changes attachments, not prose. setReadySnapshot always\n // re-derives attachments from the doc, but preserve the text/structured\n // fields so an attach that overlaps an in-flight keystroke does not republish\n // a stale doc read over the live optimistic editor value.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n logAttachedFiles(state, files.length);\n requestDocumentStoreSync(state);\n}\n\nasync function persistSlotAttachmentFile(\n state: DocumentStoreState,\n slotId: string,\n file: DocumentAttachmentUpload,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n\n if (!currentDoc || !canAttachFiles(state)) {\n state.runtime.util.log(\n \"Documents: slot attachments require a local key package.\",\n );\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const replacementAttachment = await buildAttachmentIntent(slotId, file);\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n addDocumentAttachments(currentDoc, [replacementAttachment]);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n\n const storageKey = `${state.localId}-${slotId}-${crypto.randomUUID()}`;\n const pendingAttachment: PendingAttachmentRecord = {\n byteLength: replacementAttachment.byteLength,\n contentSha256: replacementAttachment.contentSha256,\n localId: state.localId,\n mimeType: replacementAttachment.mimeType,\n name: replacementAttachment.name,\n slotId,\n storageKey,\n };\n let staged: Awaited<ReturnType<typeof stagePendingAttachments>>;\n try {\n staged = await stagePendingAttachments({\n files: [file],\n generation,\n pendingAttachments: [pendingAttachment],\n state,\n });\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (!staged) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n return;\n }\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // a slot replacement overlapping a keystroke must not regress the editor.\n const persisted = await persistStagedAttachmentMutation({\n attachmentUpdate,\n coveredVersion,\n currentDoc,\n generation,\n rollbackSnapshot,\n staged,\n state,\n });\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n await deleteUnreferencedStagedAttachmentBytes({\n generation,\n pendingAttachments: staged.pendingAttachments,\n state,\n });\n return;\n }\n installPendingAttachmentRows({ ...staged, state });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(`Queued attachment ${file.name} for slot ${slotId}.`);\n requestDocumentStoreSync(state);\n}\n\nfunction installCommittedAttachmentRemoval(input: {\n currentDoc: NonNullable<DocumentStoreState[\"doc\"]>;\n slotId: string;\n state: DocumentStoreState;\n storageKey: string | undefined;\n syncedAttachment: boolean;\n}): void {\n const { slotId, state, storageKey } = input;\n if (!storageKey) return;\n const pending = state.pendingAttachments.find(\n (attachment) =>\n attachment.slotId === slotId && attachment.storageKey === storageKey,\n );\n if (input.syncedAttachment && pending?.upload) {\n assignLocalAttachmentSlots(state, [\n {\n slotId,\n storageKey,\n blobId: pending.upload.blobId,\n contentSha256: pending.contentSha256,\n },\n ]);\n }\n state.pendingAttachments = state.pendingAttachments.filter(\n (attachment) =>\n attachment.slotId !== slotId || attachment.storageKey !== storageKey,\n );\n // The committed removal queued this key. A reset can leave another document\n // uploading from the same copy; a detached marker keeps it until sync.\n void runDocumentOrphanMaintenance(state);\n if (\n input.syncedAttachment ||\n state.attachmentStorageKeyBySlotId[slotId] !== storageKey\n ) {\n return;\n }\n removeLocalAttachmentSlot(state, slotId);\n setReadySnapshot(\n state,\n input.currentDoc,\n state.snapshot.syncing,\n state.snapshot.text,\n state.snapshot.structuredFields,\n );\n}\n\nasync function persistRemovedAttachment(\n state: DocumentStoreState,\n slotId: string,\n writeGeneration: AttachmentWriteGeneration,\n) {\n if (!isAttachmentWriteGenerationCurrent(state, writeGeneration)) return;\n const currentDoc = state.doc;\n if (!currentDoc) {\n return;\n }\n const generation = captureDocumentStoreSyncGeneration(state, currentDoc);\n if (!generation) {\n return;\n }\n\n const existingAttachment = getDocumentAttachments(currentDoc).find(\n (attachment) => attachment.slotId === slotId,\n );\n if (!existingAttachment) {\n return;\n }\n\n // After a reset, a store opened cold holds the slot only as a pending upload.\n const storageKey =\n state.attachmentStorageKeyBySlotId[slotId] ??\n state.pendingAttachments.find((attachment) => attachment.slotId === slotId)\n ?.storageKey;\n if (\n !isAttachmentWriteGenerationCurrent(state, writeGeneration) ||\n state.doc !== currentDoc\n )\n return;\n const rollbackSnapshot = exportFullHistorySnapshot(currentDoc).slice();\n removeDocumentAttachment(currentDoc, slotId);\n const attachmentUpdate = pendingDeltaSinceBase(state, currentDoc);\n const coveredVersion = encodeVersionVector(currentDoc);\n const pendingUpdate = createPendingUpdateFields(attachmentUpdate);\n const syncedAttachment = Boolean(state.record?.documentId);\n\n // Preserve the optimistic text/structured fields (see persistAttachedFiles):\n // removing an attachment while typing must not regress the editor.\n let persisted: Awaited<ReturnType<typeof persistDocument>>;\n try {\n persisted = await persistDocument(\n state,\n currentDoc,\n { snapshotEndVersion: coveredVersion },\n {\n ...(storageKey\n ? {\n attachmentRemoval: {\n mode: syncedAttachment\n ? (\"detach\" as const)\n : (\"delete\" as const),\n slotId,\n storageKey,\n },\n }\n : {}),\n ...(pendingUpdate ? { pendingUpdate } : {}),\n preserveSnapshotStructuredFields: true,\n preserveSnapshotText: true,\n },\n generation,\n );\n } catch (error) {\n await restoreFailedAttachmentMutation({\n generation,\n rollbackSnapshot,\n state,\n });\n throw error;\n }\n if (\n shouldAbortAttachmentPersistFollowup(\n state,\n writeGeneration,\n generation,\n persisted,\n )\n ) {\n // See persistAttachedFiles: only durable same-identity work re-arms sync.\n return;\n }\n installCommittedAttachmentRemoval({\n currentDoc,\n slotId,\n state,\n storageKey,\n syncedAttachment,\n });\n advancePendingBaseVersion(state, currentDoc);\n state.runtime.util.log(\n `Removed attachment ${existingAttachment.name} from document ${state.localId}.`,\n );\n requestDocumentStoreSync(state);\n}\n\nexport async function attachFilesToDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n files: ReadonlyArray<DocumentAttachmentUpload>,\n) {\n if (files.length === 0) {\n return;\n }\n\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistAttachedFiles(state, files, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function removeAttachmentFromDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () => persistRemovedAttachment(state, slotId, writeGeneration))\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n\nexport async function replaceAttachmentInDocumentStore(\n state: DocumentStoreState,\n scheduleSync: () => void,\n slotId: string,\n file: DocumentAttachmentUpload,\n) {\n let ready: boolean;\n try {\n ready = await ensureDocumentStoreReady(state, scheduleSync);\n } catch (error) {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n return;\n }\n\n if (!ready || !state.doc || !canWriteDocument(state)) {\n return;\n }\n\n const writeGeneration = captureAttachmentWriteGeneration(state);\n if (!writeGeneration) return;\n state.writeChain = state.writeChain\n .catch(() => undefined)\n .then(async () =>\n persistSlotAttachmentFile(state, slotId, file, writeGeneration),\n )\n .catch((error: unknown) => {\n reportDocumentStoreWriteFailure(state, ATTACHMENT_FAILURE_MESSAGE, error);\n });\n return state.writeChain;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobAccessManifestHash, computeBlobContentKeyTargetHash, computeContentRecordNonceDomainHash, makeVerifiedBlobKekTargets, verifyWriteHeader, } from "../../internal/crypto/index.js";
2
- import { blobContentMetadataHash, deriveBlobContentRecordKey, } from "../../data/documents/blob/shared/crypto.js";
1
+ import { blobContentMetadataHash, bytesToHex, CONTENT_RECORD_ENCRYPTION_SUITE, computeBlobAccessManifestHash, computeBlobContentKeyTargetHash, computeContentRecordNonceDomainHash, makeVerifiedBlobKekTargets, verifyWriteHeader, } from "../../internal/crypto/index.js";
2
+ import { deriveBlobContentRecordKey } from "../../data/documents/blob/shared/crypto.js";
3
3
  import { unwrapBlobContentKey } from "../../data/documents/blob/shared/projection.js";
4
4
  import { parseBlobEncryptedBytes, readBlobKekTarget, readDocumentManifestIdentity, sortBlobTargets, } from "../../data/documents/blob/shared/readers.js";
5
5
  import { assertBlobContentKeyBundleTargetHash } from "../../data/documents/blob/shared/responses.js";
@@ -1 +1 @@
1
- {"version":3,"file":"decrypt.js","sourceRoot":"","sources":["decrypt.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContentRecordNonceDomainHash,\n makeVerifiedBlobKekTargets,\n verifyWriteHeader,\n} from \"@tearleads/crypto\";\nimport type { BlobKekTargetsResponse } from \"@tearleads/validators/response\";\nimport type { BlobBytes } from \"../../data/blobContracts\";\nimport {\n blobContentMetadataHash,\n deriveBlobContentRecordKey,\n} from \"../../data/documents/blob/shared/crypto\";\nimport { unwrapBlobContentKey } from \"../../data/documents/blob/shared/projection\";\nimport {\n parseBlobEncryptedBytes,\n readBlobKekTarget,\n readDocumentManifestIdentity,\n sortBlobTargets,\n} from \"../../data/documents/blob/shared/readers\";\nimport { assertBlobContentKeyBundleTargetHash } from \"../../data/documents/blob/shared/responses\";\nimport type {\n BlobEncryptedBytesHeader,\n DecryptDocumentAttachmentBlobInput,\n} from \"../../data/documents/blob/shared/types\";\nimport { importContentKeyMaterial } from \"../../data/documents/shared/contentRecordKeys\";\nimport { assertDocumentWriterProjectionConsistent } from \"../../data/documents/shared/projection\";\nimport {\n asWebCryptoBytes,\n readWriteHeader,\n uniqueSortedStrings,\n} from \"../../data/documents/shared/readers\";\nimport { projectionVerificationOptions } from \"../../data/documents/shared/types\";\nimport { readCanonicalRecord } from \"../../data/keyingCanonicalJson\";\nimport type { DocumentWriterProjectionAuthorization } from \"../../data/keyingProjectionVerification\";\nimport {\n readRecordString,\n requireProjectionUserKeyResolver,\n} from \"../../data/keyingProjectionVerification\";\nimport { resolveEventContainerPaths } from \"../../data/keyingProjectionVerification/documentDependencyPaths\";\n\nimport { assertAttachmentBindingVerified } from \"./attachmentBindingVerification\";\nimport { decryptBlobChunks } from \"./blobChunkDecryption\";\nimport { assertBlobWrapScopeVerified } from \"./verifiedWrapScope\";\n\nasync function assertBlobEncryptionMetadata(input: {\n readonly contentKeyBundle: DecryptDocumentAttachmentBlobInput[\"binding\"][\"contentKeyBundle\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly expectedBlobId: string;\n readonly organizationId: string;\n}): Promise<void> {\n const { contentKeyBundle, encrypted, expectedBlobId, organizationId } = input;\n if (\n encrypted.blobId !== expectedBlobId ||\n encrypted.contentRecordId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes blob id mismatch\");\n }\n if (\n encrypted.contentKeyEpoch !== contentKeyBundle.contentKeyEpoch ||\n contentKeyBundle.blobId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes content-key bundle mismatch\");\n }\n await assertBlobContentKeyBundleTargetHash(contentKeyBundle);\n\n const expectedNonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId,\n objectKind: \"blob\",\n objectId: expectedBlobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: encrypted.contentRecordId,\n });\n if (encrypted.nonceDomainHash !== expectedNonceDomainHash) {\n throw new Error(\"Blob encrypted bytes nonce domain mismatch\");\n }\n const expectedMetadataHash = await blobContentMetadataHash({\n blobId: expectedBlobId,\n byteLength: encrypted.byteLength,\n chunkCount: encrypted.chunkCount,\n chunkSize: encrypted.chunkSize,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n });\n if (encrypted.metadataHash !== expectedMetadataHash) {\n throw new Error(\"Blob encrypted bytes metadata hash mismatch\");\n }\n}\n\nexport type AttachmentBlobDecryptionInput = Omit<\n DecryptDocumentAttachmentBlobInput,\n \"encryptedBytes\"\n> & {\n encrypted: BlobEncryptedBytesHeader;\n};\n\n/** Verifies metadata and authority; the caller must still authenticate ciphertext. */\nexport async function prepareDocumentAttachmentBlobDecryption({\n binding,\n encrypted,\n expectedDocumentId,\n expectedSlotId,\n execSql,\n resolveProjectionUserKey,\n targetSecretKey,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n writerProjection,\n}: AttachmentBlobDecryptionInput): Promise<{\n contentKey: Uint8Array;\n recordKey: CryptoKey;\n organizationId: string;\n}> {\n const requiredResolveProjectionUserKey = requireProjectionUserKeyResolver(\n resolveProjectionUserKey,\n \"Document attachment blob decrypt\",\n );\n const verificationOptions = projectionVerificationOptions({\n execSql,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n let documentAuthorization: DocumentWriterProjectionAuthorization | undefined;\n await assertDocumentWriterProjectionConsistent(writerProjection, {\n allowStaleContentKeyBundle: true,\n execSql,\n onVerifiedAuthorization: (authorization) => {\n documentAuthorization = authorization;\n },\n ...verificationOptions,\n });\n const { documentId, organizationId } =\n readDocumentManifestIdentity(writerProjection);\n await assertBlobEncryptionMetadata({\n contentKeyBundle: binding.contentKeyBundle,\n encrypted,\n expectedBlobId: binding.blobId,\n organizationId,\n });\n await assertAttachmentBindingVerified({\n authorization: documentAuthorization,\n binding,\n expectedDocumentId,\n expectedSlotId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n await assertBlobWriteHeaderVerified({\n authorization: documentAuthorization,\n binding,\n encrypted,\n organizationId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n\n if (!documentAuthorization)\n throw new Error(\"Blob document authorization is unavailable\");\n assertBlobWrapScopeVerified({\n authorization: documentAuthorization,\n binding,\n documentId,\n });\n const contentKey = await unwrapBlobContentKey({\n blobId: binding.blobId,\n contentKeyBundle: binding.contentKeyBundle,\n documentId,\n encrypted,\n execSql,\n expectedBindingId: binding.bindingId,\n secretKey: targetSecretKey,\n ...verificationOptions,\n writerProjection,\n });\n const contentKeyMaterial = await importContentKeyMaterial(contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: binding.blobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId: encrypted.contentRecordId,\n organizationId,\n usage: \"decrypt\",\n });\n return { contentKey, recordKey, organizationId };\n}\n\nasync function decryptDocumentAttachmentBlobWithKey(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<{ bytes: BlobBytes; contentKey: Uint8Array }> {\n const encrypted = parseBlobEncryptedBytes(input.encryptedBytes);\n const context = await prepareDocumentAttachmentBlobDecryption({\n ...input,\n encrypted,\n });\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const ciphertextHash = bytesToHex(\n new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n asWebCryptoBytes(input.encryptedBytes),\n ),\n ),\n );\n if (header.ciphertextHash !== ciphertextHash)\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n const bytes = await decryptBlobChunks({\n ...context,\n encrypted,\n expectedBlobId: input.binding.blobId,\n });\n return { bytes, contentKey: context.contentKey };\n}\n\nexport async function decryptDocumentAttachmentBlob(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<BlobBytes> {\n return (await decryptDocumentAttachmentBlobWithKey(input)).bytes;\n}\n\nfunction assertStringSetEquals(input: {\n readonly actual: readonly string[];\n readonly expected: readonly string[];\n readonly label: string;\n}): void {\n const actual = uniqueSortedStrings(input.actual);\n const expected = uniqueSortedStrings(input.expected);\n if (\n actual.length !== expected.length ||\n actual.some((value, index) => value !== expected[index])\n ) {\n throw new Error(`${input.label} is inconsistent`);\n }\n}\n\nasync function verifiedBlobKekTargetsForBinding(input: {\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly requireBindingMembership: boolean;\n readonly projection: BlobKekTargetsResponse | undefined;\n}) {\n const { binding, projection } = input;\n if (!projection) {\n throw new Error(\"Attachment binding lacks blob KEK verification material\");\n }\n const targets = sortBlobTargets(\n projection.targets.map((target, index) =>\n readBlobKekTarget(target, `Attachment blob KEK target[${index}]`),\n ),\n );\n const targetHash = await computeBlobContentKeyTargetHash(targets);\n if (\n targetHash !== projection.blobKeyTargetHash ||\n projection.blobId !== binding.blobId\n ) {\n throw new Error(\"Attachment blob KEK target hash is inconsistent\");\n }\n if (\n input.requireBindingMembership &&\n (!projection.activeBindingIds.includes(binding.bindingId) ||\n !targets.some(\n (target) =>\n target.bindingId === binding.bindingId &&\n target.documentId === attachmentBindingDocumentId(binding),\n ))\n ) {\n throw new Error(\"Attachment binding is absent from blob KEK targets\");\n }\n assertStringSetEquals({\n actual: projection.linkedContainerManifestHashes,\n expected: targets.map((target) => target.containerManifestHash),\n label: \"Attachment blob container manifests\",\n });\n assertStringSetEquals({\n actual: projection.linkedContainerKeyEpochIds,\n expected: targets.map((target) => target.containerKeyEpochId),\n label: \"Attachment blob container KEKs\",\n });\n const accessManifestHash = await computeBlobAccessManifestHash({\n version: 1,\n blobId: projection.blobId,\n organizationId: projection.organizationId,\n activeBindingIds: projection.activeBindingIds,\n documentManifestHashes: projection.documentManifestHashes,\n linkedContainerManifestHashes: projection.linkedContainerManifestHashes,\n linkedContainerKeyEpochIds: projection.linkedContainerKeyEpochIds,\n blobKeyTargetHash: projection.blobKeyTargetHash,\n });\n if (accessManifestHash !== projection.blobAccessManifestHash) {\n throw new Error(\"Attachment blob access manifest hash is inconsistent\");\n }\n return makeVerifiedBlobKekTargets({ ...projection, targets });\n}\n\nfunction attachmentBindingDocumentId(\n binding: DecryptDocumentAttachmentBlobInput[\"binding\"],\n): string {\n if (!binding.bindingEvent) {\n throw new Error(\"Attachment binding lacks a signed event\");\n }\n return readRecordString(\n readCanonicalRecord(\n binding.bindingEvent.body,\n \"Attachment binding event body\",\n ),\n \"documentId\",\n \"Attachment binding event body\",\n );\n}\n\nasync function assertBlobWriteHeaderVerified(input: {\n readonly authorization: DocumentWriterProjectionAuthorization | undefined;\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly organizationId: string;\n readonly resolveProjectionUserKey: ReturnType<\n typeof requireProjectionUserKeyResolver\n >;\n}): Promise<void> {\n if (!input.authorization || !input.binding.writeHeader) {\n throw new Error(\"Attachment blob lacks verified write authority\");\n }\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const writer = await input.resolveProjectionUserKey(header.writerUserId);\n if (!writer) {\n throw new Error(\"Attachment blob writer identity is unavailable\");\n }\n await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.blobKekTargets,\n requireBindingMembership: true,\n });\n const blobKekTargets = await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.writeAuthorization,\n // The signed binding proves which slot exposes the blob now. Writer\n // authorization describes the older target set committed when the bytes\n // were written, so a later valid binding need not appear in that set.\n requireBindingMembership: false,\n });\n if (\n header.metadataHash !== input.encrypted.metadataHash ||\n header.contentKeyEpoch !== input.encrypted.contentKeyEpoch ||\n header.contentRecordId !== input.encrypted.contentRecordId ||\n header.nonceDomainHash !== input.encrypted.nonceDomainHash\n ) {\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n }\n const verified = await verifyWriteHeader({\n authorizationMembership: \"referenced\",\n blobAuthorization: {\n authorizingContainerPaths: resolveEventContainerPaths({\n containerPathByManifestHash:\n input.authorization.containerPathByManifestHash,\n dependencyManifestHashes: header.dependencyManifestHashes,\n }).dependencyContainerPaths,\n blobKekTargets,\n principalPolicies: input.authorization.principalPolicies,\n },\n expectedAccessManifestHash: blobKekTargets.blobAccessManifestHash,\n expectedObject: {\n objectKind: \"blob\",\n objectId: input.binding.blobId,\n organizationId: input.organizationId,\n },\n expectedTargetHash: blobKekTargets.blobKeyTargetHash,\n header,\n writerPublicKey: writer.signingPublicKey,\n });\n if (!verified.ok) {\n throw verified.error;\n }\n}\n"]}
1
+ {"version":3,"file":"decrypt.js","sourceRoot":"","sources":["decrypt.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n blobContentMetadataHash,\n bytesToHex,\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeBlobAccessManifestHash,\n computeBlobContentKeyTargetHash,\n computeContentRecordNonceDomainHash,\n makeVerifiedBlobKekTargets,\n verifyWriteHeader,\n} from \"@tearleads/crypto\";\nimport type { BlobKekTargetsResponse } from \"@tearleads/validators/response\";\nimport type { BlobBytes } from \"../../data/blobContracts\";\nimport { deriveBlobContentRecordKey } from \"../../data/documents/blob/shared/crypto\";\nimport { unwrapBlobContentKey } from \"../../data/documents/blob/shared/projection\";\nimport {\n parseBlobEncryptedBytes,\n readBlobKekTarget,\n readDocumentManifestIdentity,\n sortBlobTargets,\n} from \"../../data/documents/blob/shared/readers\";\nimport { assertBlobContentKeyBundleTargetHash } from \"../../data/documents/blob/shared/responses\";\nimport type {\n BlobEncryptedBytesHeader,\n DecryptDocumentAttachmentBlobInput,\n} from \"../../data/documents/blob/shared/types\";\nimport { importContentKeyMaterial } from \"../../data/documents/shared/contentRecordKeys\";\nimport { assertDocumentWriterProjectionConsistent } from \"../../data/documents/shared/projection\";\nimport {\n asWebCryptoBytes,\n readWriteHeader,\n uniqueSortedStrings,\n} from \"../../data/documents/shared/readers\";\nimport { projectionVerificationOptions } from \"../../data/documents/shared/types\";\nimport { readCanonicalRecord } from \"../../data/keyingCanonicalJson\";\nimport type { DocumentWriterProjectionAuthorization } from \"../../data/keyingProjectionVerification\";\nimport {\n readRecordString,\n requireProjectionUserKeyResolver,\n} from \"../../data/keyingProjectionVerification\";\nimport { resolveEventContainerPaths } from \"../../data/keyingProjectionVerification/documentDependencyPaths\";\n\nimport { assertAttachmentBindingVerified } from \"./attachmentBindingVerification\";\nimport { decryptBlobChunks } from \"./blobChunkDecryption\";\nimport { assertBlobWrapScopeVerified } from \"./verifiedWrapScope\";\n\nasync function assertBlobEncryptionMetadata(input: {\n readonly contentKeyBundle: DecryptDocumentAttachmentBlobInput[\"binding\"][\"contentKeyBundle\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly expectedBlobId: string;\n readonly organizationId: string;\n}): Promise<void> {\n const { contentKeyBundle, encrypted, expectedBlobId, organizationId } = input;\n if (\n encrypted.blobId !== expectedBlobId ||\n encrypted.contentRecordId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes blob id mismatch\");\n }\n if (\n encrypted.contentKeyEpoch !== contentKeyBundle.contentKeyEpoch ||\n contentKeyBundle.blobId !== expectedBlobId\n ) {\n throw new Error(\"Blob encrypted bytes content-key bundle mismatch\");\n }\n await assertBlobContentKeyBundleTargetHash(contentKeyBundle);\n\n const expectedNonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId,\n objectKind: \"blob\",\n objectId: expectedBlobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: encrypted.contentRecordId,\n });\n if (encrypted.nonceDomainHash !== expectedNonceDomainHash) {\n throw new Error(\"Blob encrypted bytes nonce domain mismatch\");\n }\n const expectedMetadataHash = await blobContentMetadataHash({\n blobId: expectedBlobId,\n byteLength: encrypted.byteLength,\n chunkCount: encrypted.chunkCount,\n chunkSize: encrypted.chunkSize,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n });\n if (encrypted.metadataHash !== expectedMetadataHash) {\n throw new Error(\"Blob encrypted bytes metadata hash mismatch\");\n }\n}\n\nexport type AttachmentBlobDecryptionInput = Omit<\n DecryptDocumentAttachmentBlobInput,\n \"encryptedBytes\"\n> & {\n encrypted: BlobEncryptedBytesHeader;\n};\n\n/** Verifies metadata and authority; the caller must still authenticate ciphertext. */\nexport async function prepareDocumentAttachmentBlobDecryption({\n binding,\n encrypted,\n expectedDocumentId,\n expectedSlotId,\n execSql,\n resolveProjectionUserKey,\n targetSecretKey,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n writerProjection,\n}: AttachmentBlobDecryptionInput): Promise<{\n contentKey: Uint8Array;\n recordKey: CryptoKey;\n organizationId: string;\n}> {\n const requiredResolveProjectionUserKey = requireProjectionUserKeyResolver(\n resolveProjectionUserKey,\n \"Document attachment blob decrypt\",\n );\n const verificationOptions = projectionVerificationOptions({\n execSql,\n stillCurrent,\n warmReferencedPrincipalPolicies,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n let documentAuthorization: DocumentWriterProjectionAuthorization | undefined;\n await assertDocumentWriterProjectionConsistent(writerProjection, {\n allowStaleContentKeyBundle: true,\n execSql,\n onVerifiedAuthorization: (authorization) => {\n documentAuthorization = authorization;\n },\n ...verificationOptions,\n });\n const { documentId, organizationId } =\n readDocumentManifestIdentity(writerProjection);\n await assertBlobEncryptionMetadata({\n contentKeyBundle: binding.contentKeyBundle,\n encrypted,\n expectedBlobId: binding.blobId,\n organizationId,\n });\n await assertAttachmentBindingVerified({\n authorization: documentAuthorization,\n binding,\n expectedDocumentId,\n expectedSlotId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n await assertBlobWriteHeaderVerified({\n authorization: documentAuthorization,\n binding,\n encrypted,\n organizationId,\n resolveProjectionUserKey: requiredResolveProjectionUserKey,\n });\n\n if (!documentAuthorization)\n throw new Error(\"Blob document authorization is unavailable\");\n assertBlobWrapScopeVerified({\n authorization: documentAuthorization,\n binding,\n documentId,\n });\n const contentKey = await unwrapBlobContentKey({\n blobId: binding.blobId,\n contentKeyBundle: binding.contentKeyBundle,\n documentId,\n encrypted,\n execSql,\n expectedBindingId: binding.bindingId,\n secretKey: targetSecretKey,\n ...verificationOptions,\n writerProjection,\n });\n const contentKeyMaterial = await importContentKeyMaterial(contentKey);\n const recordKey = await deriveBlobContentRecordKey({\n blobId: binding.blobId,\n contentKeyEpoch: encrypted.contentKeyEpoch,\n contentKeyMaterial,\n contentRecordId: encrypted.contentRecordId,\n organizationId,\n usage: \"decrypt\",\n });\n return { contentKey, recordKey, organizationId };\n}\n\nasync function decryptDocumentAttachmentBlobWithKey(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<{ bytes: BlobBytes; contentKey: Uint8Array }> {\n const encrypted = parseBlobEncryptedBytes(input.encryptedBytes);\n const context = await prepareDocumentAttachmentBlobDecryption({\n ...input,\n encrypted,\n });\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const ciphertextHash = bytesToHex(\n new Uint8Array(\n await crypto.subtle.digest(\n \"SHA-256\",\n asWebCryptoBytes(input.encryptedBytes),\n ),\n ),\n );\n if (header.ciphertextHash !== ciphertextHash)\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n const bytes = await decryptBlobChunks({\n ...context,\n encrypted,\n expectedBlobId: input.binding.blobId,\n });\n return { bytes, contentKey: context.contentKey };\n}\n\nexport async function decryptDocumentAttachmentBlob(\n input: DecryptDocumentAttachmentBlobInput,\n): Promise<BlobBytes> {\n return (await decryptDocumentAttachmentBlobWithKey(input)).bytes;\n}\n\nfunction assertStringSetEquals(input: {\n readonly actual: readonly string[];\n readonly expected: readonly string[];\n readonly label: string;\n}): void {\n const actual = uniqueSortedStrings(input.actual);\n const expected = uniqueSortedStrings(input.expected);\n if (\n actual.length !== expected.length ||\n actual.some((value, index) => value !== expected[index])\n ) {\n throw new Error(`${input.label} is inconsistent`);\n }\n}\n\nasync function verifiedBlobKekTargetsForBinding(input: {\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly requireBindingMembership: boolean;\n readonly projection: BlobKekTargetsResponse | undefined;\n}) {\n const { binding, projection } = input;\n if (!projection) {\n throw new Error(\"Attachment binding lacks blob KEK verification material\");\n }\n const targets = sortBlobTargets(\n projection.targets.map((target, index) =>\n readBlobKekTarget(target, `Attachment blob KEK target[${index}]`),\n ),\n );\n const targetHash = await computeBlobContentKeyTargetHash(targets);\n if (\n targetHash !== projection.blobKeyTargetHash ||\n projection.blobId !== binding.blobId\n ) {\n throw new Error(\"Attachment blob KEK target hash is inconsistent\");\n }\n if (\n input.requireBindingMembership &&\n (!projection.activeBindingIds.includes(binding.bindingId) ||\n !targets.some(\n (target) =>\n target.bindingId === binding.bindingId &&\n target.documentId === attachmentBindingDocumentId(binding),\n ))\n ) {\n throw new Error(\"Attachment binding is absent from blob KEK targets\");\n }\n assertStringSetEquals({\n actual: projection.linkedContainerManifestHashes,\n expected: targets.map((target) => target.containerManifestHash),\n label: \"Attachment blob container manifests\",\n });\n assertStringSetEquals({\n actual: projection.linkedContainerKeyEpochIds,\n expected: targets.map((target) => target.containerKeyEpochId),\n label: \"Attachment blob container KEKs\",\n });\n const accessManifestHash = await computeBlobAccessManifestHash({\n version: 1,\n blobId: projection.blobId,\n organizationId: projection.organizationId,\n activeBindingIds: projection.activeBindingIds,\n documentManifestHashes: projection.documentManifestHashes,\n linkedContainerManifestHashes: projection.linkedContainerManifestHashes,\n linkedContainerKeyEpochIds: projection.linkedContainerKeyEpochIds,\n blobKeyTargetHash: projection.blobKeyTargetHash,\n });\n if (accessManifestHash !== projection.blobAccessManifestHash) {\n throw new Error(\"Attachment blob access manifest hash is inconsistent\");\n }\n return makeVerifiedBlobKekTargets({ ...projection, targets });\n}\n\nfunction attachmentBindingDocumentId(\n binding: DecryptDocumentAttachmentBlobInput[\"binding\"],\n): string {\n if (!binding.bindingEvent) {\n throw new Error(\"Attachment binding lacks a signed event\");\n }\n return readRecordString(\n readCanonicalRecord(\n binding.bindingEvent.body,\n \"Attachment binding event body\",\n ),\n \"documentId\",\n \"Attachment binding event body\",\n );\n}\n\nasync function assertBlobWriteHeaderVerified(input: {\n readonly authorization: DocumentWriterProjectionAuthorization | undefined;\n readonly binding: DecryptDocumentAttachmentBlobInput[\"binding\"];\n readonly encrypted: BlobEncryptedBytesHeader;\n readonly organizationId: string;\n readonly resolveProjectionUserKey: ReturnType<\n typeof requireProjectionUserKeyResolver\n >;\n}): Promise<void> {\n if (!input.authorization || !input.binding.writeHeader) {\n throw new Error(\"Attachment blob lacks verified write authority\");\n }\n const header = readWriteHeader(\n input.binding.writeHeader,\n \"Attachment blob write header\",\n );\n const writer = await input.resolveProjectionUserKey(header.writerUserId);\n if (!writer) {\n throw new Error(\"Attachment blob writer identity is unavailable\");\n }\n await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.blobKekTargets,\n requireBindingMembership: true,\n });\n const blobKekTargets = await verifiedBlobKekTargetsForBinding({\n binding: input.binding,\n projection: input.binding.writeAuthorization,\n // The signed binding proves which slot exposes the blob now. Writer\n // authorization describes the older target set committed when the bytes\n // were written, so a later valid binding need not appear in that set.\n requireBindingMembership: false,\n });\n if (\n header.metadataHash !== input.encrypted.metadataHash ||\n header.contentKeyEpoch !== input.encrypted.contentKeyEpoch ||\n header.contentRecordId !== input.encrypted.contentRecordId ||\n header.nonceDomainHash !== input.encrypted.nonceDomainHash\n ) {\n throw new Error(\"Attachment blob write header does not match ciphertext\");\n }\n const verified = await verifyWriteHeader({\n authorizationMembership: \"referenced\",\n blobAuthorization: {\n authorizingContainerPaths: resolveEventContainerPaths({\n containerPathByManifestHash:\n input.authorization.containerPathByManifestHash,\n dependencyManifestHashes: header.dependencyManifestHashes,\n }).dependencyContainerPaths,\n blobKekTargets,\n principalPolicies: input.authorization.principalPolicies,\n },\n expectedAccessManifestHash: blobKekTargets.blobAccessManifestHash,\n expectedObject: {\n objectKind: \"blob\",\n objectId: input.binding.blobId,\n organizationId: input.organizationId,\n },\n expectedTargetHash: blobKekTargets.blobKeyTargetHash,\n header,\n writerPublicKey: writer.signingPublicKey,\n });\n if (!verified.ok) {\n throw verified.error;\n }\n}\n"]}