@tearleads/client-sdk 0.1.40 → 0.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/data/documents/shared/syncResponses.js +5 -7
- package/data/documents/shared/syncResponses.js.map +1 -1
- package/internal/validators/documentSyncRefinements.js +5 -0
- package/internal/validators/documentSyncRefinements.js.map +1 -1
- package/internal/validators/operation/documentSync.d.ts +3 -0
- package/internal/validators/response/documentSyncSchema.js +35 -1
- package/internal/validators/response/documentSyncSchema.js.map +1 -1
- package/package.json +1 -1
- package/workflows/documents/syncContentKeys.js +4 -9
- package/workflows/documents/syncContentKeys.js.map +1 -1
|
@@ -227,14 +227,12 @@ function documentSyncContentKeyBundlesByEpoch(plan, response) {
|
|
|
227
227
|
bundle.contentKeyEpoch <= 0) {
|
|
228
228
|
throw new Error("Document sync response content-key bundle epoch mismatch");
|
|
229
229
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
if (contentKeyBundleCanonical(existing) !== contentKeyBundleCanonical(bundle)) {
|
|
236
|
-
throw new Error("Document sync response content-key bundle conflict");
|
|
230
|
+
// The current epoch travels only as contentKeyBundle; the array carries
|
|
231
|
+
// each older epoch once.
|
|
232
|
+
if (bundleByEpoch.has(bundle.contentKeyEpoch)) {
|
|
233
|
+
throw new Error("Document sync response repeats a content-key epoch");
|
|
237
234
|
}
|
|
235
|
+
bundleByEpoch.set(bundle.contentKeyEpoch, bundle);
|
|
238
236
|
}
|
|
239
237
|
return bundleByEpoch;
|
|
240
238
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncResponses.js","sourceRoot":"","sources":["syncResponses.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeContentRecordNonceDomainHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n verifyWriteHeader,\n type WriteHeader,\n} from \"@tearleads/crypto\";\nimport { isPlainObject as isPlainRecord } from \"@tearleads/validators/isPlainObject\";\nimport {\n DOCUMENT_NOT_FOUND_ERROR_CODE,\n DOCUMENT_SYNC_ERROR_CODES,\n type DocumentSyncResponse,\n} from \"@tearleads/validators/response\";\nimport { parseWalLsn } from \"@tearleads/validators/util\";\nimport { throwKeyingVerificationShapeFailure } from \"../../keyingProjectionVerification/error\";\nimport {\n readRecordPositiveInteger,\n readRecordString,\n readWriteHeader,\n serializeCanonical,\n serializedPersistedDocumentState,\n} from \"./readers\";\n\nexport { submitDocumentSync } from \"./syncPagination\";\n\nimport { documentWriteAuthorizationForHeader } from \"./syncResponseAuthorization\";\nimport type {\n DocumentSyncPlan,\n DocumentSyncSubmitFailure,\n DocumentWriterPublicKeyResolver,\n PersistedDocumentSyncState,\n} from \"./types\";\n\n// Remote-primary databases without a portable WAL position use the zero LSN\n// as an explicit \"checkpoint not tracked\" sentinel. It is accepted only when\n// the response declares that mode, letting a local record carrying a checkpoint\n// from another backend move to that primary without weakening tracked checks.\n// This protects against replica lag, not a server that falsely declares its own\n// consistency capability; the authenticated server remains the source of data.\nconst UNTRACKED_COMMIT_LSN = \"0/0\";\n\nexport class DocumentSyncResponseUpdateContentKeyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DocumentSyncResponseUpdateContentKeyError\";\n }\n}\n\nfunction assertAcceptedOutgoingUpdateIdsMatchPlan(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): void {\n const expected = plan.request.outgoingUpdates.map((update) => update.id);\n const accepted = response.acceptedOutgoingUpdateIds;\n const expectedSorted = [...expected].sort();\n const acceptedSorted = [...accepted].sort();\n if (\n expectedSorted.length !== acceptedSorted.length ||\n expectedSorted.some((id, index) => id !== acceptedSorted[index])\n ) {\n throw new Error(\"Document sync response accepted update mismatch\");\n }\n}\n\nasync function assertDocumentSyncResponseUpdateMatchesPlan(input: {\n contentKeyBundlesByEpoch: ReadonlyMap<\n number,\n DocumentSyncResponse[\"contentKeyBundle\"]\n >;\n plan: DocumentSyncPlan;\n update: DocumentSyncResponse[\"updates\"][number];\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n}): Promise<void> {\n const { plan, update } = input;\n if (update.documentId !== plan.documentId) {\n throw new Error(\"Document sync response update document mismatch\");\n }\n const header = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n );\n await assertDocumentSyncResponseUpdateHashes({ update });\n const contentKeyBundle = assertDocumentSyncResponseUpdateContentKeyBundle({\n contentKeyBundlesByEpoch: input.contentKeyBundlesByEpoch,\n header,\n plan,\n });\n assertDocumentSyncResponseWriteHeaderFields({ header, plan, update });\n await assertDocumentSyncResponseNonceDomain({ header, plan });\n await assertDocumentSyncResponseWriteHeaderSignature({\n header,\n contentKeyBundle,\n plan,\n resolveWriterPublicKey: input.resolveWriterPublicKey,\n update,\n });\n}\n\nfunction assertDocumentSyncResponseUpdateContentKeyBundle(input: {\n contentKeyBundlesByEpoch: ReadonlyMap<\n number,\n DocumentSyncResponse[\"contentKeyBundle\"]\n >;\n header: WriteHeader;\n plan: DocumentSyncPlan;\n}): DocumentSyncResponse[\"contentKeyBundle\"] {\n const { header, plan } = input;\n if (header.contentKeyEpoch > plan.contentKeyEpoch) {\n throw new DocumentSyncResponseUpdateContentKeyError(\n \"Document sync response includes a future content-key epoch\",\n );\n }\n\n const bundle = input.contentKeyBundlesByEpoch.get(header.contentKeyEpoch);\n if (!bundle) {\n throw new DocumentSyncResponseUpdateContentKeyError(\n \"Document sync response content-key bundle missing\",\n );\n }\n if (\n bundle.documentId !== plan.documentId ||\n bundle.contentKeyEpoch !== header.contentKeyEpoch\n ) {\n throw new Error(\"Document sync response content-key bundle mismatch\");\n }\n return bundle;\n}\n\nasync function assertDocumentSyncResponseUpdateHashes(input: {\n update: DocumentSyncResponse[\"updates\"][number];\n}): Promise<void> {\n const { update } = input;\n const ciphertextHash = await computeDocumentContentRecordCiphertextHash(\n update.encryptedData,\n );\n if (\n ciphertextHash !==\n readRecordString(update.writeHeader, \"ciphertextHash\", \"write header\")\n ) {\n throw new Error(\"Document sync response ciphertext hash mismatch\");\n }\n const metadataHash = await computeDocumentContentRecordMetadataHash({\n ...(update.checkpointKind === undefined\n ? {}\n : { checkpointKind: update.checkpointKind }),\n ...(update.checkpointPayloadKind === undefined\n ? {}\n : { checkpointPayloadKind: update.checkpointPayloadKind }),\n documentId: update.documentId,\n partialEndVersionVector: update.partialEndVersionVector,\n partialStartVersionVector: update.partialStartVersionVector,\n plaintextHash: update.plaintextHash,\n ...(update.sourceVersionVector === undefined\n ? {}\n : { sourceVersionVector: update.sourceVersionVector }),\n updateId: update.id,\n });\n if (\n metadataHash !==\n readRecordString(update.writeHeader, \"metadataHash\", \"write header\")\n ) {\n throw new Error(\"Document sync response metadata hash mismatch\");\n }\n}\n\nfunction assertDocumentSyncResponseWriteHeaderFields(input: {\n header: WriteHeader;\n plan: DocumentSyncPlan;\n update: DocumentSyncResponse[\"updates\"][number];\n}): void {\n const { header, plan, update } = input;\n const mustMatchCurrentBoundary = isAcceptedOutgoingSyncUpdate(plan, update);\n\n if (\n header.version !== 1 ||\n header.objectKind !== \"document\" ||\n header.objectId !== plan.documentId ||\n header.organizationId !== plan.organizationId ||\n header.encryptionSuite !== CONTENT_RECORD_ENCRYPTION_SUITE ||\n header.writerKeyFingerprint !== update.authorFingerprint ||\n (mustMatchCurrentBoundary &&\n (header.contentKeyEpoch !== plan.contentKeyEpoch ||\n header.accessManifestHash !== plan.expectedLinkSetManifestHash ||\n header.targetHash !== plan.expectedTargetHash))\n ) {\n throw new Error(\"Document sync response write header mismatch\");\n }\n}\n\nfunction isAcceptedOutgoingSyncUpdate(\n plan: DocumentSyncPlan,\n update: DocumentSyncResponse[\"updates\"][number],\n): boolean {\n return plan.request.outgoingUpdates.some(\n (outgoingUpdate) => outgoingUpdate.id === update.id,\n );\n}\n\nasync function assertDocumentSyncResponseNonceDomain(input: {\n header: WriteHeader;\n plan: DocumentSyncPlan;\n}): Promise<void> {\n const { header, plan } = input;\n const nonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: plan.organizationId,\n objectKind: \"document\",\n objectId: plan.documentId,\n contentKeyEpoch: header.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: header.contentRecordId,\n });\n if (nonceDomainHash !== header.nonceDomainHash) {\n throw new Error(\"Document sync response nonce domain mismatch\");\n }\n}\n\nasync function assertDocumentSyncResponseWriteHeaderSignature(input: {\n contentKeyBundle: DocumentSyncResponse[\"contentKeyBundle\"];\n header: WriteHeader;\n plan: DocumentSyncPlan;\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n update: DocumentSyncResponse[\"updates\"][number];\n}): Promise<void> {\n const { header, plan, update } = input;\n const writerPublicKey = await input.resolveWriterPublicKey({\n writerSigningKeyFingerprint: header.writerKeyFingerprint,\n writerUserId: header.writerUserId,\n });\n if (!writerPublicKey) {\n throw new Error(\"Document sync response writer public key missing\");\n }\n\n const documentAuthorization = await documentWriteAuthorizationForHeader({\n allowMissingAuthorization: isAcceptedOutgoingSyncUpdate(plan, update),\n authorizationTargets: update.authorizationTargets,\n dependencyManifestHashes: header.dependencyManifestHashes,\n contentKeyBundle: input.contentKeyBundle,\n manifestHash: header.accessManifestHash,\n plan,\n targetHash: header.targetHash,\n });\n const verified = await verifyWriteHeader({\n authorizationMembership: \"referenced\",\n expectedAccessManifestHash: documentAuthorization\n ? header.accessManifestHash\n : plan.expectedLinkSetManifestHash,\n expectedObject: {\n objectKind: \"document\",\n objectId: plan.documentId,\n organizationId: plan.organizationId,\n },\n header,\n ...(documentAuthorization ? { documentAuthorization } : {}),\n expectedTargetHash: documentAuthorization\n ? header.targetHash\n : plan.expectedTargetHash,\n writerPublicKey,\n });\n if (!verified.ok) {\n throw verified.error;\n }\n}\n\nfunction assertDocumentSyncCommitCheckpointMatchesPlan(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): void {\n if (\n response.commitLsnMode === \"untracked\" &&\n response.commitLsn !== UNTRACKED_COMMIT_LSN\n ) {\n throw new Error(\n \"Document sync response untracked commit LSN must use the 0/0 sentinel\",\n );\n }\n if (\n response.commitLsn === UNTRACKED_COMMIT_LSN &&\n response.commitLsnMode !== \"untracked\"\n ) {\n throw new Error(\n \"Document sync response 0/0 commit LSN must be declared untracked\",\n );\n }\n if (response.commitLsn !== null) {\n parseWalLsn(response.commitLsn, \"Document sync response commit LSN\");\n }\n if (plan.minLsn === undefined) {\n return;\n }\n const minLsn = parseWalLsn(\n plan.minLsn,\n \"Document sync requested minimum LSN\",\n );\n if (response.commitLsn === null) {\n throw new Error(\"Document sync response commit LSN is missing\");\n }\n if (\n !(\n response.commitLsn === UNTRACKED_COMMIT_LSN &&\n response.commitLsnMode === \"untracked\"\n ) &&\n parseWalLsn(response.commitLsn, \"Document sync response commit LSN\") <\n minLsn\n ) {\n throw new Error(\"Document sync response commit LSN is stale\");\n }\n}\n\nfunction documentSyncManifestEpoch(plan: DocumentSyncPlan): number {\n if (!isPlainRecord(plan.documentManifest.state)) {\n throw new Error(\"Document sync manifest state is invalid\");\n }\n\n return readRecordPositiveInteger(\n plan.documentManifest.state,\n \"epoch\",\n \"Document sync manifest state\",\n );\n}\n\n// The one load-bearing epoch check: no returned update may claim an\n// accessEpoch newer than the current manifest epoch.\nfunction assertDocumentSyncAccessEpochsNotInFuture(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): void {\n const currentAccessEpoch = documentSyncManifestEpoch(plan);\n for (const update of response.updates) {\n if (update.accessEpoch > currentAccessEpoch) {\n throw new Error(\"Document sync response includes a future access epoch\");\n }\n }\n}\n\nfunction contentKeyBundleCanonical(\n bundle: DocumentSyncResponse[\"contentKeyBundle\"],\n): string {\n return serializeCanonical(bundle, \"content-key bundle\");\n}\n\nfunction documentSyncContentKeyBundlesByEpoch(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): ReadonlyMap<number, DocumentSyncResponse[\"contentKeyBundle\"]> {\n if (\n contentKeyBundleCanonical(response.contentKeyBundle) !==\n contentKeyBundleCanonical(plan.sourceContentKeyBundle)\n ) {\n throw new Error(\"Document sync response content-key bundle mismatch\");\n }\n\n const bundleByEpoch = new Map<\n number,\n DocumentSyncResponse[\"contentKeyBundle\"]\n >();\n const bundles = [response.contentKeyBundle, ...response.contentKeyBundles];\n\n for (const bundle of bundles) {\n if (bundle.documentId !== plan.documentId) {\n throw new Error(\n \"Document sync response content-key bundle document mismatch\",\n );\n }\n if (\n !Number.isInteger(bundle.contentKeyEpoch) ||\n bundle.contentKeyEpoch <= 0\n ) {\n throw new Error(\n \"Document sync response content-key bundle epoch mismatch\",\n );\n }\n\n const existing = bundleByEpoch.get(bundle.contentKeyEpoch);\n if (!existing) {\n bundleByEpoch.set(bundle.contentKeyEpoch, bundle);\n continue;\n }\n if (\n contentKeyBundleCanonical(existing) !== contentKeyBundleCanonical(bundle)\n ) {\n throw new Error(\"Document sync response content-key bundle conflict\");\n }\n }\n\n return bundleByEpoch;\n}\n\nasync function persistedDocumentSyncStateFromResponseInternal(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n options: {\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n },\n): Promise<PersistedDocumentSyncState> {\n if (response.documentId !== plan.documentId) {\n throw new Error(\"Document sync response document id mismatch\");\n }\n const contentKeyBundlesByEpoch = documentSyncContentKeyBundlesByEpoch(\n plan,\n response,\n );\n if (\n serializeCanonical(response.documentKekTargets, \"KEK targets\") !==\n serializeCanonical(plan.documentKekTargets, \"KEK targets\")\n ) {\n throw new Error(\"Document sync response KEK target mismatch\");\n }\n assertAcceptedOutgoingUpdateIdsMatchPlan(plan, response);\n assertDocumentSyncCommitCheckpointMatchesPlan(plan, response);\n assertDocumentSyncAccessEpochsNotInFuture(plan, response);\n\n await Promise.all(\n response.updates.map((update) =>\n assertDocumentSyncResponseUpdateMatchesPlan({\n contentKeyBundlesByEpoch,\n plan,\n resolveWriterPublicKey: options.resolveWriterPublicKey,\n update,\n }),\n ),\n );\n\n return serializedPersistedDocumentState({\n contentKeyBundle: response.contentKeyBundle,\n documentId: plan.documentId,\n documentKekTargets: response.documentKekTargets,\n documentManifestBundle: plan.documentManifest,\n });\n}\n\nexport async function persistedDocumentSyncStateFromResponse(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n options: {\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n },\n): Promise<PersistedDocumentSyncState> {\n try {\n return await persistedDocumentSyncStateFromResponseInternal(\n plan,\n response,\n options,\n );\n } catch (error) {\n if (error instanceof DocumentSyncResponseUpdateContentKeyError) throw error;\n throwKeyingVerificationShapeFailure(error);\n }\n}\n\nexport function isRetryableDocumentSyncConflict(\n failure: DocumentSyncSubmitFailure,\n): boolean {\n return (\n failure.status === 409 &&\n failure.code === DOCUMENT_SYNC_ERROR_CODES.stateStale\n );\n}\n\n// The wipe gate is deliberately strict — coded 404s only, which is the right\n// failure mode for a destructive action. A bare 404 proves nothing about the\n// document: proxy/tunnel error pages, deploy-skew route misses, and\n// container-level lookups all produce one.\nexport function isUpstreamDeletedDocumentSyncFailure(\n failure: DocumentSyncSubmitFailure,\n): boolean {\n return (\n failure.status === 404 && failure.code === DOCUMENT_NOT_FOUND_ERROR_CODE\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"syncResponses.js","sourceRoot":"","sources":["syncResponses.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n CONTENT_RECORD_ENCRYPTION_SUITE,\n computeContentRecordNonceDomainHash,\n computeDocumentContentRecordCiphertextHash,\n computeDocumentContentRecordMetadataHash,\n verifyWriteHeader,\n type WriteHeader,\n} from \"@tearleads/crypto\";\nimport { isPlainObject as isPlainRecord } from \"@tearleads/validators/isPlainObject\";\nimport {\n DOCUMENT_NOT_FOUND_ERROR_CODE,\n DOCUMENT_SYNC_ERROR_CODES,\n type DocumentSyncResponse,\n} from \"@tearleads/validators/response\";\nimport { parseWalLsn } from \"@tearleads/validators/util\";\nimport { throwKeyingVerificationShapeFailure } from \"../../keyingProjectionVerification/error\";\nimport {\n readRecordPositiveInteger,\n readRecordString,\n readWriteHeader,\n serializeCanonical,\n serializedPersistedDocumentState,\n} from \"./readers\";\n\nexport { submitDocumentSync } from \"./syncPagination\";\n\nimport { documentWriteAuthorizationForHeader } from \"./syncResponseAuthorization\";\nimport type {\n DocumentSyncPlan,\n DocumentSyncSubmitFailure,\n DocumentWriterPublicKeyResolver,\n PersistedDocumentSyncState,\n} from \"./types\";\n\n// Remote-primary databases without a portable WAL position use the zero LSN\n// as an explicit \"checkpoint not tracked\" sentinel. It is accepted only when\n// the response declares that mode, letting a local record carrying a checkpoint\n// from another backend move to that primary without weakening tracked checks.\n// This protects against replica lag, not a server that falsely declares its own\n// consistency capability; the authenticated server remains the source of data.\nconst UNTRACKED_COMMIT_LSN = \"0/0\";\n\nexport class DocumentSyncResponseUpdateContentKeyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"DocumentSyncResponseUpdateContentKeyError\";\n }\n}\n\nfunction assertAcceptedOutgoingUpdateIdsMatchPlan(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): void {\n const expected = plan.request.outgoingUpdates.map((update) => update.id);\n const accepted = response.acceptedOutgoingUpdateIds;\n const expectedSorted = [...expected].sort();\n const acceptedSorted = [...accepted].sort();\n if (\n expectedSorted.length !== acceptedSorted.length ||\n expectedSorted.some((id, index) => id !== acceptedSorted[index])\n ) {\n throw new Error(\"Document sync response accepted update mismatch\");\n }\n}\n\nasync function assertDocumentSyncResponseUpdateMatchesPlan(input: {\n contentKeyBundlesByEpoch: ReadonlyMap<\n number,\n DocumentSyncResponse[\"contentKeyBundle\"]\n >;\n plan: DocumentSyncPlan;\n update: DocumentSyncResponse[\"updates\"][number];\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n}): Promise<void> {\n const { plan, update } = input;\n if (update.documentId !== plan.documentId) {\n throw new Error(\"Document sync response update document mismatch\");\n }\n const header = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n );\n await assertDocumentSyncResponseUpdateHashes({ update });\n const contentKeyBundle = assertDocumentSyncResponseUpdateContentKeyBundle({\n contentKeyBundlesByEpoch: input.contentKeyBundlesByEpoch,\n header,\n plan,\n });\n assertDocumentSyncResponseWriteHeaderFields({ header, plan, update });\n await assertDocumentSyncResponseNonceDomain({ header, plan });\n await assertDocumentSyncResponseWriteHeaderSignature({\n header,\n contentKeyBundle,\n plan,\n resolveWriterPublicKey: input.resolveWriterPublicKey,\n update,\n });\n}\n\nfunction assertDocumentSyncResponseUpdateContentKeyBundle(input: {\n contentKeyBundlesByEpoch: ReadonlyMap<\n number,\n DocumentSyncResponse[\"contentKeyBundle\"]\n >;\n header: WriteHeader;\n plan: DocumentSyncPlan;\n}): DocumentSyncResponse[\"contentKeyBundle\"] {\n const { header, plan } = input;\n if (header.contentKeyEpoch > plan.contentKeyEpoch) {\n throw new DocumentSyncResponseUpdateContentKeyError(\n \"Document sync response includes a future content-key epoch\",\n );\n }\n\n const bundle = input.contentKeyBundlesByEpoch.get(header.contentKeyEpoch);\n if (!bundle) {\n throw new DocumentSyncResponseUpdateContentKeyError(\n \"Document sync response content-key bundle missing\",\n );\n }\n if (\n bundle.documentId !== plan.documentId ||\n bundle.contentKeyEpoch !== header.contentKeyEpoch\n ) {\n throw new Error(\"Document sync response content-key bundle mismatch\");\n }\n return bundle;\n}\n\nasync function assertDocumentSyncResponseUpdateHashes(input: {\n update: DocumentSyncResponse[\"updates\"][number];\n}): Promise<void> {\n const { update } = input;\n const ciphertextHash = await computeDocumentContentRecordCiphertextHash(\n update.encryptedData,\n );\n if (\n ciphertextHash !==\n readRecordString(update.writeHeader, \"ciphertextHash\", \"write header\")\n ) {\n throw new Error(\"Document sync response ciphertext hash mismatch\");\n }\n const metadataHash = await computeDocumentContentRecordMetadataHash({\n ...(update.checkpointKind === undefined\n ? {}\n : { checkpointKind: update.checkpointKind }),\n ...(update.checkpointPayloadKind === undefined\n ? {}\n : { checkpointPayloadKind: update.checkpointPayloadKind }),\n documentId: update.documentId,\n partialEndVersionVector: update.partialEndVersionVector,\n partialStartVersionVector: update.partialStartVersionVector,\n plaintextHash: update.plaintextHash,\n ...(update.sourceVersionVector === undefined\n ? {}\n : { sourceVersionVector: update.sourceVersionVector }),\n updateId: update.id,\n });\n if (\n metadataHash !==\n readRecordString(update.writeHeader, \"metadataHash\", \"write header\")\n ) {\n throw new Error(\"Document sync response metadata hash mismatch\");\n }\n}\n\nfunction assertDocumentSyncResponseWriteHeaderFields(input: {\n header: WriteHeader;\n plan: DocumentSyncPlan;\n update: DocumentSyncResponse[\"updates\"][number];\n}): void {\n const { header, plan, update } = input;\n const mustMatchCurrentBoundary = isAcceptedOutgoingSyncUpdate(plan, update);\n\n if (\n header.version !== 1 ||\n header.objectKind !== \"document\" ||\n header.objectId !== plan.documentId ||\n header.organizationId !== plan.organizationId ||\n header.encryptionSuite !== CONTENT_RECORD_ENCRYPTION_SUITE ||\n header.writerKeyFingerprint !== update.authorFingerprint ||\n (mustMatchCurrentBoundary &&\n (header.contentKeyEpoch !== plan.contentKeyEpoch ||\n header.accessManifestHash !== plan.expectedLinkSetManifestHash ||\n header.targetHash !== plan.expectedTargetHash))\n ) {\n throw new Error(\"Document sync response write header mismatch\");\n }\n}\n\nfunction isAcceptedOutgoingSyncUpdate(\n plan: DocumentSyncPlan,\n update: DocumentSyncResponse[\"updates\"][number],\n): boolean {\n return plan.request.outgoingUpdates.some(\n (outgoingUpdate) => outgoingUpdate.id === update.id,\n );\n}\n\nasync function assertDocumentSyncResponseNonceDomain(input: {\n header: WriteHeader;\n plan: DocumentSyncPlan;\n}): Promise<void> {\n const { header, plan } = input;\n const nonceDomainHash = await computeContentRecordNonceDomainHash({\n version: 1,\n organizationId: plan.organizationId,\n objectKind: \"document\",\n objectId: plan.documentId,\n contentKeyEpoch: header.contentKeyEpoch,\n encryptionSuite: CONTENT_RECORD_ENCRYPTION_SUITE,\n contentRecordId: header.contentRecordId,\n });\n if (nonceDomainHash !== header.nonceDomainHash) {\n throw new Error(\"Document sync response nonce domain mismatch\");\n }\n}\n\nasync function assertDocumentSyncResponseWriteHeaderSignature(input: {\n contentKeyBundle: DocumentSyncResponse[\"contentKeyBundle\"];\n header: WriteHeader;\n plan: DocumentSyncPlan;\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n update: DocumentSyncResponse[\"updates\"][number];\n}): Promise<void> {\n const { header, plan, update } = input;\n const writerPublicKey = await input.resolveWriterPublicKey({\n writerSigningKeyFingerprint: header.writerKeyFingerprint,\n writerUserId: header.writerUserId,\n });\n if (!writerPublicKey) {\n throw new Error(\"Document sync response writer public key missing\");\n }\n\n const documentAuthorization = await documentWriteAuthorizationForHeader({\n allowMissingAuthorization: isAcceptedOutgoingSyncUpdate(plan, update),\n authorizationTargets: update.authorizationTargets,\n dependencyManifestHashes: header.dependencyManifestHashes,\n contentKeyBundle: input.contentKeyBundle,\n manifestHash: header.accessManifestHash,\n plan,\n targetHash: header.targetHash,\n });\n const verified = await verifyWriteHeader({\n authorizationMembership: \"referenced\",\n expectedAccessManifestHash: documentAuthorization\n ? header.accessManifestHash\n : plan.expectedLinkSetManifestHash,\n expectedObject: {\n objectKind: \"document\",\n objectId: plan.documentId,\n organizationId: plan.organizationId,\n },\n header,\n ...(documentAuthorization ? { documentAuthorization } : {}),\n expectedTargetHash: documentAuthorization\n ? header.targetHash\n : plan.expectedTargetHash,\n writerPublicKey,\n });\n if (!verified.ok) {\n throw verified.error;\n }\n}\n\nfunction assertDocumentSyncCommitCheckpointMatchesPlan(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): void {\n if (\n response.commitLsnMode === \"untracked\" &&\n response.commitLsn !== UNTRACKED_COMMIT_LSN\n ) {\n throw new Error(\n \"Document sync response untracked commit LSN must use the 0/0 sentinel\",\n );\n }\n if (\n response.commitLsn === UNTRACKED_COMMIT_LSN &&\n response.commitLsnMode !== \"untracked\"\n ) {\n throw new Error(\n \"Document sync response 0/0 commit LSN must be declared untracked\",\n );\n }\n if (response.commitLsn !== null) {\n parseWalLsn(response.commitLsn, \"Document sync response commit LSN\");\n }\n if (plan.minLsn === undefined) {\n return;\n }\n const minLsn = parseWalLsn(\n plan.minLsn,\n \"Document sync requested minimum LSN\",\n );\n if (response.commitLsn === null) {\n throw new Error(\"Document sync response commit LSN is missing\");\n }\n if (\n !(\n response.commitLsn === UNTRACKED_COMMIT_LSN &&\n response.commitLsnMode === \"untracked\"\n ) &&\n parseWalLsn(response.commitLsn, \"Document sync response commit LSN\") <\n minLsn\n ) {\n throw new Error(\"Document sync response commit LSN is stale\");\n }\n}\n\nfunction documentSyncManifestEpoch(plan: DocumentSyncPlan): number {\n if (!isPlainRecord(plan.documentManifest.state)) {\n throw new Error(\"Document sync manifest state is invalid\");\n }\n\n return readRecordPositiveInteger(\n plan.documentManifest.state,\n \"epoch\",\n \"Document sync manifest state\",\n );\n}\n\n// The one load-bearing epoch check: no returned update may claim an\n// accessEpoch newer than the current manifest epoch.\nfunction assertDocumentSyncAccessEpochsNotInFuture(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): void {\n const currentAccessEpoch = documentSyncManifestEpoch(plan);\n for (const update of response.updates) {\n if (update.accessEpoch > currentAccessEpoch) {\n throw new Error(\"Document sync response includes a future access epoch\");\n }\n }\n}\n\nfunction contentKeyBundleCanonical(\n bundle: DocumentSyncResponse[\"contentKeyBundle\"],\n): string {\n return serializeCanonical(bundle, \"content-key bundle\");\n}\n\nfunction documentSyncContentKeyBundlesByEpoch(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n): ReadonlyMap<number, DocumentSyncResponse[\"contentKeyBundle\"]> {\n if (\n contentKeyBundleCanonical(response.contentKeyBundle) !==\n contentKeyBundleCanonical(plan.sourceContentKeyBundle)\n ) {\n throw new Error(\"Document sync response content-key bundle mismatch\");\n }\n\n const bundleByEpoch = new Map<\n number,\n DocumentSyncResponse[\"contentKeyBundle\"]\n >();\n const bundles = [response.contentKeyBundle, ...response.contentKeyBundles];\n\n for (const bundle of bundles) {\n if (bundle.documentId !== plan.documentId) {\n throw new Error(\n \"Document sync response content-key bundle document mismatch\",\n );\n }\n if (\n !Number.isInteger(bundle.contentKeyEpoch) ||\n bundle.contentKeyEpoch <= 0\n ) {\n throw new Error(\n \"Document sync response content-key bundle epoch mismatch\",\n );\n }\n\n // The current epoch travels only as contentKeyBundle; the array carries\n // each older epoch once.\n if (bundleByEpoch.has(bundle.contentKeyEpoch)) {\n throw new Error(\"Document sync response repeats a content-key epoch\");\n }\n bundleByEpoch.set(bundle.contentKeyEpoch, bundle);\n }\n\n return bundleByEpoch;\n}\n\nasync function persistedDocumentSyncStateFromResponseInternal(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n options: {\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n },\n): Promise<PersistedDocumentSyncState> {\n if (response.documentId !== plan.documentId) {\n throw new Error(\"Document sync response document id mismatch\");\n }\n const contentKeyBundlesByEpoch = documentSyncContentKeyBundlesByEpoch(\n plan,\n response,\n );\n if (\n serializeCanonical(response.documentKekTargets, \"KEK targets\") !==\n serializeCanonical(plan.documentKekTargets, \"KEK targets\")\n ) {\n throw new Error(\"Document sync response KEK target mismatch\");\n }\n assertAcceptedOutgoingUpdateIdsMatchPlan(plan, response);\n assertDocumentSyncCommitCheckpointMatchesPlan(plan, response);\n assertDocumentSyncAccessEpochsNotInFuture(plan, response);\n\n await Promise.all(\n response.updates.map((update) =>\n assertDocumentSyncResponseUpdateMatchesPlan({\n contentKeyBundlesByEpoch,\n plan,\n resolveWriterPublicKey: options.resolveWriterPublicKey,\n update,\n }),\n ),\n );\n\n return serializedPersistedDocumentState({\n contentKeyBundle: response.contentKeyBundle,\n documentId: plan.documentId,\n documentKekTargets: response.documentKekTargets,\n documentManifestBundle: plan.documentManifest,\n });\n}\n\nexport async function persistedDocumentSyncStateFromResponse(\n plan: DocumentSyncPlan,\n response: DocumentSyncResponse,\n options: {\n resolveWriterPublicKey: DocumentWriterPublicKeyResolver;\n },\n): Promise<PersistedDocumentSyncState> {\n try {\n return await persistedDocumentSyncStateFromResponseInternal(\n plan,\n response,\n options,\n );\n } catch (error) {\n if (error instanceof DocumentSyncResponseUpdateContentKeyError) throw error;\n throwKeyingVerificationShapeFailure(error);\n }\n}\n\nexport function isRetryableDocumentSyncConflict(\n failure: DocumentSyncSubmitFailure,\n): boolean {\n return (\n failure.status === 409 &&\n failure.code === DOCUMENT_SYNC_ERROR_CODES.stateStale\n );\n}\n\n// The wipe gate is deliberately strict — coded 404s only, which is the right\n// failure mode for a destructive action. A bare 404 proves nothing about the\n// document: proxy/tunnel error pages, deploy-skew route misses, and\n// container-level lookups all produce one.\nexport function isUpstreamDeletedDocumentSyncFailure(\n failure: DocumentSyncSubmitFailure,\n): boolean {\n return (\n failure.status === 404 && failure.code === DOCUMENT_NOT_FOUND_ERROR_CODE\n );\n}\n"]}
|
|
@@ -60,10 +60,15 @@ export const documentSyncResponsePullPageRefinement = {
|
|
|
60
60
|
description: "pullPage.hasMore is true exactly when pullPage.nextCursor is non-null",
|
|
61
61
|
id: "response.pull-page-continuation",
|
|
62
62
|
};
|
|
63
|
+
export const documentSyncResponseHistoricalBundlesRefinement = {
|
|
64
|
+
description: "contentKeyBundles carries each content-key epoch at most once, and only epochs older than contentKeyBundle.contentKeyEpoch",
|
|
65
|
+
id: "response.historical-content-key-bundles",
|
|
66
|
+
};
|
|
63
67
|
export const documentSyncResponseRuntimeRefinements = [
|
|
64
68
|
documentSyncResponseRotationRefinement,
|
|
65
69
|
documentSyncResponseCommitLsnModeRefinement,
|
|
66
70
|
documentSyncResponseCommitLsnSentinelRefinement,
|
|
67
71
|
documentSyncResponsePullPageRefinement,
|
|
72
|
+
documentSyncResponseHistoricalBundlesRefinement,
|
|
68
73
|
];
|
|
69
74
|
//# sourceMappingURL=documentSyncRefinements.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentSyncRefinements.js","sourceRoot":"","sources":["documentSyncRefinements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yCAAyC,EAAE,MAAM,2BAA2B,CAAC;AAEtF,MAAM,CAAC,MAAM,qCAAqC,GAAG;IACnD,WAAW,EACT,oHAAoH;IACtH,EAAE,EAAE,oCAAoC;CAChC,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD;QACE,WAAW,EACT,+EAA+E;QACjF,EAAE,EAAE,oCAAoC;KACzC;IACD;QACE,WAAW,EACT,4EAA4E;QAC9E,EAAE,EAAE,sCAAsC;KAC3C;IACD;QACE,WAAW,EACT,yEAAyE;QAC3E,EAAE,EAAE,wCAAwC;KAC7C;IACD;QACE,WAAW,EAAE,uDAAuD;QACpE,EAAE,EAAE,oCAAoC;KACzC;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,kCAAkC,GAAG;IAChD;QACE,WAAW,EACT,+HAA+H;QACjI,EAAE,EAAE,qCAAqC;KAC1C;IACD;QACE,WAAW,EACT,4HAA4H;QAC9H,EAAE,EAAE,+BAA+B;KACpC;IACD;QACE,WAAW,EACT,wGAAwG;QAC1G,EAAE,EAAE,mCAAmC;KACxC;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,qCAAqC,GAAG;IACnD,GAAG,sCAAsC;IACzC,GAAG,kCAAkC;IACrC,qCAAqC;CAC7B,CAAC;AAEX,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,WAAW,EAAE,gFAAgF,yCAAyC,sBAAsB;IAC5J,EAAE,EAAE,uDAAuD;CACnD,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,WAAW,EACT,oHAAoH;IACtH,EAAE,EAAE,qCAAqC;CACjC,CAAC;AAEX,MAAM,CAAC,MAAM,2CAA2C,GAAG;IACzD,WAAW,EACT,iEAAiE;IACnE,EAAE,EAAE,wCAAwC;CACpC,CAAC;AAEX,MAAM,CAAC,MAAM,+CAA+C,GAAG;IAC7D,WAAW,EACT,yEAAyE;IAC3E,EAAE,EAAE,oCAAoC;CAChC,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,WAAW,EACT,uEAAuE;IACzE,EAAE,EAAE,iCAAiC;CAC7B,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,sCAAsC;IACtC,2CAA2C;IAC3C,+CAA+C;IAC/C,sCAAsC;
|
|
1
|
+
{"version":3,"file":"documentSyncRefinements.js","sourceRoot":"","sources":["documentSyncRefinements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yCAAyC,EAAE,MAAM,2BAA2B,CAAC;AAEtF,MAAM,CAAC,MAAM,qCAAqC,GAAG;IACnD,WAAW,EACT,oHAAoH;IACtH,EAAE,EAAE,oCAAoC;CAChC,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD;QACE,WAAW,EACT,+EAA+E;QACjF,EAAE,EAAE,oCAAoC;KACzC;IACD;QACE,WAAW,EACT,4EAA4E;QAC9E,EAAE,EAAE,sCAAsC;KAC3C;IACD;QACE,WAAW,EACT,yEAAyE;QAC3E,EAAE,EAAE,wCAAwC;KAC7C;IACD;QACE,WAAW,EAAE,uDAAuD;QACpE,EAAE,EAAE,oCAAoC;KACzC;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,kCAAkC,GAAG;IAChD;QACE,WAAW,EACT,+HAA+H;QACjI,EAAE,EAAE,qCAAqC;KAC1C;IACD;QACE,WAAW,EACT,4HAA4H;QAC9H,EAAE,EAAE,+BAA+B;KACpC;IACD;QACE,WAAW,EACT,wGAAwG;QAC1G,EAAE,EAAE,mCAAmC;KACxC;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,qCAAqC,GAAG;IACnD,GAAG,sCAAsC;IACzC,GAAG,kCAAkC;IACrC,qCAAqC;CAC7B,CAAC;AAEX,MAAM,CAAC,MAAM,6BAA6B,GAAG;IAC3C,WAAW,EAAE,gFAAgF,yCAAyC,sBAAsB;IAC5J,EAAE,EAAE,uDAAuD;CACnD,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,WAAW,EACT,oHAAoH;IACtH,EAAE,EAAE,qCAAqC;CACjC,CAAC;AAEX,MAAM,CAAC,MAAM,2CAA2C,GAAG;IACzD,WAAW,EACT,iEAAiE;IACnE,EAAE,EAAE,wCAAwC;CACpC,CAAC;AAEX,MAAM,CAAC,MAAM,+CAA+C,GAAG;IAC7D,WAAW,EACT,yEAAyE;IAC3E,EAAE,EAAE,oCAAoC;CAChC,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,WAAW,EACT,uEAAuE;IACzE,EAAE,EAAE,iCAAiC;CAC7B,CAAC;AAEX,MAAM,CAAC,MAAM,+CAA+C,GAAG;IAC7D,WAAW,EACT,4HAA4H;IAC9H,EAAE,EAAE,yCAAyC;CACrC,CAAC;AAEX,MAAM,CAAC,MAAM,sCAAsC,GAAG;IACpD,sCAAsC;IACtC,2CAA2C;IAC3C,+CAA+C;IAC/C,sCAAsC;IACtC,+CAA+C;CACvC,CAAC","sourcesContent":["import { MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS } from \"./util/documentSyncLimits\";\n\nexport const documentSyncRequestRotationRefinement = {\n description:\n \"checkpointKind, checkpointPayloadKind, and sourceVersionVector must be absent together or form a rotation baseline\",\n id: \"request.rotation-checkpoint-fields\",\n} as const;\n\nexport const documentSyncRequestEnvelopeRefinements = [\n {\n description:\n \"inlineRekeyCommitId must be present exactly when containerRekeys is non-empty\",\n id: \"request.inline-rekey-commit-marker\",\n },\n {\n description:\n \"authorizingContainerPathRefs is required when outgoingUpdates is non-empty\",\n id: \"request.authorizing-paths-for-writes\",\n },\n {\n description:\n \"containerRekeys may be non-empty only when outgoingUpdates is non-empty\",\n id: \"request.container-rekeys-require-write\",\n },\n {\n description: \"outgoing update ids must be unique within the request\",\n id: \"request.unique-outgoing-update-ids\",\n },\n] as const;\n\nexport const documentSyncRequestPullRefinements = [\n {\n description:\n \"a request carrying pullCursor must not contain outgoing updates, container rekeys, a content-key bundle, or authorizing paths\",\n id: \"request.pull-continuation-read-only\",\n },\n {\n description:\n 'historyMode \"raw\" must not be combined with outgoing updates, container rekeys, a content-key bundle, or authorizing paths',\n id: \"request.raw-history-read-only\",\n },\n {\n description:\n 'historyMode \"raw\" requires localVersionVector to be null so the complete retained history is requested',\n id: \"request.raw-history-null-frontier\",\n },\n] as const;\n\nexport const documentSyncRequestRuntimeRefinements = [\n ...documentSyncRequestEnvelopeRefinements,\n ...documentSyncRequestPullRefinements,\n documentSyncRequestRotationRefinement,\n] as const;\n\nexport const documentLinkSetPathRefinement = {\n description: `authorizingContainerPathRefs and targetContainerPathRefs may contain at most ${MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS} references in total`,\n id: \"request.post-link-authorization-path-total-references\",\n} as const;\n\nexport const documentSyncResponseRotationRefinement = {\n description:\n \"checkpointKind, checkpointPayloadKind, and sourceVersionVector must be absent together or form a rotation baseline\",\n id: \"response.rotation-checkpoint-fields\",\n} as const;\n\nexport const documentSyncResponseCommitLsnModeRefinement = {\n description:\n 'commitLsnMode \"untracked\" requires the commitLsn \"0/0\" sentinel',\n id: \"response.untracked-commit-lsn-sentinel\",\n} as const;\n\nexport const documentSyncResponseCommitLsnSentinelRefinement = {\n description:\n 'commitLsn \"0/0\" requires commitLsnMode \"untracked\" in the same response',\n id: \"response.untracked-commit-lsn-mode\",\n} as const;\n\nexport const documentSyncResponsePullPageRefinement = {\n description:\n \"pullPage.hasMore is true exactly when pullPage.nextCursor is non-null\",\n id: \"response.pull-page-continuation\",\n} as const;\n\nexport const documentSyncResponseHistoricalBundlesRefinement = {\n description:\n \"contentKeyBundles carries each content-key epoch at most once, and only epochs older than contentKeyBundle.contentKeyEpoch\",\n id: \"response.historical-content-key-bundles\",\n} as const;\n\nexport const documentSyncResponseRuntimeRefinements = [\n documentSyncResponseRotationRefinement,\n documentSyncResponseCommitLsnModeRefinement,\n documentSyncResponseCommitLsnSentinelRefinement,\n documentSyncResponsePullPageRefinement,\n documentSyncResponseHistoricalBundlesRefinement,\n] as const;\n"]}
|
|
@@ -746,6 +746,9 @@ export declare const documentSyncOperation: Omit<{
|
|
|
746
746
|
}, {
|
|
747
747
|
readonly description: "pullPage.hasMore is true exactly when pullPage.nextCursor is non-null";
|
|
748
748
|
readonly id: "response.pull-page-continuation";
|
|
749
|
+
}, {
|
|
750
|
+
readonly description: "contentKeyBundles carries each content-key epoch at most once, and only epochs older than contentKeyBundle.contentKeyEpoch";
|
|
751
|
+
readonly id: "response.historical-content-key-bundles";
|
|
749
752
|
}];
|
|
750
753
|
}, "failureResponses" | "failureStatuses"> & {
|
|
751
754
|
readonly failureResponses: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { classifyDocumentSyncCheckpointFields, DOCUMENT_SYNC_CHECKPOINT_FIELD_SET_ISSUE_MESSAGE, DOCUMENT_SYNC_ROTATION_CHECKPOINT_KIND, DOCUMENT_SYNC_ROTATION_CHECKPOINT_PAYLOAD_KIND, } from "../documentSyncCheckpoint.js";
|
|
3
|
-
import { documentSyncResponseCommitLsnModeRefinement, documentSyncResponseCommitLsnSentinelRefinement, documentSyncResponsePullPageRefinement, documentSyncResponseRotationRefinement, } from "../documentSyncRefinements.js";
|
|
3
|
+
import { documentSyncResponseCommitLsnModeRefinement, documentSyncResponseCommitLsnSentinelRefinement, documentSyncResponseHistoricalBundlesRefinement, documentSyncResponsePullPageRefinement, documentSyncResponseRotationRefinement, } from "../documentSyncRefinements.js";
|
|
4
4
|
import { registerJsonSchemaRuntimeRefinements } from "../jsonSchema.js";
|
|
5
5
|
import { arraySchema, boundedNonEmptyStringSchema, loosePlainObject, nonEmptyArraySchema, nonEmptyStringSchema, plainObjectSchema, positiveIntegerSchema, } from "../schema.js";
|
|
6
6
|
import { MAX_DOCUMENT_SYNC_OUTGOING_UPDATES, MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH, MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES, } from "../util/documentSyncLimits.js";
|
|
@@ -64,6 +64,38 @@ export const DocumentSyncPullPageResponseSchema = loosePlainObject({
|
|
|
64
64
|
hasMore: z.boolean(),
|
|
65
65
|
nextCursor: boundedNonEmptyStringSchema(MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH).nullable(),
|
|
66
66
|
});
|
|
67
|
+
function contentKeyEpochOf(bundle) {
|
|
68
|
+
if (typeof bundle !== "object" || bundle === null)
|
|
69
|
+
return null;
|
|
70
|
+
const contentKeyEpoch = Reflect.get(bundle, "contentKeyEpoch");
|
|
71
|
+
return typeof contentKeyEpoch === "number" ? contentKeyEpoch : null;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The current epoch's bundle travels only as `contentKeyBundle`, so the array
|
|
75
|
+
* holds the distinct older epochs that served updates cite. Field issues do not
|
|
76
|
+
* stop this object refinement, so it judges only epochs that parsed.
|
|
77
|
+
*/
|
|
78
|
+
function addHistoricalContentKeyBundleIssues(response, context) {
|
|
79
|
+
const currentEpoch = contentKeyEpochOf(response.contentKeyBundle);
|
|
80
|
+
if (currentEpoch === null || !Array.isArray(response.contentKeyBundles)) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const seenEpochs = new Set();
|
|
84
|
+
// forEach skips holes exactly as arraySchema's item validation does.
|
|
85
|
+
response.contentKeyBundles.forEach((bundle, index) => {
|
|
86
|
+
const contentKeyEpoch = contentKeyEpochOf(bundle);
|
|
87
|
+
if (contentKeyEpoch === null)
|
|
88
|
+
return;
|
|
89
|
+
if (contentKeyEpoch >= currentEpoch || seenEpochs.has(contentKeyEpoch)) {
|
|
90
|
+
context.addIssue({
|
|
91
|
+
code: "custom",
|
|
92
|
+
message: "content-key bundles must be distinct epochs older than contentKeyBundle",
|
|
93
|
+
path: ["contentKeyBundles", index, "contentKeyEpoch"],
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
seenEpochs.add(contentKeyEpoch);
|
|
97
|
+
});
|
|
98
|
+
}
|
|
67
99
|
export const DocumentSyncResponseSchema = registerJsonSchemaRuntimeRefinements(loosePlainObject({
|
|
68
100
|
acceptedOutgoingUpdateIds: arraySchema(z.string(), MAX_DOCUMENT_SYNC_OUTGOING_UPDATES),
|
|
69
101
|
commitLsn: z.string().nullable(),
|
|
@@ -99,9 +131,11 @@ export const DocumentSyncResponseSchema = registerJsonSchemaRuntimeRefinements(l
|
|
|
99
131
|
path: ["commitLsnMode"],
|
|
100
132
|
});
|
|
101
133
|
}
|
|
134
|
+
addHistoricalContentKeyBundleIssues(response, context);
|
|
102
135
|
}), [
|
|
103
136
|
documentSyncResponseCommitLsnModeRefinement,
|
|
104
137
|
documentSyncResponseCommitLsnSentinelRefinement,
|
|
105
138
|
documentSyncResponsePullPageRefinement,
|
|
139
|
+
documentSyncResponseHistoricalBundlesRefinement,
|
|
106
140
|
]);
|
|
107
141
|
//# sourceMappingURL=documentSyncSchema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentSyncSchema.js","sourceRoot":"","sources":["documentSyncSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport {\n classifyDocumentSyncCheckpointFields,\n DOCUMENT_SYNC_CHECKPOINT_FIELD_SET_ISSUE_MESSAGE,\n DOCUMENT_SYNC_ROTATION_CHECKPOINT_KIND,\n DOCUMENT_SYNC_ROTATION_CHECKPOINT_PAYLOAD_KIND,\n} from \"../documentSyncCheckpoint\";\nimport {\n documentSyncResponseCommitLsnModeRefinement,\n documentSyncResponseCommitLsnSentinelRefinement,\n documentSyncResponsePullPageRefinement,\n documentSyncResponseRotationRefinement,\n} from \"../documentSyncRefinements\";\nimport { registerJsonSchemaRuntimeRefinements } from \"../jsonSchema\";\nimport {\n arraySchema,\n boundedNonEmptyStringSchema,\n loosePlainObject,\n nonEmptyArraySchema,\n nonEmptyStringSchema,\n plainObjectSchema,\n positiveIntegerSchema,\n} from \"../schema\";\nimport {\n MAX_DOCUMENT_SYNC_OUTGOING_UPDATES,\n MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH,\n MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES,\n} from \"../util/documentSyncLimits\";\n\nexport const DocumentContentKeyTargetEnvelopeResponseSchema = loosePlainObject({\n containerId: nonEmptyStringSchema,\n containerKeyEpoch: positiveIntegerSchema,\n containerKeyEpochId: nonEmptyStringSchema,\n containerManifestHash: nonEmptyStringSchema,\n wrappedKey: nonEmptyStringSchema,\n wrappingMetadata: plainObjectSchema,\n});\n\nexport type DocumentContentKeyTargetEnvelopeResponse = z.infer<\n typeof DocumentContentKeyTargetEnvelopeResponseSchema\n>;\n\nconst DocumentContentKeyTargetResponseSchema = loosePlainObject({\n containerId: nonEmptyStringSchema,\n containerKeyEpoch: positiveIntegerSchema,\n containerKeyEpochId: nonEmptyStringSchema,\n containerManifestHash: nonEmptyStringSchema,\n});\n\nexport const DocumentKekTargetsResponseSchema = loosePlainObject({\n documentId: z.string(),\n documentKeyTargetHash: nonEmptyStringSchema,\n linkedContainerKeyEpochIds: nonEmptyArraySchema(nonEmptyStringSchema),\n linkedContainerManifestHashes: nonEmptyArraySchema(nonEmptyStringSchema),\n linkSetManifestHash: nonEmptyStringSchema,\n targets: nonEmptyArraySchema(plainObjectSchema),\n});\n\nexport type DocumentKekTargetsResponse = z.infer<\n typeof DocumentKekTargetsResponseSchema\n>;\n\nexport const DocumentContentKeyBundleResponseSchema = loosePlainObject({\n contentKeyEpoch: positiveIntegerSchema,\n documentId: z.string(),\n linkSetManifestHash: nonEmptyStringSchema,\n targetHash: nonEmptyStringSchema,\n targets: nonEmptyArraySchema(DocumentContentKeyTargetEnvelopeResponseSchema),\n});\n\nexport type DocumentContentKeyBundleResponse = z.infer<\n typeof DocumentContentKeyBundleResponseSchema\n>;\n\nexport const DocumentSyncUpdateResponseSchema =\n registerJsonSchemaRuntimeRefinements(\n loosePlainObject({\n accessEpoch: positiveIntegerSchema,\n authorizationTargets: nonEmptyArraySchema(\n DocumentContentKeyTargetResponseSchema,\n ),\n authorFingerprint: z.string(),\n checkpointKind: z\n .literal(DOCUMENT_SYNC_ROTATION_CHECKPOINT_KIND)\n .optional(),\n checkpointPayloadKind: z\n .literal(DOCUMENT_SYNC_ROTATION_CHECKPOINT_PAYLOAD_KIND)\n .optional(),\n createdAt: z.string(),\n documentId: z.string(),\n encryptedData: z.string(),\n id: z.string(),\n partialEndVersionVector: z.string(),\n partialStartVersionVector: z.string(),\n plaintextHash: nonEmptyStringSchema,\n sourceVersionVector: z.string().min(1).optional(),\n writeHeader: plainObjectSchema,\n }).superRefine((update, context) => {\n if (classifyDocumentSyncCheckpointFields(update) === \"invalid\") {\n context.addIssue({\n code: \"custom\",\n message: DOCUMENT_SYNC_CHECKPOINT_FIELD_SET_ISSUE_MESSAGE,\n });\n }\n }),\n [documentSyncResponseRotationRefinement],\n );\n\nexport type DocumentSyncUpdateResponse = z.infer<\n typeof DocumentSyncUpdateResponseSchema\n>;\n\nexport const DocumentSyncPullPageResponseSchema = loosePlainObject({\n hasMore: z.boolean(),\n nextCursor: boundedNonEmptyStringSchema(\n MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH,\n ).nullable(),\n});\n\nexport type DocumentSyncPullPageResponse = z.infer<\n typeof DocumentSyncPullPageResponseSchema\n>;\n\nexport const DocumentSyncResponseSchema = registerJsonSchemaRuntimeRefinements(\n loosePlainObject({\n acceptedOutgoingUpdateIds: arraySchema(\n z.string(),\n MAX_DOCUMENT_SYNC_OUTGOING_UPDATES,\n ),\n commitLsn: z.string().nullable(),\n commitLsnMode: z.union([z.literal(\"tracked\"), z.literal(\"untracked\")]),\n contentKeyBundle: DocumentContentKeyBundleResponseSchema,\n contentKeyBundles: arraySchema(DocumentContentKeyBundleResponseSchema),\n documentId: z.string(),\n documentKekTargets: DocumentKekTargetsResponseSchema,\n pullPage: DocumentSyncPullPageResponseSchema,\n updates: arraySchema(\n DocumentSyncUpdateResponseSchema,\n MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES,\n ),\n }).superRefine((response, context) => {\n if (\n response.pullPage !== undefined &&\n response.pullPage.hasMore !== (response.pullPage.nextCursor !== null)\n ) {\n context.addIssue({\n code: \"custom\",\n message: \"pull page continuation does not match hasMore\",\n path: [\"pullPage\", \"nextCursor\"],\n });\n }\n if (\n response.commitLsnMode === \"untracked\" &&\n response.commitLsn !== \"0/0\"\n ) {\n context.addIssue({\n code: \"custom\",\n message: \"untracked commit LSN must use the 0/0 sentinel\",\n path: [\"commitLsn\"],\n });\n }\n if (\n response.commitLsn === \"0/0\" &&\n response.commitLsnMode !== \"untracked\"\n ) {\n context.addIssue({\n code: \"custom\",\n message: \"0/0 commit LSN must be declared untracked\",\n path: [\"commitLsnMode\"],\n });\n }\n }),\n [\n documentSyncResponseCommitLsnModeRefinement,\n documentSyncResponseCommitLsnSentinelRefinement,\n documentSyncResponsePullPageRefinement,\n ],\n);\n\nexport type DocumentSyncResponse = z.infer<typeof DocumentSyncResponseSchema>;\n"]}
|
|
1
|
+
{"version":3,"file":"documentSyncSchema.js","sourceRoot":"","sources":["documentSyncSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport {\n classifyDocumentSyncCheckpointFields,\n DOCUMENT_SYNC_CHECKPOINT_FIELD_SET_ISSUE_MESSAGE,\n DOCUMENT_SYNC_ROTATION_CHECKPOINT_KIND,\n DOCUMENT_SYNC_ROTATION_CHECKPOINT_PAYLOAD_KIND,\n} from \"../documentSyncCheckpoint\";\nimport {\n documentSyncResponseCommitLsnModeRefinement,\n documentSyncResponseCommitLsnSentinelRefinement,\n documentSyncResponseHistoricalBundlesRefinement,\n documentSyncResponsePullPageRefinement,\n documentSyncResponseRotationRefinement,\n} from \"../documentSyncRefinements\";\nimport { registerJsonSchemaRuntimeRefinements } from \"../jsonSchema\";\nimport {\n arraySchema,\n boundedNonEmptyStringSchema,\n loosePlainObject,\n nonEmptyArraySchema,\n nonEmptyStringSchema,\n plainObjectSchema,\n positiveIntegerSchema,\n} from \"../schema\";\nimport {\n MAX_DOCUMENT_SYNC_OUTGOING_UPDATES,\n MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH,\n MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES,\n} from \"../util/documentSyncLimits\";\n\nexport const DocumentContentKeyTargetEnvelopeResponseSchema = loosePlainObject({\n containerId: nonEmptyStringSchema,\n containerKeyEpoch: positiveIntegerSchema,\n containerKeyEpochId: nonEmptyStringSchema,\n containerManifestHash: nonEmptyStringSchema,\n wrappedKey: nonEmptyStringSchema,\n wrappingMetadata: plainObjectSchema,\n});\n\nexport type DocumentContentKeyTargetEnvelopeResponse = z.infer<\n typeof DocumentContentKeyTargetEnvelopeResponseSchema\n>;\n\nconst DocumentContentKeyTargetResponseSchema = loosePlainObject({\n containerId: nonEmptyStringSchema,\n containerKeyEpoch: positiveIntegerSchema,\n containerKeyEpochId: nonEmptyStringSchema,\n containerManifestHash: nonEmptyStringSchema,\n});\n\nexport const DocumentKekTargetsResponseSchema = loosePlainObject({\n documentId: z.string(),\n documentKeyTargetHash: nonEmptyStringSchema,\n linkedContainerKeyEpochIds: nonEmptyArraySchema(nonEmptyStringSchema),\n linkedContainerManifestHashes: nonEmptyArraySchema(nonEmptyStringSchema),\n linkSetManifestHash: nonEmptyStringSchema,\n targets: nonEmptyArraySchema(plainObjectSchema),\n});\n\nexport type DocumentKekTargetsResponse = z.infer<\n typeof DocumentKekTargetsResponseSchema\n>;\n\nexport const DocumentContentKeyBundleResponseSchema = loosePlainObject({\n contentKeyEpoch: positiveIntegerSchema,\n documentId: z.string(),\n linkSetManifestHash: nonEmptyStringSchema,\n targetHash: nonEmptyStringSchema,\n targets: nonEmptyArraySchema(DocumentContentKeyTargetEnvelopeResponseSchema),\n});\n\nexport type DocumentContentKeyBundleResponse = z.infer<\n typeof DocumentContentKeyBundleResponseSchema\n>;\n\nexport const DocumentSyncUpdateResponseSchema =\n registerJsonSchemaRuntimeRefinements(\n loosePlainObject({\n accessEpoch: positiveIntegerSchema,\n authorizationTargets: nonEmptyArraySchema(\n DocumentContentKeyTargetResponseSchema,\n ),\n authorFingerprint: z.string(),\n checkpointKind: z\n .literal(DOCUMENT_SYNC_ROTATION_CHECKPOINT_KIND)\n .optional(),\n checkpointPayloadKind: z\n .literal(DOCUMENT_SYNC_ROTATION_CHECKPOINT_PAYLOAD_KIND)\n .optional(),\n createdAt: z.string(),\n documentId: z.string(),\n encryptedData: z.string(),\n id: z.string(),\n partialEndVersionVector: z.string(),\n partialStartVersionVector: z.string(),\n plaintextHash: nonEmptyStringSchema,\n sourceVersionVector: z.string().min(1).optional(),\n writeHeader: plainObjectSchema,\n }).superRefine((update, context) => {\n if (classifyDocumentSyncCheckpointFields(update) === \"invalid\") {\n context.addIssue({\n code: \"custom\",\n message: DOCUMENT_SYNC_CHECKPOINT_FIELD_SET_ISSUE_MESSAGE,\n });\n }\n }),\n [documentSyncResponseRotationRefinement],\n );\n\nexport type DocumentSyncUpdateResponse = z.infer<\n typeof DocumentSyncUpdateResponseSchema\n>;\n\nexport const DocumentSyncPullPageResponseSchema = loosePlainObject({\n hasMore: z.boolean(),\n nextCursor: boundedNonEmptyStringSchema(\n MAX_DOCUMENT_SYNC_PULL_CURSOR_LENGTH,\n ).nullable(),\n});\n\nexport type DocumentSyncPullPageResponse = z.infer<\n typeof DocumentSyncPullPageResponseSchema\n>;\n\nfunction contentKeyEpochOf(bundle: unknown): number | null {\n if (typeof bundle !== \"object\" || bundle === null) return null;\n const contentKeyEpoch: unknown = Reflect.get(bundle, \"contentKeyEpoch\");\n return typeof contentKeyEpoch === \"number\" ? contentKeyEpoch : null;\n}\n\n/**\n * The current epoch's bundle travels only as `contentKeyBundle`, so the array\n * holds the distinct older epochs that served updates cite. Field issues do not\n * stop this object refinement, so it judges only epochs that parsed.\n */\nfunction addHistoricalContentKeyBundleIssues(\n response: {\n readonly contentKeyBundle: unknown;\n readonly contentKeyBundles: unknown;\n },\n context: z.RefinementCtx,\n): void {\n const currentEpoch = contentKeyEpochOf(response.contentKeyBundle);\n if (currentEpoch === null || !Array.isArray(response.contentKeyBundles)) {\n return;\n }\n const seenEpochs = new Set<number>();\n // forEach skips holes exactly as arraySchema's item validation does.\n response.contentKeyBundles.forEach((bundle: unknown, index: number) => {\n const contentKeyEpoch = contentKeyEpochOf(bundle);\n if (contentKeyEpoch === null) return;\n if (contentKeyEpoch >= currentEpoch || seenEpochs.has(contentKeyEpoch)) {\n context.addIssue({\n code: \"custom\",\n message:\n \"content-key bundles must be distinct epochs older than contentKeyBundle\",\n path: [\"contentKeyBundles\", index, \"contentKeyEpoch\"],\n });\n }\n seenEpochs.add(contentKeyEpoch);\n });\n}\n\nexport const DocumentSyncResponseSchema = registerJsonSchemaRuntimeRefinements(\n loosePlainObject({\n acceptedOutgoingUpdateIds: arraySchema(\n z.string(),\n MAX_DOCUMENT_SYNC_OUTGOING_UPDATES,\n ),\n commitLsn: z.string().nullable(),\n commitLsnMode: z.union([z.literal(\"tracked\"), z.literal(\"untracked\")]),\n contentKeyBundle: DocumentContentKeyBundleResponseSchema,\n contentKeyBundles: arraySchema(DocumentContentKeyBundleResponseSchema),\n documentId: z.string(),\n documentKekTargets: DocumentKekTargetsResponseSchema,\n pullPage: DocumentSyncPullPageResponseSchema,\n updates: arraySchema(\n DocumentSyncUpdateResponseSchema,\n MAX_DOCUMENT_SYNC_RESPONSE_PAGE_UPDATES,\n ),\n }).superRefine((response, context) => {\n if (\n response.pullPage !== undefined &&\n response.pullPage.hasMore !== (response.pullPage.nextCursor !== null)\n ) {\n context.addIssue({\n code: \"custom\",\n message: \"pull page continuation does not match hasMore\",\n path: [\"pullPage\", \"nextCursor\"],\n });\n }\n if (\n response.commitLsnMode === \"untracked\" &&\n response.commitLsn !== \"0/0\"\n ) {\n context.addIssue({\n code: \"custom\",\n message: \"untracked commit LSN must use the 0/0 sentinel\",\n path: [\"commitLsn\"],\n });\n }\n if (\n response.commitLsn === \"0/0\" &&\n response.commitLsnMode !== \"untracked\"\n ) {\n context.addIssue({\n code: \"custom\",\n message: \"0/0 commit LSN must be declared untracked\",\n path: [\"commitLsnMode\"],\n });\n }\n addHistoricalContentKeyBundleIssues(response, context);\n }),\n [\n documentSyncResponseCommitLsnModeRefinement,\n documentSyncResponseCommitLsnSentinelRefinement,\n documentSyncResponsePullPageRefinement,\n documentSyncResponseHistoricalBundlesRefinement,\n ],\n);\n\nexport type DocumentSyncResponse = z.infer<typeof DocumentSyncResponseSchema>;\n"]}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@ import { encryptDocumentPendingUpdate } from "../../data/documents/shared/crypto
|
|
|
3
3
|
import { assertDocumentSyncUpdateEncryptedRecord } from "../../data/documents/shared/documentSyncUpdateDecryption.js";
|
|
4
4
|
import { isDocumentSyncUpdateIsolationError, isolateDocumentSyncBatchError, isolateDocumentSyncUpdateError, } from "../../data/documents/shared/documentSyncUpdateIsolation.js";
|
|
5
5
|
import { ContainerKekHistoryUnavailableError, collectContainerKeksForDocumentSync, DocumentContentKeyUnavailableError, DocumentHistoryUnavailableError, unwrapDocumentContentKeyFromBundle, } from "../../data/documents/shared/projection.js";
|
|
6
|
-
import { readWriteHeader
|
|
6
|
+
import { readWriteHeader } from "../../data/documents/shared/readers.js";
|
|
7
7
|
import { projectionVerificationOptions } from "../../data/documents/shared/types.js";
|
|
8
8
|
import { isKeyingVerificationError } from "../../data/keyingProjectionVerification/error.js";
|
|
9
9
|
function isRawHistoryUnavailableCause(error) {
|
|
@@ -89,15 +89,10 @@ function syncResponseContentKeyBundlesByEpoch(response) {
|
|
|
89
89
|
response.contentKeyBundle,
|
|
90
90
|
...response.contentKeyBundles,
|
|
91
91
|
]) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
byEpoch.set(bundle.contentKeyEpoch, bundle);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
if (serializeCanonical(existing, "content-key bundle") !==
|
|
98
|
-
serializeCanonical(bundle, "content-key bundle")) {
|
|
99
|
-
throw new Error("Document sync response content-key bundle conflict");
|
|
92
|
+
if (byEpoch.has(bundle.contentKeyEpoch)) {
|
|
93
|
+
throw new Error("Document sync response repeats a content-key epoch");
|
|
100
94
|
}
|
|
95
|
+
byEpoch.set(bundle.contentKeyEpoch, bundle);
|
|
101
96
|
}
|
|
102
97
|
return byEpoch;
|
|
103
98
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncContentKeys.js","sourceRoot":"","sources":["syncContentKeys.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DocumentSyncResponse,\n DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { importContentKeyMaterial } from \"../../data/documents/shared/contentRecordKeys\";\nimport { encryptDocumentPendingUpdate } from \"../../data/documents/shared/crypto\";\nimport { assertDocumentSyncUpdateEncryptedRecord } from \"../../data/documents/shared/documentSyncUpdateDecryption\";\nimport {\n isDocumentSyncUpdateIsolationError,\n isolateDocumentSyncBatchError,\n isolateDocumentSyncUpdateError,\n} from \"../../data/documents/shared/documentSyncUpdateIsolation\";\nimport {\n ContainerKekHistoryUnavailableError,\n collectContainerKeksForDocumentSync,\n DocumentContentKeyUnavailableError,\n DocumentHistoryUnavailableError,\n unwrapDocumentContentKeyFromBundle,\n} from \"../../data/documents/shared/projection\";\nimport {\n readWriteHeader,\n serializeCanonical,\n} from \"../../data/documents/shared/readers\";\nimport type {\n DocumentSyncPreparedUpdate,\n ProjectionVerificationOptions,\n} from \"../../data/documents/shared/types\";\nimport { projectionVerificationOptions } from \"../../data/documents/shared/types\";\nimport { isKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport type { PendingUpdateRecord } from \"../../data/sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\ntype SyncResponseUpdate = DocumentSyncResponse[\"updates\"][number];\ntype RawHistoryUnavailableCause =\n | DocumentContentKeyUnavailableError\n | DocumentHistoryUnavailableError;\n\nfunction isRawHistoryUnavailableCause(\n error: unknown,\n): error is RawHistoryUnavailableCause {\n return (\n error instanceof DocumentContentKeyUnavailableError ||\n (error instanceof DocumentHistoryUnavailableError &&\n error.historyCause instanceof ContainerKekHistoryUnavailableError)\n );\n}\n\ntype RawHistoryUnavailableValidator = (input: {\n contentKeysByEpoch: ReadonlyMap<number, Uint8Array>;\n updates: readonly SyncResponseUpdate[];\n}) => void | Promise<void>;\n\n/** A raw recovery cannot reconstruct history without this retained epoch. */\nexport class DocumentRawHistoryUnavailableError extends Error {\n readonly code = \"document_raw_history_epoch_unavailable\";\n\n constructor(\n readonly contentKeyEpoch: number,\n cause: unknown,\n ) {\n super(\n `Document raw-history recovery cannot decrypt content-key epoch ${contentKeyEpoch}`,\n { cause },\n );\n this.name = \"DocumentRawHistoryUnavailableError\";\n }\n}\n\nasync function assertResponseEncryptedRecords(\n updates: readonly SyncResponseUpdate[],\n): Promise<void> {\n const inspections = await Promise.allSettled(\n updates.map(assertDocumentSyncUpdateEncryptedRecord),\n );\n const failures = inspections.flatMap((inspection, index) =>\n inspection.status === \"rejected\"\n ? [{ index, reason: inspection.reason }]\n : [],\n );\n const firstFailure = failures[0];\n if (failures.length === 1 && firstFailure) {\n throw firstFailure.reason;\n }\n if (failures.length > 1 && firstFailure) {\n const stage =\n isDocumentSyncUpdateIsolationError(firstFailure.reason) &&\n failures.every(\n ({ reason }) =>\n isDocumentSyncUpdateIsolationError(reason) &&\n reason.stage === firstFailure.reason.stage,\n )\n ? firstFailure.reason.stage\n : \"encrypted_record\";\n throw isolateDocumentSyncBatchError({\n cause: new Error(\"Multiple document encrypted records are invalid\"),\n stage,\n updateIds: failures.map(({ index }) => updates[index]?.id ?? \"unknown\"),\n });\n }\n}\n\nasync function throwRawHistoryUnavailable(input: {\n contentKeysByEpoch: ReadonlyMap<number, Uint8Array>;\n response: DocumentSyncResponse;\n unavailable: { cause: RawHistoryUnavailableCause; contentKeyEpoch: number };\n validate?: RawHistoryUnavailableValidator | undefined;\n}): Promise<never> {\n await assertResponseEncryptedRecords(input.response.updates);\n await input.validate?.({\n contentKeysByEpoch: input.contentKeysByEpoch,\n updates: input.response.updates.filter((update) =>\n input.contentKeysByEpoch.has(\n readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n ).contentKeyEpoch,\n ),\n ),\n });\n throw new DocumentRawHistoryUnavailableError(\n input.unavailable.contentKeyEpoch,\n input.unavailable.cause,\n );\n}\n\nexport async function prepareDocumentOutgoingUpdates(input: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n documentId: string;\n organizationId: string;\n pendingUpdates: readonly PendingUpdateRecord[];\n}): Promise<DocumentSyncPreparedUpdate[]> {\n if (input.pendingUpdates.length === 0) {\n return [];\n }\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n\n return Promise.all(\n input.pendingUpdates.map(async (update) => {\n const encrypted = await encryptDocumentPendingUpdate({\n contentKeyMaterial,\n contentKeyEpoch: input.contentKeyEpoch,\n documentId: input.documentId,\n organizationId: input.organizationId,\n update,\n });\n\n return {\n contentRecordId: encrypted.contentRecordId,\n encryptedData: encrypted.encryptedData,\n id: update.id,\n partialStartVersionVector: update.partialStartVersionVector,\n partialEndVersionVector: update.partialEndVersionVector,\n metadataHash: encrypted.metadataHash,\n ciphertextHash: encrypted.ciphertextHash,\n plaintextHash: encrypted.plaintextHash,\n ...(update.sourceVersionVector\n ? {\n checkpointKind: \"rotate_baseline\" as const,\n checkpointPayloadKind: \"full_history_snapshot\" as const,\n sourceVersionVector: update.sourceVersionVector,\n }\n : {}),\n };\n }),\n );\n}\n\nfunction syncResponseContentKeyBundlesByEpoch(\n response: DocumentSyncResponse,\n): ReadonlyMap<number, DocumentSyncResponse[\"contentKeyBundle\"]> {\n const byEpoch = new Map<number, DocumentSyncResponse[\"contentKeyBundle\"]>();\n\n for (const bundle of [\n response.contentKeyBundle,\n ...response.contentKeyBundles,\n ]) {\n const existing = byEpoch.get(bundle.contentKeyEpoch);\n if (!existing) {\n byEpoch.set(bundle.contentKeyEpoch, bundle);\n continue;\n }\n if (\n serializeCanonical(existing, \"content-key bundle\") !==\n serializeCanonical(bundle, \"content-key bundle\")\n ) {\n throw new Error(\"Document sync response content-key bundle conflict\");\n }\n }\n\n return byEpoch;\n}\n\nfunction syncResponseUpdatesByContentKeyEpoch(\n response: DocumentSyncResponse,\n): ReadonlyMap<number, readonly SyncResponseUpdate[]> {\n const updatesByEpoch = new Map<number, SyncResponseUpdate[]>();\n for (const update of response.updates) {\n let contentKeyEpoch: number;\n try {\n contentKeyEpoch = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n ).contentKeyEpoch;\n } catch (error) {\n throw isolateDocumentSyncUpdateError({\n cause: error,\n responseUpdate: update,\n stage: \"write_header\",\n updateId: update.id,\n });\n }\n const epochUpdates = updatesByEpoch.get(contentKeyEpoch);\n if (epochUpdates) epochUpdates.push(update);\n else updatesByEpoch.set(contentKeyEpoch, [update]);\n }\n return updatesByEpoch;\n}\n\nfunction assertBundleMatchesUpdateHeaders(input: {\n bundle: DocumentSyncResponse[\"contentKeyBundle\"];\n requireOriginalTargets: boolean;\n updates: readonly SyncResponseUpdate[];\n}): void {\n for (const update of input.updates) {\n const header = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n );\n if (\n header.objectId !== input.bundle.documentId ||\n header.contentKeyEpoch !== input.bundle.contentKeyEpoch ||\n (input.requireOriginalTargets &&\n (header.accessManifestHash !== input.bundle.linkSetManifestHash ||\n header.targetHash !== input.bundle.targetHash))\n ) {\n throw new Error(\n input.requireOriginalTargets\n ? \"Document sync response content key is unavailable and its bundle differs from the original update headers\"\n : \"Document sync response content-key bundle does not match its update headers\",\n );\n }\n }\n}\n\n/**\n * Linking can rewrap a retained key at the same content epoch. Its latest bundle\n * then has newer targets than older signed updates. A usable key is validated\n * by the caller's authenticated record decryption; the update's own historical\n * manifest, targets and signature are verified independently before this step.\n *\n * If the key cannot be recovered, require the original target commitments before\n * calling that an availability failure. Substituted unavailable targets must\n * still be quarantined, never disguised as legitimate missing history.\n */\nasync function unwrapHistoricalContentKey(input: {\n bundle: DocumentSyncResponse[\"contentKeyBundle\"];\n documentId: string;\n collectedKeks: Awaited<\n ReturnType<typeof collectContainerKeksForDocumentSync>\n >;\n updates: readonly SyncResponseUpdate[];\n}): Promise<Uint8Array> {\n assertBundleMatchesUpdateHeaders({ ...input, requireOriginalTargets: false });\n try {\n return await unwrapDocumentContentKeyFromBundle(\n input.bundle,\n input.documentId,\n input.collectedKeks.keksByEpochId,\n input.collectedKeks.predecessorFailuresByEpochId,\n input.collectedKeks.unattributedPredecessorFailuresByContainerId,\n );\n } catch (error) {\n if (isRawHistoryUnavailableCause(error)) {\n assertBundleMatchesUpdateHeaders({\n ...input,\n requireOriginalTargets: true,\n });\n }\n throw error;\n }\n}\n\n/** @internal Keeps epoch-wide failures anonymous and integrity failures typed. */\nexport function throwDocumentSyncContentKeyFailure(input: {\n cause: unknown;\n updates: readonly SyncResponseUpdate[];\n}): never {\n if (isKeyingVerificationError(input.cause)) {\n throw input.cause;\n }\n if (\n input.cause instanceof DocumentHistoryUnavailableError &&\n isKeyingVerificationError(input.cause.historyCause)\n ) {\n throw input.cause.historyCause;\n }\n throw isolateDocumentSyncBatchError({\n cause: input.cause,\n stage: \"content_key\",\n updateIds: input.updates.map((update) => update.id),\n });\n}\n\nexport async function unwrapDocumentSyncResponseContentKeys(\n input: {\n currentContentKey: Uint8Array;\n currentContentKeyEpoch: number;\n execSql?: ExecSql | undefined;\n historyMode?: \"raw\" | undefined;\n /** @internal Validate every decryptable sibling before raw availability wins. */\n onRawHistoryUnavailable?: RawHistoryUnavailableValidator | undefined;\n response: DocumentSyncResponse;\n targetSecretKey: Uint8Array;\n writerProjection: DocumentWriterProjectionResponse;\n } & ProjectionVerificationOptions,\n): Promise<ReadonlyMap<number, Uint8Array>> {\n // A stale read-only pass can carry an empty placeholder when none of its\n // authorizing paths reaches the bundle target. Do not mark that epoch as\n // resolved: every served update must instead unwrap its bundle through the\n // verified predecessor KEK chain or fail with an honest error.\n const contentKeysByEpoch = new Map<number, Uint8Array>(\n input.currentContentKey.byteLength > 0\n ? [[input.currentContentKeyEpoch, input.currentContentKey]]\n : [],\n );\n const updatesByContentKeyEpoch = syncResponseUpdatesByContentKeyEpoch(\n input.response,\n );\n const missingContentKeyEpochs = [...updatesByContentKeyEpoch.keys()].filter(\n (contentKeyEpoch) => !contentKeysByEpoch.has(contentKeyEpoch),\n );\n missingContentKeyEpochs.sort((left, right) => left - right);\n if (missingContentKeyEpochs.length === 0) {\n return contentKeysByEpoch;\n }\n const missingUpdates = missingContentKeyEpochs.flatMap(\n (contentKeyEpoch) => updatesByContentKeyEpoch.get(contentKeyEpoch) ?? [],\n );\n let bundlesByEpoch: ReturnType<typeof syncResponseContentKeyBundlesByEpoch>;\n try {\n bundlesByEpoch = syncResponseContentKeyBundlesByEpoch(input.response);\n } catch (error) {\n throwDocumentSyncContentKeyFailure({\n cause: error,\n updates: missingUpdates,\n });\n }\n const absentBundleEpoch = missingContentKeyEpochs.find(\n (contentKeyEpoch) => !bundlesByEpoch.has(contentKeyEpoch),\n );\n if (absentBundleEpoch !== undefined) {\n throwDocumentSyncContentKeyFailure({\n cause: new Error(\"Document sync response content-key bundle missing\"),\n updates:\n updatesByContentKeyEpoch.get(absentBundleEpoch) ?? missingUpdates,\n });\n }\n const collectedKeks = await collectContainerKeksForDocumentSync({\n execSql: input.execSql,\n secretKey: input.targetSecretKey,\n writerProjection: input.writerProjection,\n ...projectionVerificationOptions(input),\n });\n let unavailableRawHistory:\n | { cause: RawHistoryUnavailableCause; contentKeyEpoch: number }\n | undefined;\n\n for (const contentKeyEpoch of missingContentKeyEpochs) {\n const bundle = bundlesByEpoch.get(contentKeyEpoch);\n const responseUpdates =\n updatesByContentKeyEpoch.get(contentKeyEpoch) ?? missingUpdates;\n if (!bundle) {\n const error = new Error(\n \"Document sync response content-key bundle missing\",\n );\n throwDocumentSyncContentKeyFailure({\n cause: error,\n updates: responseUpdates,\n });\n }\n try {\n contentKeysByEpoch.set(\n bundle.contentKeyEpoch,\n await unwrapHistoricalContentKey({\n bundle,\n collectedKeks,\n documentId: input.writerProjection.documentId,\n updates: responseUpdates,\n }),\n );\n } catch (error) {\n if (input.historyMode === \"raw\" && isRawHistoryUnavailableCause(error)) {\n unavailableRawHistory ??= { cause: error, contentKeyEpoch };\n continue;\n }\n throwDocumentSyncContentKeyFailure({\n cause: error,\n updates: responseUpdates,\n });\n }\n }\n\n if (unavailableRawHistory) {\n await throwRawHistoryUnavailable({\n contentKeysByEpoch,\n response: input.response,\n unavailable: unavailableRawHistory,\n validate: input.onRawHistoryUnavailable,\n });\n }\n\n return contentKeysByEpoch;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"syncContentKeys.js","sourceRoot":"","sources":["syncContentKeys.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n DocumentSyncResponse,\n DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { importContentKeyMaterial } from \"../../data/documents/shared/contentRecordKeys\";\nimport { encryptDocumentPendingUpdate } from \"../../data/documents/shared/crypto\";\nimport { assertDocumentSyncUpdateEncryptedRecord } from \"../../data/documents/shared/documentSyncUpdateDecryption\";\nimport {\n isDocumentSyncUpdateIsolationError,\n isolateDocumentSyncBatchError,\n isolateDocumentSyncUpdateError,\n} from \"../../data/documents/shared/documentSyncUpdateIsolation\";\nimport {\n ContainerKekHistoryUnavailableError,\n collectContainerKeksForDocumentSync,\n DocumentContentKeyUnavailableError,\n DocumentHistoryUnavailableError,\n unwrapDocumentContentKeyFromBundle,\n} from \"../../data/documents/shared/projection\";\nimport { readWriteHeader } from \"../../data/documents/shared/readers\";\nimport type {\n DocumentSyncPreparedUpdate,\n ProjectionVerificationOptions,\n} from \"../../data/documents/shared/types\";\nimport { projectionVerificationOptions } from \"../../data/documents/shared/types\";\nimport { isKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport type { PendingUpdateRecord } from \"../../data/sqlite/documentPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\ntype SyncResponseUpdate = DocumentSyncResponse[\"updates\"][number];\ntype RawHistoryUnavailableCause =\n | DocumentContentKeyUnavailableError\n | DocumentHistoryUnavailableError;\n\nfunction isRawHistoryUnavailableCause(\n error: unknown,\n): error is RawHistoryUnavailableCause {\n return (\n error instanceof DocumentContentKeyUnavailableError ||\n (error instanceof DocumentHistoryUnavailableError &&\n error.historyCause instanceof ContainerKekHistoryUnavailableError)\n );\n}\n\ntype RawHistoryUnavailableValidator = (input: {\n contentKeysByEpoch: ReadonlyMap<number, Uint8Array>;\n updates: readonly SyncResponseUpdate[];\n}) => void | Promise<void>;\n\n/** A raw recovery cannot reconstruct history without this retained epoch. */\nexport class DocumentRawHistoryUnavailableError extends Error {\n readonly code = \"document_raw_history_epoch_unavailable\";\n\n constructor(\n readonly contentKeyEpoch: number,\n cause: unknown,\n ) {\n super(\n `Document raw-history recovery cannot decrypt content-key epoch ${contentKeyEpoch}`,\n { cause },\n );\n this.name = \"DocumentRawHistoryUnavailableError\";\n }\n}\n\nasync function assertResponseEncryptedRecords(\n updates: readonly SyncResponseUpdate[],\n): Promise<void> {\n const inspections = await Promise.allSettled(\n updates.map(assertDocumentSyncUpdateEncryptedRecord),\n );\n const failures = inspections.flatMap((inspection, index) =>\n inspection.status === \"rejected\"\n ? [{ index, reason: inspection.reason }]\n : [],\n );\n const firstFailure = failures[0];\n if (failures.length === 1 && firstFailure) {\n throw firstFailure.reason;\n }\n if (failures.length > 1 && firstFailure) {\n const stage =\n isDocumentSyncUpdateIsolationError(firstFailure.reason) &&\n failures.every(\n ({ reason }) =>\n isDocumentSyncUpdateIsolationError(reason) &&\n reason.stage === firstFailure.reason.stage,\n )\n ? firstFailure.reason.stage\n : \"encrypted_record\";\n throw isolateDocumentSyncBatchError({\n cause: new Error(\"Multiple document encrypted records are invalid\"),\n stage,\n updateIds: failures.map(({ index }) => updates[index]?.id ?? \"unknown\"),\n });\n }\n}\n\nasync function throwRawHistoryUnavailable(input: {\n contentKeysByEpoch: ReadonlyMap<number, Uint8Array>;\n response: DocumentSyncResponse;\n unavailable: { cause: RawHistoryUnavailableCause; contentKeyEpoch: number };\n validate?: RawHistoryUnavailableValidator | undefined;\n}): Promise<never> {\n await assertResponseEncryptedRecords(input.response.updates);\n await input.validate?.({\n contentKeysByEpoch: input.contentKeysByEpoch,\n updates: input.response.updates.filter((update) =>\n input.contentKeysByEpoch.has(\n readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n ).contentKeyEpoch,\n ),\n ),\n });\n throw new DocumentRawHistoryUnavailableError(\n input.unavailable.contentKeyEpoch,\n input.unavailable.cause,\n );\n}\n\nexport async function prepareDocumentOutgoingUpdates(input: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n documentId: string;\n organizationId: string;\n pendingUpdates: readonly PendingUpdateRecord[];\n}): Promise<DocumentSyncPreparedUpdate[]> {\n if (input.pendingUpdates.length === 0) {\n return [];\n }\n const contentKeyMaterial = await importContentKeyMaterial(input.contentKey);\n\n return Promise.all(\n input.pendingUpdates.map(async (update) => {\n const encrypted = await encryptDocumentPendingUpdate({\n contentKeyMaterial,\n contentKeyEpoch: input.contentKeyEpoch,\n documentId: input.documentId,\n organizationId: input.organizationId,\n update,\n });\n\n return {\n contentRecordId: encrypted.contentRecordId,\n encryptedData: encrypted.encryptedData,\n id: update.id,\n partialStartVersionVector: update.partialStartVersionVector,\n partialEndVersionVector: update.partialEndVersionVector,\n metadataHash: encrypted.metadataHash,\n ciphertextHash: encrypted.ciphertextHash,\n plaintextHash: encrypted.plaintextHash,\n ...(update.sourceVersionVector\n ? {\n checkpointKind: \"rotate_baseline\" as const,\n checkpointPayloadKind: \"full_history_snapshot\" as const,\n sourceVersionVector: update.sourceVersionVector,\n }\n : {}),\n };\n }),\n );\n}\n\nfunction syncResponseContentKeyBundlesByEpoch(\n response: DocumentSyncResponse,\n): ReadonlyMap<number, DocumentSyncResponse[\"contentKeyBundle\"]> {\n const byEpoch = new Map<number, DocumentSyncResponse[\"contentKeyBundle\"]>();\n\n for (const bundle of [\n response.contentKeyBundle,\n ...response.contentKeyBundles,\n ]) {\n if (byEpoch.has(bundle.contentKeyEpoch)) {\n throw new Error(\"Document sync response repeats a content-key epoch\");\n }\n byEpoch.set(bundle.contentKeyEpoch, bundle);\n }\n\n return byEpoch;\n}\n\nfunction syncResponseUpdatesByContentKeyEpoch(\n response: DocumentSyncResponse,\n): ReadonlyMap<number, readonly SyncResponseUpdate[]> {\n const updatesByEpoch = new Map<number, SyncResponseUpdate[]>();\n for (const update of response.updates) {\n let contentKeyEpoch: number;\n try {\n contentKeyEpoch = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n ).contentKeyEpoch;\n } catch (error) {\n throw isolateDocumentSyncUpdateError({\n cause: error,\n responseUpdate: update,\n stage: \"write_header\",\n updateId: update.id,\n });\n }\n const epochUpdates = updatesByEpoch.get(contentKeyEpoch);\n if (epochUpdates) epochUpdates.push(update);\n else updatesByEpoch.set(contentKeyEpoch, [update]);\n }\n return updatesByEpoch;\n}\n\nfunction assertBundleMatchesUpdateHeaders(input: {\n bundle: DocumentSyncResponse[\"contentKeyBundle\"];\n requireOriginalTargets: boolean;\n updates: readonly SyncResponseUpdate[];\n}): void {\n for (const update of input.updates) {\n const header = readWriteHeader(\n update.writeHeader,\n \"Document sync response write header\",\n );\n if (\n header.objectId !== input.bundle.documentId ||\n header.contentKeyEpoch !== input.bundle.contentKeyEpoch ||\n (input.requireOriginalTargets &&\n (header.accessManifestHash !== input.bundle.linkSetManifestHash ||\n header.targetHash !== input.bundle.targetHash))\n ) {\n throw new Error(\n input.requireOriginalTargets\n ? \"Document sync response content key is unavailable and its bundle differs from the original update headers\"\n : \"Document sync response content-key bundle does not match its update headers\",\n );\n }\n }\n}\n\n/**\n * Linking can rewrap a retained key at the same content epoch. Its latest bundle\n * then has newer targets than older signed updates. A usable key is validated\n * by the caller's authenticated record decryption; the update's own historical\n * manifest, targets and signature are verified independently before this step.\n *\n * If the key cannot be recovered, require the original target commitments before\n * calling that an availability failure. Substituted unavailable targets must\n * still be quarantined, never disguised as legitimate missing history.\n */\nasync function unwrapHistoricalContentKey(input: {\n bundle: DocumentSyncResponse[\"contentKeyBundle\"];\n documentId: string;\n collectedKeks: Awaited<\n ReturnType<typeof collectContainerKeksForDocumentSync>\n >;\n updates: readonly SyncResponseUpdate[];\n}): Promise<Uint8Array> {\n assertBundleMatchesUpdateHeaders({ ...input, requireOriginalTargets: false });\n try {\n return await unwrapDocumentContentKeyFromBundle(\n input.bundle,\n input.documentId,\n input.collectedKeks.keksByEpochId,\n input.collectedKeks.predecessorFailuresByEpochId,\n input.collectedKeks.unattributedPredecessorFailuresByContainerId,\n );\n } catch (error) {\n if (isRawHistoryUnavailableCause(error)) {\n assertBundleMatchesUpdateHeaders({\n ...input,\n requireOriginalTargets: true,\n });\n }\n throw error;\n }\n}\n\n/** @internal Keeps epoch-wide failures anonymous and integrity failures typed. */\nexport function throwDocumentSyncContentKeyFailure(input: {\n cause: unknown;\n updates: readonly SyncResponseUpdate[];\n}): never {\n if (isKeyingVerificationError(input.cause)) {\n throw input.cause;\n }\n if (\n input.cause instanceof DocumentHistoryUnavailableError &&\n isKeyingVerificationError(input.cause.historyCause)\n ) {\n throw input.cause.historyCause;\n }\n throw isolateDocumentSyncBatchError({\n cause: input.cause,\n stage: \"content_key\",\n updateIds: input.updates.map((update) => update.id),\n });\n}\n\nexport async function unwrapDocumentSyncResponseContentKeys(\n input: {\n currentContentKey: Uint8Array;\n currentContentKeyEpoch: number;\n execSql?: ExecSql | undefined;\n historyMode?: \"raw\" | undefined;\n /** @internal Validate every decryptable sibling before raw availability wins. */\n onRawHistoryUnavailable?: RawHistoryUnavailableValidator | undefined;\n response: DocumentSyncResponse;\n targetSecretKey: Uint8Array;\n writerProjection: DocumentWriterProjectionResponse;\n } & ProjectionVerificationOptions,\n): Promise<ReadonlyMap<number, Uint8Array>> {\n // A stale read-only pass can carry an empty placeholder when none of its\n // authorizing paths reaches the bundle target. Do not mark that epoch as\n // resolved: every served update must instead unwrap its bundle through the\n // verified predecessor KEK chain or fail with an honest error.\n const contentKeysByEpoch = new Map<number, Uint8Array>(\n input.currentContentKey.byteLength > 0\n ? [[input.currentContentKeyEpoch, input.currentContentKey]]\n : [],\n );\n const updatesByContentKeyEpoch = syncResponseUpdatesByContentKeyEpoch(\n input.response,\n );\n const missingContentKeyEpochs = [...updatesByContentKeyEpoch.keys()].filter(\n (contentKeyEpoch) => !contentKeysByEpoch.has(contentKeyEpoch),\n );\n missingContentKeyEpochs.sort((left, right) => left - right);\n if (missingContentKeyEpochs.length === 0) {\n return contentKeysByEpoch;\n }\n const missingUpdates = missingContentKeyEpochs.flatMap(\n (contentKeyEpoch) => updatesByContentKeyEpoch.get(contentKeyEpoch) ?? [],\n );\n let bundlesByEpoch: ReturnType<typeof syncResponseContentKeyBundlesByEpoch>;\n try {\n bundlesByEpoch = syncResponseContentKeyBundlesByEpoch(input.response);\n } catch (error) {\n throwDocumentSyncContentKeyFailure({\n cause: error,\n updates: missingUpdates,\n });\n }\n const absentBundleEpoch = missingContentKeyEpochs.find(\n (contentKeyEpoch) => !bundlesByEpoch.has(contentKeyEpoch),\n );\n if (absentBundleEpoch !== undefined) {\n throwDocumentSyncContentKeyFailure({\n cause: new Error(\"Document sync response content-key bundle missing\"),\n updates:\n updatesByContentKeyEpoch.get(absentBundleEpoch) ?? missingUpdates,\n });\n }\n const collectedKeks = await collectContainerKeksForDocumentSync({\n execSql: input.execSql,\n secretKey: input.targetSecretKey,\n writerProjection: input.writerProjection,\n ...projectionVerificationOptions(input),\n });\n let unavailableRawHistory:\n | { cause: RawHistoryUnavailableCause; contentKeyEpoch: number }\n | undefined;\n\n for (const contentKeyEpoch of missingContentKeyEpochs) {\n const bundle = bundlesByEpoch.get(contentKeyEpoch);\n const responseUpdates =\n updatesByContentKeyEpoch.get(contentKeyEpoch) ?? missingUpdates;\n if (!bundle) {\n const error = new Error(\n \"Document sync response content-key bundle missing\",\n );\n throwDocumentSyncContentKeyFailure({\n cause: error,\n updates: responseUpdates,\n });\n }\n try {\n contentKeysByEpoch.set(\n bundle.contentKeyEpoch,\n await unwrapHistoricalContentKey({\n bundle,\n collectedKeks,\n documentId: input.writerProjection.documentId,\n updates: responseUpdates,\n }),\n );\n } catch (error) {\n if (input.historyMode === \"raw\" && isRawHistoryUnavailableCause(error)) {\n unavailableRawHistory ??= { cause: error, contentKeyEpoch };\n continue;\n }\n throwDocumentSyncContentKeyFailure({\n cause: error,\n updates: responseUpdates,\n });\n }\n }\n\n if (unavailableRawHistory) {\n await throwRawHistoryUnavailable({\n contentKeysByEpoch,\n response: input.response,\n unavailable: unavailableRawHistory,\n validate: input.onRawHistoryUnavailable,\n });\n }\n\n return contentKeysByEpoch;\n}\n"]}
|