@tearleads/client-sdk 0.1.23 → 0.1.25
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/persistence/container-contents/containerContentsPersistenceTypes.d.ts +2 -0
- package/data/persistence/container-contents/containerContentsPersistenceTypes.js.map +1 -1
- package/data/persistence/container-contents/containerDeletionPersistence.js +3 -0
- package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
- package/data/persistence/container-contents/documentPurgePlacement.js +15 -0
- package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -0
- package/data/persistence/documents/documentsPersistence.js +7 -0
- package/data/persistence/documents/documentsPersistence.js.map +1 -1
- package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -0
- package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
- package/data/persistence/documents/orphanBlobPersistence.d.ts +13 -0
- package/data/persistence/documents/orphanBlobPersistence.js +2 -0
- package/data/persistence/documents/orphanBlobPersistence.js.map +1 -0
- package/data/persistence/documents/types.d.ts +4 -1
- package/data/persistence/documents/types.js.map +1 -1
- package/data/persistence/principalCurrentAcknowledgementPersistence.d.ts +13 -2
- package/data/persistence/principalCurrentAcknowledgementPersistence.js +36 -20
- package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/internal/api-client/ApiClient.d.ts +6 -2
- package/internal/api-client/ApiClient.js +4 -5
- package/internal/api-client/ApiClient.js.map +1 -1
- package/internal/api-client/apiRequestRuntime.d.ts +1 -2
- package/internal/api-client/apiRequestRuntime.js +12 -15
- package/internal/api-client/apiRequestRuntime.js.map +1 -1
- package/internal/api-client/routes/containers/mutations.js +2 -2
- package/internal/api-client/routes/containers/mutations.js.map +1 -1
- package/internal/api-client/sessionRefresh.js +14 -6
- package/internal/api-client/sessionRefresh.js.map +1 -1
- package/internal/api-client/sessionRenewal.js +54 -0
- package/internal/api-client/sessionRenewal.js.map +1 -0
- package/internal/validators/operation/containerMutations.d.ts +3 -0
- package/internal/validators/operation/containerMutations.js +1 -0
- package/internal/validators/operation/containerMutations.js.map +1 -1
- package/package.json +1 -1
- package/stores/documents/documentStore/attachmentHydration.js +2 -2
- package/stores/documents/documentStore/attachmentHydration.js.map +1 -1
- package/stores/documents/documentStore/attachments.js +7 -5
- package/stores/documents/documentStore/attachments.js.map +1 -1
- package/stores/documents/documentStore/discard.js +3 -2
- package/stores/documents/documentStore/discard.js.map +1 -1
- package/stores/documents/documentStore/orphanMaintenance.js +3 -5
- package/stores/documents/documentStore/orphanMaintenance.js.map +1 -1
- package/stores/documents/documentStore/persistence.js +3 -4
- package/stores/documents/documentStore/persistence.js.map +1 -1
- package/stores/documents/documentStore/syncDetachedAttachments.js +3 -2
- package/stores/documents/documentStore/syncDetachedAttachments.js.map +1 -1
- package/stores/documents/documentStore/syncRequest.js +3 -4
- package/stores/documents/documentStore/syncRequest.js.map +1 -1
- package/workflows/container-contents/container-state/delete.js +2 -0
- package/workflows/container-contents/container-state/delete.js.map +1 -1
- package/workflows/container-contents/container-state/localPurgeScope.js +49 -0
- package/workflows/container-contents/container-state/localPurgeScope.js.map +1 -0
- package/workflows/container-contents/container-state/purgeTree.js +42 -7
- package/workflows/container-contents/container-state/purgeTree.js.map +1 -1
- package/workflows/container-contents/container-state/purgeTreeCollection.js +11 -0
- package/workflows/container-contents/container-state/purgeTreeCollection.js.map +1 -1
- package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js +4 -1
- package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js.map +1 -1
- package/workflows/container-contents/container-state/purgeTreeScope.js +158 -0
- package/workflows/container-contents/container-state/purgeTreeScope.js.map +1 -0
- package/workflows/container-contents/container-state/readLocalPurgeScope.js +18 -0
- package/workflows/container-contents/container-state/readLocalPurgeScope.js.map +1 -0
- package/workflows/container-contents/container-state/remote.js +1 -0
- package/workflows/container-contents/container-state/remote.js.map +1 -1
- package/workflows/container-contents/container-state/types.js.map +1 -1
- package/workflows/container-contents/documentLinks.d.ts +4 -0
- package/workflows/container-contents/documentLinks.js +2 -0
- package/workflows/container-contents/documentLinks.js.map +1 -1
- package/workflows/container-contents/documentPurge.d.ts +6 -1
- package/workflows/container-contents/documentPurge.js +12 -5
- package/workflows/container-contents/documentPurge.js.map +1 -1
- package/workflows/documents/linkSet.d.ts +1 -0
- package/workflows/documents/linkSet.js +2 -0
- package/workflows/documents/linkSet.js.map +1 -1
- package/workflows/documents/linkSetRemote.d.ts +2 -0
- package/workflows/documents/linkSetRemote.js +3 -0
- package/workflows/documents/linkSetRemote.js.map +1 -1
- package/workflows/documents/orphanBlobReclaimState.js +20 -0
- package/workflows/documents/orphanBlobReclaimState.js.map +1 -0
- package/workflows/documents/orphanBlobReclaims.d.ts +3 -2
- package/workflows/documents/orphanBlobReclaims.js +49 -47
- package/workflows/documents/orphanBlobReclaims.js.map +1 -1
- package/workflows/documents/purge.d.ts +3 -0
- package/workflows/documents/purge.js +7 -0
- package/workflows/documents/purge.js.map +1 -1
- package/workflows/documents/subtreePurgeScope.js +11 -0
- package/workflows/documents/subtreePurgeScope.js.map +1 -0
- package/workflows/organizations/acknowledgedPrincipalPredecessor.d.ts +39 -0
- package/workflows/organizations/acknowledgedPrincipalPredecessor.js +87 -0
- package/workflows/organizations/acknowledgedPrincipalPredecessor.js.map +1 -0
- package/workflows/organizations/currentOrganizationMutationRetention.js +13 -2
- package/workflows/organizations/currentOrganizationMutationRetention.js.map +1 -1
- package/workflows/organizations/index.d.ts +1 -0
- package/workflows/organizations/index.js +1 -0
- package/workflows/organizations/index.js.map +1 -1
- package/workflows/organizations/initialPrincipalCurrentPublication.js +3 -2
- package/workflows/organizations/initialPrincipalCurrentPublication.js.map +1 -1
- package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js +63 -0
- package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js.map +1 -0
- package/workflows/organizations/retainAcknowledgedPrincipalCurrents.d.ts +3 -0
- package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js +33 -55
- package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js.map +1 -1
- package/workflows/organizations/runtimeCurrentGroupMutation.js +13 -2
- package/workflows/organizations/runtimeCurrentGroupMutation.js.map +1 -1
- package/workflows/organizations/runtimeCurrentOrganizationMutation.js +2 -2
- package/workflows/organizations/runtimeCurrentOrganizationMutation.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentLinks.js","sourceRoot":"","sources":["documentLinks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { VerifiedDocumentLinkSetManifest } from \"@tearleads/crypto\";\nimport type { DocumentWriterProjectionResponse } from \"@tearleads/validators/response\";\nimport {\n assertDocumentWriterProjectionConsistent,\n readLinkedContainerIdsFromDocumentManifest,\n} from \"../../data/documents/shared/projection\";\nimport { persistedDocumentCreateStateFromWriterProjection } from \"../../data/documents/shared/responses\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../data/keyingProjectionVerification\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport { sqlDocumentContainerProjectionPersistence } from \"../../data/persistence/containers/documentContainerProjectionPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { isDatabaseUnavailableError } from \"../../data/sync/databaseUnavailable\";\nimport {\n type DocumentLinkSetFailureHandler,\n type RelinkRemoteDocumentResult,\n relinkRemoteDocument,\n resolveDocumentCreateAuthor,\n} from \"../documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../principals/runtimePolicyWarmer\";\nimport { resolveContainerDocumentMoveUnlinkIds } from \"./documentMoveUnlinkIds\";\nimport type { ContainerContentsWorkflowRuntime } from \"./runtime\";\n\ntype ContainerDocumentLinkApi = Parameters<\n typeof relinkRemoteDocument\n>[0][\"apiClient\"];\ntype ContainerDocumentLinkOperation = Parameters<\n typeof relinkRemoteDocument\n>[0][\"operation\"];\ntype RemoteDocumentPersistedState =\n RelinkRemoteDocumentResult[\"persistedState\"];\n\n// A cold-cache denial must keep its HTTP status: collapsing a 403 to null\n// would leave an access-denied move routinely retriable instead of parking\n// it for the access-restored signal (row 7).\nasync function fetchMoveWriterProjection(input: {\n documentId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n runtime: ContainerDocumentLinkRuntime;\n}): Promise<Awaited<\n ReturnType<ContainerDocumentLinkApi[\"getDocumentWriterProjection\"]>\n> | null> {\n const { apiClient } = input.runtime;\n const result = await apiClient.getDocumentWriterProjectionResult(\n input.documentId,\n { reportErrors: false },\n );\n if (result.ok) {\n return result.data;\n }\n result.report();\n input.onFailure?.({ message: result.message, status: result.status });\n return null;\n}\n\ninterface ContainerDocumentLinkRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: ContainerDocumentLinkApi &\n Pick<\n ContainerContentsWorkflowRuntime[\"apiClient\"],\n \"getPrincipalPolicyPages\" | \"getProjectionPolicyHistoryPages\"\n >;\n}\n\ntype MoveRemoteContainerDocumentStatus = \"complete\" | \"partial\";\n\ninterface MoveRemoteContainerDocumentResult {\n accessEpoch: number | null;\n accessStateHash: string | null;\n linkedContainerIds: readonly string[];\n nextContainerId: string;\n remoteState: RemoteDocumentPersistedState | null;\n status: MoveRemoteContainerDocumentStatus;\n}\n\nexport type { RemoteDocumentPersistedState };\n\nexport async function initializeDocumentLinksSchema(\n execSql: ExecSql,\n): Promise<void> {\n await sqlDocumentContainerProjectionPersistence.ensureSchema(execSql);\n}\n\nexport async function listDocumentLinkedContainerIds(\n execSql: ExecSql,\n documentId: string,\n): Promise<ReadonlyArray<string>> {\n return sqlDocumentContainerProjectionPersistence.listLinkedContainerIds(\n execSql,\n documentId,\n );\n}\n\nexport async function replaceDocumentLinks(\n execSql: ExecSql,\n documentId: string,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinks(\n execSql,\n documentId,\n containerIds,\n );\n}\n\nfunction resolveActiveDocumentContainerId(\n linkedContainerIds: ReadonlyArray<string>,\n preferredContainerId: string,\n): string | null {\n if (linkedContainerIds.includes(preferredContainerId)) {\n return preferredContainerId;\n }\n\n return linkedContainerIds[0] ?? null;\n}\n\nfunction containerDocumentMoveResult(input: {\n document: RelinkRemoteDocumentResult;\n nextContainerId: string;\n status: MoveRemoteContainerDocumentStatus;\n}): MoveRemoteContainerDocumentResult {\n return {\n accessEpoch: input.document.plan.state.epoch,\n accessStateHash: input.document.response.accessManifest.manifestHash,\n linkedContainerIds: input.document.linkedContainerIds,\n nextContainerId: input.nextContainerId,\n remoteState: input.document.persistedState,\n status: input.status,\n };\n}\n\nasync function containerDocumentAlreadyMovedResult(input: {\n writerProjection: DocumentWriterProjectionResponse;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentLinkRuntime;\n isCurrent?: (() => boolean) | undefined;\n linkedContainerIds: readonly string[];\n nextContainerId: string;\n status: MoveRemoteContainerDocumentStatus;\n}): Promise<MoveRemoteContainerDocumentResult> {\n const verified: { head?: VerifiedDocumentLinkSetManifest | undefined } = {};\n await assertDocumentWriterProjectionConsistent(input.writerProjection, {\n execSql: input.runtime.infra.execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.isCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n onVerifiedAuthorization: (authorization) => {\n verified.head = authorization.documentManifestByHash.get(\n input.writerProjection.documentManifest.manifestHash,\n );\n },\n });\n if (!verified.head)\n throw new Error(\"Move replay lacks a verified document head\");\n return {\n accessEpoch: verified.head.state.epoch,\n accessStateHash: verified.head.manifestHash,\n linkedContainerIds: input.linkedContainerIds,\n nextContainerId: input.nextContainerId,\n remoteState: persistedDocumentCreateStateFromWriterProjection(\n input.writerProjection,\n ),\n status: input.status,\n };\n}\n\n/**\n * Hand a link/unlink/move/purge failure to host diagnostics as a real `Error`.\n * The caller's string-only log line stays the fallback for a host without\n * `logError`, so this never logs a second copy of the same failure.\n */\nfunction reportDocumentLinkMutationFailure(\n util: ContainerDocumentLinkRuntime[\"util\"],\n error: unknown,\n): void {\n // `ApiClient` returns `RequestFailure` objects instead of throwing, so\n // offline and 403/404/409 already left through the `!result` branch: a status\n // filter here would be dead code. Only a database lost mid-flight stays quiet.\n if (!util.logError || isDatabaseUnavailableError(error)) return;\n try {\n // A host logger that throws or rejects must not replace the null result or\n // skip the failure handler below.\n void Promise.resolve(\n util.logError(\"Container contents: document link mutation failed\", error),\n ).catch(() => undefined);\n } catch {\n // Hosts may throw synchronously or return a rejected promise.\n }\n}\n\nexport async function relinkRemoteContainerDocument(input: {\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n operation: ContainerDocumentLinkOperation;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot?: Uint8Array | undefined;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<RelinkRemoteDocumentResult | null> {\n const {\n documentId,\n noteId,\n operation,\n resolveProjectionUserKey,\n runtime,\n targetContainerId,\n } = input;\n const author = resolveDocumentCreateAuthor(runtime);\n const targetSecretKey = runtime.crypto.encapsulationKeyPair?.secretKey;\n const execSql = runtime.infra.execSql;\n if (input.isCurrent?.() === false) return null;\n if (!author || !targetSecretKey) {\n runtime.util.log(\n \"Container contents: document mutation skipped because the local key context is unavailable\",\n );\n input.onFailure?.({\n message: \"the local key context is unavailable\",\n status: null,\n });\n return null;\n }\n\n try {\n const result = await relinkRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n documentId,\n execSql,\n onFailure: input.onFailure,\n operation,\n resolveProjectionUserKey,\n rotationSnapshot: input.rotationSnapshot,\n stillCurrent: input.isCurrent,\n targetContainerId,\n targetSecretKey,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!result) {\n runtime.util.log(\n `Container contents: failed to ${operation} note ${noteId} ${operation === \"link\" ? \"to\" : \"from\"} container ${targetContainerId}`,\n );\n return null;\n }\n if (input.isCurrent?.() === false) return null;\n\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinks(\n execSql,\n documentId,\n result.linkedContainerIds,\n { stillCurrent: input.isCurrent },\n );\n\n return result;\n } catch (error) {\n await reportAndRethrowKeyingVerificationError(\n error,\n runtime.util.reportSecurityIncident,\n {\n objectId: documentId,\n objectKind: \"document\",\n operation: `document.${operation}`,\n },\n );\n const message = errorMessage(error);\n runtime.util.log(\n `Container contents: failed to ${operation} note ${noteId} ${operation === \"link\" ? \"to\" : \"from\"} container ${targetContainerId}: ${message}`,\n );\n // After the keying rethrow above, so a verification failure keeps its\n // security incident instead of being reported twice, and before the failure\n // handler, so a throwing caller callback cannot swallow the report.\n reportDocumentLinkMutationFailure(runtime.util, error);\n input.onFailure?.({ message, status: null });\n return null;\n }\n}\n\nasync function linkRemoteContainerDocument(input: {\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<RelinkRemoteDocumentResult | null> {\n return relinkRemoteContainerDocument({\n ...input,\n operation: \"link\",\n });\n}\n\nexport async function unlinkRemoteContainerDocument(input: {\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot: Uint8Array;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<RelinkRemoteDocumentResult | null> {\n return relinkRemoteContainerDocument({\n ...input,\n operation: \"unlink\",\n });\n}\n\nexport async function moveRemoteContainerDocument(input: {\n linkOnly?: boolean | undefined;\n additionalLinkContainerIds?: readonly string[] | undefined;\n removedLinkContainerIds?: readonly string[] | undefined;\n currentContainerId: string;\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n replaceLinkedContainers?: boolean | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot: Uint8Array;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<MoveRemoteContainerDocumentResult | null> {\n const {\n documentId,\n noteId,\n resolveProjectionUserKey,\n runtime,\n targetContainerId,\n } = input;\n if (input.isCurrent?.() === false) return null;\n const writerProjection = await fetchMoveWriterProjection({\n documentId,\n onFailure: input.onFailure,\n runtime,\n });\n if (!writerProjection || input.isCurrent?.() === false) {\n return null;\n }\n if (writerProjection.documentId !== documentId)\n throw new Error(\"Move replay document id mismatch\");\n const initialLinkedContainerIds =\n readLinkedContainerIdsFromDocumentManifest(writerProjection);\n\n let latestLinkedContainerIds: readonly string[] = initialLinkedContainerIds;\n let latestDocument: RelinkRemoteDocumentResult | null = null;\n for (const linkTarget of new Set([\n ...(input.linkOnly ? [] : [targetContainerId]),\n ...(input.additionalLinkContainerIds ?? []),\n ])) {\n if (latestLinkedContainerIds.includes(linkTarget)) continue;\n const linkedDocument = await linkRemoteContainerDocument({\n documentId,\n isCurrent: input.isCurrent,\n noteId,\n onFailure: input.onFailure,\n resolveProjectionUserKey,\n runtime,\n targetContainerId: linkTarget,\n });\n if (!linkedDocument || input.isCurrent?.() === false) {\n return null;\n }\n\n latestDocument = linkedDocument;\n latestLinkedContainerIds = linkedDocument.linkedContainerIds;\n }\n\n const unlinkContainerIds = resolveContainerDocumentMoveUnlinkIds({\n ...input,\n linkedContainerIds: latestLinkedContainerIds,\n });\n\n const unlinked = await unlinkMovedDocumentSources({\n ...input,\n latestDocument,\n latestLinkedContainerIds,\n unlinkContainerIds,\n });\n if (!unlinked) return null;\n latestDocument = unlinked.latestDocument;\n latestLinkedContainerIds = unlinked.latestLinkedContainerIds;\n\n const nextContainerId = resolveActiveDocumentContainerId(\n latestLinkedContainerIds,\n targetContainerId,\n );\n if (!nextContainerId) {\n return null;\n }\n\n const status =\n unlinked.failedUnlinkContainerIds.length > 0 ? \"partial\" : \"complete\";\n return latestDocument\n ? containerDocumentMoveResult({\n document: latestDocument,\n nextContainerId,\n status,\n })\n : containerDocumentAlreadyMovedResult({\n writerProjection,\n resolveProjectionUserKey,\n runtime,\n isCurrent: input.isCurrent,\n linkedContainerIds: latestLinkedContainerIds,\n nextContainerId,\n status,\n });\n}\n\n// The unlink half of a move: every source is attempted even when an earlier\n// one fails, so one dead source cannot strand the others; a failed unlink\n// leaves the move \"partial\" for the queue to retry.\nasync function unlinkMovedDocumentSources(input: {\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n latestDocument: RelinkRemoteDocumentResult | null;\n latestLinkedContainerIds: readonly string[];\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot: Uint8Array;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n unlinkContainerIds: readonly string[];\n}): Promise<{\n failedUnlinkContainerIds: readonly string[];\n latestDocument: RelinkRemoteDocumentResult | null;\n latestLinkedContainerIds: readonly string[];\n} | null> {\n const { noteId, runtime, targetContainerId } = input;\n let { latestDocument, latestLinkedContainerIds } = input;\n const failedUnlinkContainerIds: string[] = [];\n for (const unlinkContainerId of input.unlinkContainerIds) {\n if (input.isCurrent?.() === false) return null;\n const unlinkedDocument = await unlinkRemoteContainerDocument({\n documentId: input.documentId,\n isCurrent: input.isCurrent,\n noteId,\n onFailure: input.onFailure,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n rotationSnapshot: input.rotationSnapshot,\n runtime,\n targetContainerId: unlinkContainerId,\n });\n if (input.isCurrent?.() === false) return null;\n if (!unlinkedDocument) {\n failedUnlinkContainerIds.push(unlinkContainerId);\n runtime.util.log(\n `Container contents: note ${noteId} was linked to ${targetContainerId} but failed to unlink from ${unlinkContainerId}`,\n );\n continue;\n }\n\n latestDocument = unlinkedDocument;\n latestLinkedContainerIds = unlinkedDocument.linkedContainerIds;\n }\n return { failedUnlinkContainerIds, latestDocument, latestLinkedContainerIds };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentLinks.js","sourceRoot":"","sources":["documentLinks.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,wCAAwC,EACxC,0CAA0C,GAC3C,MAAM,wCAAwC,CAAC;AAChD,OAAO,EAAE,gDAAgD,EAAE,MAAM,uCAAuC,CAAC;AACzG,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAAE,uCAAuC,EAAE,MAAM,+CAA+C,CAAC;AACxG,OAAO,EAAE,yCAAyC,EAAE,MAAM,0EAA0E,CAAC;AAErI,OAAO,EAAE,0BAA0B,EAAE,MAAM,qCAAqC,CAAC;AACjF,OAAO,EAGL,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,kCAAkC,EAAE,MAAM,mCAAmC,CAAC;AACvF,OAAO,EAAE,qCAAqC,EAAE,MAAM,yBAAyB,CAAC;AAYhF,0EAA0E;AAC1E,2EAA2E;AAC3E,6CAA6C;AAC7C,KAAK,UAAU,yBAAyB,CAAC,KAIxC;IAGC,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC;IACpC,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,iCAAiC,CAC9D,KAAK,CAAC,UAAU,EAChB,EAAE,YAAY,EAAE,KAAK,EAAE,CACxB,CAAC;IACF,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IACD,MAAM,CAAC,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACtE,OAAO,IAAI,CAAC;AACd,CAAC;AAgCD,MAAM,CAAC,KAAK,UAAU,6BAA6B,CACjD,OAAgB;IAEhB,MAAM,yCAAyC,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;AACxE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,OAAgB,EAChB,UAAkB;IAElB,OAAO,yCAAyC,CAAC,sBAAsB,CACrE,OAAO,EACP,UAAU,CACX,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAgB,EAChB,UAAkB,EAClB,YAAmC;IAEnC,MAAM,yCAAyC,CAAC,oBAAoB,CAClE,OAAO,EACP,UAAU,EACV,YAAY,CACb,CAAC;AACJ,CAAC;AAED,SAAS,gCAAgC,CACvC,kBAAyC,EACzC,oBAA4B;IAE5B,IAAI,kBAAkB,CAAC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;QACtD,OAAO,oBAAoB,CAAC;IAC9B,CAAC;IAED,OAAO,kBAAkB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACvC,CAAC;AAED,SAAS,2BAA2B,CAAC,KAIpC;IACC,OAAO;QACL,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK;QAC5C,eAAe,EAAE,KAAK,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,YAAY;QACpE,kBAAkB,EAAE,KAAK,CAAC,QAAQ,CAAC,kBAAkB;QACrD,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,WAAW,EAAE,KAAK,CAAC,QAAQ,CAAC,cAAc;QAC1C,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,mCAAmC,CAAC,KAQlD;IACC,MAAM,QAAQ,GAA2D,EAAE,CAAC;IAC5E,MAAM,wCAAwC,CAAC,KAAK,CAAC,gBAAgB,EAAE;QACrE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;QACpC,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;QACxD,YAAY,EAAE,KAAK,CAAC,SAAS;QAC7B,+BAA+B,EAAE,kCAAkC,CACjE,KAAK,CAAC,OAAO,CACd;QACD,uBAAuB,EAAE,CAAC,aAAa,EAAE,EAAE;YACzC,QAAQ,CAAC,IAAI,GAAG,aAAa,CAAC,sBAAsB,CAAC,GAAG,CACtD,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,YAAY,CACrD,CAAC;QACJ,CAAC;KACF,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,IAAI;QAChB,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,OAAO;QACL,WAAW,EAAE,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK;QACtC,eAAe,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY;QAC3C,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,WAAW,EAAE,gDAAgD,CAC3D,KAAK,CAAC,gBAAgB,CACvB;QACD,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,SAAS,iCAAiC,CACxC,IAA0C,EAC1C,KAAc;IAEd,uEAAuE;IACvE,8EAA8E;IAC9E,+EAA+E;IAC/E,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,0BAA0B,CAAC,KAAK,CAAC;QAAE,OAAO;IAChE,IAAI,CAAC;QACH,2EAA2E;QAC3E,kCAAkC;QAClC,KAAK,OAAO,CAAC,OAAO,CAClB,IAAI,CAAC,QAAQ,CAAC,mDAAmD,EAAE,KAAK,CAAC,CAC1E,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,8DAA8D;IAChE,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAAC,KAYnD;IACC,MAAM,EACJ,UAAU,EACV,MAAM,EACN,SAAS,EACT,wBAAwB,EACxB,OAAO,EACP,iBAAiB,GAClB,GAAG,KAAK,CAAC;IACV,MAAM,MAAM,GAAG,2BAA2B,CAAC,OAAO,CAAC,CAAC;IACpD,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,SAAS,CAAC;IACvE,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC;IACtC,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC/C,IAAI,CAAC,MAAM,IAAI,CAAC,eAAe,EAAE,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,4FAA4F,CAC7F,CAAC;QACF,KAAK,CAAC,SAAS,EAAE,CAAC;YAChB,OAAO,EAAE,sCAAsC;YAC/C,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC;YACxC,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,MAAM;YACN,UAAU;YACV,qBAAqB,EAAE,KAAK,CAAC,qBAAqB;YAClD,OAAO;YACP,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,SAAS;YACT,wBAAwB;YACxB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,YAAY,EAAE,KAAK,CAAC,SAAS;YAC7B,iBAAiB;YACjB,eAAe;YACf,+BAA+B,EAC7B,kCAAkC,CAAC,OAAO,CAAC;SAC9C,CAAC,CAAC;QACH,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,iCAAiC,SAAS,SAAS,MAAM,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,cAAc,iBAAiB,EAAE,CACnI,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QAE/C,MAAM,yCAAyC,CAAC,oBAAoB,CAClE,OAAO,EACP,UAAU,EACV,MAAM,CAAC,kBAAkB,EACzB,EAAE,YAAY,EAAE,KAAK,CAAC,SAAS,EAAE,CAClC,CAAC;QAEF,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,uCAAuC,CAC3C,KAAK,EACL,OAAO,CAAC,IAAI,CAAC,sBAAsB,EACnC;YACE,QAAQ,EAAE,UAAU;YACpB,UAAU,EAAE,UAAU;YACtB,SAAS,EAAE,YAAY,SAAS,EAAE;SACnC,CACF,CAAC;QACF,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,iCAAiC,SAAS,SAAS,MAAM,IAAI,SAAS,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,cAAc,iBAAiB,KAAK,OAAO,EAAE,CAC/I,CAAC;QACF,sEAAsE;QACtE,4EAA4E;QAC5E,oEAAoE;QACpE,iCAAiC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvD,KAAK,CAAC,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,2BAA2B,CAAC,KAQ1C;IACC,OAAO,6BAA6B,CAAC;QACnC,GAAG,KAAK;QACR,SAAS,EAAE,MAAM;KAClB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,6BAA6B,CAAC,KAWnD;IACC,OAAO,6BAA6B,CAAC;QACnC,GAAG,KAAK;QACR,SAAS,EAAE,QAAQ;KACpB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAAC,KAcjD;IACC,MAAM,EACJ,UAAU,EACV,MAAM,EACN,wBAAwB,EACxB,OAAO,EACP,iBAAiB,GAClB,GAAG,KAAK,CAAC;IACV,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,gBAAgB,GAAG,MAAM,yBAAyB,CAAC;QACvD,UAAU;QACV,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO;KACR,CAAC,CAAC;IACH,IAAI,CAAC,gBAAgB,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK,EAAE,CAAC;QACvD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,gBAAgB,CAAC,UAAU,KAAK,UAAU;QAC5C,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,MAAM,yBAAyB,GAC7B,0CAA0C,CAAC,gBAAgB,CAAC,CAAC;IAE/D,IAAI,wBAAwB,GAAsB,yBAAyB,CAAC;IAC5E,IAAI,cAAc,GAAsC,IAAI,CAAC;IAC7D,KAAK,MAAM,UAAU,IAAI,IAAI,GAAG,CAAC;QAC/B,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC;QAC9C,GAAG,CAAC,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC;KAC5C,CAAC,EAAE,CAAC;QACH,IAAI,wBAAwB,CAAC,QAAQ,CAAC,UAAU,CAAC;YAAE,SAAS;QAC5D,MAAM,cAAc,GAAG,MAAM,2BAA2B,CAAC;YACvD,UAAU;YACV,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,MAAM;YACN,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,wBAAwB;YACxB,OAAO;YACP,iBAAiB,EAAE,UAAU;SAC9B,CAAC,CAAC;QACH,IAAI,CAAC,cAAc,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK,EAAE,CAAC;YACrD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,cAAc,GAAG,cAAc,CAAC;QAChC,wBAAwB,GAAG,cAAc,CAAC,kBAAkB,CAAC;IAC/D,CAAC;IAED,MAAM,kBAAkB,GAAG,qCAAqC,CAAC;QAC/D,GAAG,KAAK;QACR,kBAAkB,EAAE,wBAAwB;KAC7C,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,MAAM,0BAA0B,CAAC;QAChD,GAAG,KAAK;QACR,cAAc;QACd,wBAAwB;QACxB,kBAAkB;KACnB,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,cAAc,GAAG,QAAQ,CAAC,cAAc,CAAC;IACzC,wBAAwB,GAAG,QAAQ,CAAC,wBAAwB,CAAC;IAE7D,MAAM,eAAe,GAAG,gCAAgC,CACtD,wBAAwB,EACxB,iBAAiB,CAClB,CAAC;IACF,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GACV,QAAQ,CAAC,wBAAwB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;IACxE,OAAO,cAAc;QACnB,CAAC,CAAC,2BAA2B,CAAC;YAC1B,QAAQ,EAAE,cAAc;YACxB,eAAe;YACf,MAAM;SACP,CAAC;QACJ,CAAC,CAAC,mCAAmC,CAAC;YAClC,gBAAgB;YAChB,wBAAwB;YACxB,OAAO;YACP,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,kBAAkB,EAAE,wBAAwB;YAC5C,eAAe;YACf,MAAM;SACP,CAAC,CAAC;AACT,CAAC;AAED,4EAA4E;AAC5E,0EAA0E;AAC1E,oDAAoD;AACpD,KAAK,UAAU,0BAA0B,CAAC,KAYzC;IAKC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,EAAE,GAAG,KAAK,CAAC;IACrD,IAAI,EAAE,cAAc,EAAE,wBAAwB,EAAE,GAAG,KAAK,CAAC;IACzD,MAAM,wBAAwB,GAAa,EAAE,CAAC;IAC9C,KAAK,MAAM,iBAAiB,IAAI,KAAK,CAAC,kBAAkB,EAAE,CAAC;QACzD,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QAC/C,MAAM,gBAAgB,GAAG,MAAM,6BAA6B,CAAC;YAC3D,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,MAAM;YACN,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;YACxD,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;YACxC,OAAO;YACP,iBAAiB,EAAE,iBAAiB;SACrC,CAAC,CAAC;QACH,IAAI,KAAK,CAAC,SAAS,EAAE,EAAE,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QAC/C,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACtB,wBAAwB,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,GAAG,CACd,4BAA4B,MAAM,kBAAkB,iBAAiB,8BAA8B,iBAAiB,EAAE,CACvH,CAAC;YACF,SAAS;QACX,CAAC;QAED,cAAc,GAAG,gBAAgB,CAAC;QAClC,wBAAwB,GAAG,gBAAgB,CAAC,kBAAkB,CAAC;IACjE,CAAC;IACD,OAAO,EAAE,wBAAwB,EAAE,cAAc,EAAE,wBAAwB,EAAE,CAAC;AAChF,CAAC","sourcesContent":["import type { VerifiedDocumentLinkSetManifest } from \"@tearleads/crypto\";\nimport type { DocumentWriterProjectionResponse } from \"@tearleads/validators/response\";\nimport {\n assertDocumentWriterProjectionConsistent,\n readLinkedContainerIdsFromDocumentManifest,\n} from \"../../data/documents/shared/projection\";\nimport { persistedDocumentCreateStateFromWriterProjection } from \"../../data/documents/shared/responses\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../data/keyingProjectionVerification\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport { sqlDocumentContainerProjectionPersistence } from \"../../data/persistence/containers/documentContainerProjectionPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { isDatabaseUnavailableError } from \"../../data/sync/databaseUnavailable\";\nimport {\n type DocumentLinkSetFailureHandler,\n type RelinkRemoteDocumentResult,\n relinkRemoteDocument,\n resolveDocumentCreateAuthor,\n} from \"../documents\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../principals/runtimePolicyWarmer\";\nimport { resolveContainerDocumentMoveUnlinkIds } from \"./documentMoveUnlinkIds\";\nimport type { ContainerContentsWorkflowRuntime } from \"./runtime\";\n\ntype ContainerDocumentLinkApi = Parameters<\n typeof relinkRemoteDocument\n>[0][\"apiClient\"];\ntype ContainerDocumentLinkOperation = Parameters<\n typeof relinkRemoteDocument\n>[0][\"operation\"];\ntype RemoteDocumentPersistedState =\n RelinkRemoteDocumentResult[\"persistedState\"];\n\n// A cold-cache denial must keep its HTTP status: collapsing a 403 to null\n// would leave an access-denied move routinely retriable instead of parking\n// it for the access-restored signal (row 7).\nasync function fetchMoveWriterProjection(input: {\n documentId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n runtime: ContainerDocumentLinkRuntime;\n}): Promise<Awaited<\n ReturnType<ContainerDocumentLinkApi[\"getDocumentWriterProjection\"]>\n> | null> {\n const { apiClient } = input.runtime;\n const result = await apiClient.getDocumentWriterProjectionResult(\n input.documentId,\n { reportErrors: false },\n );\n if (result.ok) {\n return result.data;\n }\n result.report();\n input.onFailure?.({ message: result.message, status: result.status });\n return null;\n}\n\ninterface ContainerDocumentLinkRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: ContainerDocumentLinkApi &\n Pick<\n ContainerContentsWorkflowRuntime[\"apiClient\"],\n \"getPrincipalPolicyPages\" | \"getProjectionPolicyHistoryPages\"\n >;\n}\n\ntype MoveRemoteContainerDocumentStatus = \"complete\" | \"partial\";\n\ninterface MoveRemoteContainerDocumentResult {\n accessEpoch: number | null;\n accessStateHash: string | null;\n linkedContainerIds: readonly string[];\n nextContainerId: string;\n remoteState: RemoteDocumentPersistedState | null;\n status: MoveRemoteContainerDocumentStatus;\n}\n\nexport type { RemoteDocumentPersistedState };\n\nexport async function initializeDocumentLinksSchema(\n execSql: ExecSql,\n): Promise<void> {\n await sqlDocumentContainerProjectionPersistence.ensureSchema(execSql);\n}\n\nexport async function listDocumentLinkedContainerIds(\n execSql: ExecSql,\n documentId: string,\n): Promise<ReadonlyArray<string>> {\n return sqlDocumentContainerProjectionPersistence.listLinkedContainerIds(\n execSql,\n documentId,\n );\n}\n\nexport async function replaceDocumentLinks(\n execSql: ExecSql,\n documentId: string,\n containerIds: ReadonlyArray<string>,\n): Promise<void> {\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinks(\n execSql,\n documentId,\n containerIds,\n );\n}\n\nfunction resolveActiveDocumentContainerId(\n linkedContainerIds: ReadonlyArray<string>,\n preferredContainerId: string,\n): string | null {\n if (linkedContainerIds.includes(preferredContainerId)) {\n return preferredContainerId;\n }\n\n return linkedContainerIds[0] ?? null;\n}\n\nfunction containerDocumentMoveResult(input: {\n document: RelinkRemoteDocumentResult;\n nextContainerId: string;\n status: MoveRemoteContainerDocumentStatus;\n}): MoveRemoteContainerDocumentResult {\n return {\n accessEpoch: input.document.plan.state.epoch,\n accessStateHash: input.document.response.accessManifest.manifestHash,\n linkedContainerIds: input.document.linkedContainerIds,\n nextContainerId: input.nextContainerId,\n remoteState: input.document.persistedState,\n status: input.status,\n };\n}\n\nasync function containerDocumentAlreadyMovedResult(input: {\n writerProjection: DocumentWriterProjectionResponse;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentLinkRuntime;\n isCurrent?: (() => boolean) | undefined;\n linkedContainerIds: readonly string[];\n nextContainerId: string;\n status: MoveRemoteContainerDocumentStatus;\n}): Promise<MoveRemoteContainerDocumentResult> {\n const verified: { head?: VerifiedDocumentLinkSetManifest | undefined } = {};\n await assertDocumentWriterProjectionConsistent(input.writerProjection, {\n execSql: input.runtime.infra.execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.isCurrent,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n onVerifiedAuthorization: (authorization) => {\n verified.head = authorization.documentManifestByHash.get(\n input.writerProjection.documentManifest.manifestHash,\n );\n },\n });\n if (!verified.head)\n throw new Error(\"Move replay lacks a verified document head\");\n return {\n accessEpoch: verified.head.state.epoch,\n accessStateHash: verified.head.manifestHash,\n linkedContainerIds: input.linkedContainerIds,\n nextContainerId: input.nextContainerId,\n remoteState: persistedDocumentCreateStateFromWriterProjection(\n input.writerProjection,\n ),\n status: input.status,\n };\n}\n\n/**\n * Hand a link/unlink/move/purge failure to host diagnostics as a real `Error`.\n * The caller's string-only log line stays the fallback for a host without\n * `logError`, so this never logs a second copy of the same failure.\n */\nfunction reportDocumentLinkMutationFailure(\n util: ContainerDocumentLinkRuntime[\"util\"],\n error: unknown,\n): void {\n // `ApiClient` returns `RequestFailure` objects instead of throwing, so\n // offline and 403/404/409 already left through the `!result` branch: a status\n // filter here would be dead code. Only a database lost mid-flight stays quiet.\n if (!util.logError || isDatabaseUnavailableError(error)) return;\n try {\n // A host logger that throws or rejects must not replace the null result or\n // skip the failure handler below.\n void Promise.resolve(\n util.logError(\"Container contents: document link mutation failed\", error),\n ).catch(() => undefined);\n } catch {\n // Hosts may throw synchronously or return a rejected promise.\n }\n}\n\nexport async function relinkRemoteContainerDocument(input: {\n beforeSubmit?: (() => Promise<boolean>) | undefined;\n documentId: string;\n expectedSubtreeRootId?: string | undefined;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n operation: ContainerDocumentLinkOperation;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot?: Uint8Array | undefined;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<RelinkRemoteDocumentResult | null> {\n const {\n documentId,\n noteId,\n operation,\n resolveProjectionUserKey,\n runtime,\n targetContainerId,\n } = input;\n const author = resolveDocumentCreateAuthor(runtime);\n const targetSecretKey = runtime.crypto.encapsulationKeyPair?.secretKey;\n const execSql = runtime.infra.execSql;\n if (input.isCurrent?.() === false) return null;\n if (!author || !targetSecretKey) {\n runtime.util.log(\n \"Container contents: document mutation skipped because the local key context is unavailable\",\n );\n input.onFailure?.({\n message: \"the local key context is unavailable\",\n status: null,\n });\n return null;\n }\n\n try {\n const result = await relinkRemoteDocument({\n beforeSubmit: input.beforeSubmit,\n apiClient: runtime.apiClient,\n author,\n documentId,\n expectedSubtreeRootId: input.expectedSubtreeRootId,\n execSql,\n onFailure: input.onFailure,\n operation,\n resolveProjectionUserKey,\n rotationSnapshot: input.rotationSnapshot,\n stillCurrent: input.isCurrent,\n targetContainerId,\n targetSecretKey,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!result) {\n runtime.util.log(\n `Container contents: failed to ${operation} note ${noteId} ${operation === \"link\" ? \"to\" : \"from\"} container ${targetContainerId}`,\n );\n return null;\n }\n if (input.isCurrent?.() === false) return null;\n\n await sqlDocumentContainerProjectionPersistence.replaceDocumentLinks(\n execSql,\n documentId,\n result.linkedContainerIds,\n { stillCurrent: input.isCurrent },\n );\n\n return result;\n } catch (error) {\n await reportAndRethrowKeyingVerificationError(\n error,\n runtime.util.reportSecurityIncident,\n {\n objectId: documentId,\n objectKind: \"document\",\n operation: `document.${operation}`,\n },\n );\n const message = errorMessage(error);\n runtime.util.log(\n `Container contents: failed to ${operation} note ${noteId} ${operation === \"link\" ? \"to\" : \"from\"} container ${targetContainerId}: ${message}`,\n );\n // After the keying rethrow above, so a verification failure keeps its\n // security incident instead of being reported twice, and before the failure\n // handler, so a throwing caller callback cannot swallow the report.\n reportDocumentLinkMutationFailure(runtime.util, error);\n input.onFailure?.({ message, status: null });\n return null;\n }\n}\n\nasync function linkRemoteContainerDocument(input: {\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<RelinkRemoteDocumentResult | null> {\n return relinkRemoteContainerDocument({\n ...input,\n operation: \"link\",\n });\n}\n\nexport async function unlinkRemoteContainerDocument(input: {\n beforeSubmit?: (() => Promise<boolean>) | undefined;\n documentId: string;\n expectedSubtreeRootId?: string | undefined;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot: Uint8Array;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<RelinkRemoteDocumentResult | null> {\n return relinkRemoteContainerDocument({\n ...input,\n operation: \"unlink\",\n });\n}\n\nexport async function moveRemoteContainerDocument(input: {\n linkOnly?: boolean | undefined;\n additionalLinkContainerIds?: readonly string[] | undefined;\n removedLinkContainerIds?: readonly string[] | undefined;\n currentContainerId: string;\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n replaceLinkedContainers?: boolean | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot: Uint8Array;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n}): Promise<MoveRemoteContainerDocumentResult | null> {\n const {\n documentId,\n noteId,\n resolveProjectionUserKey,\n runtime,\n targetContainerId,\n } = input;\n if (input.isCurrent?.() === false) return null;\n const writerProjection = await fetchMoveWriterProjection({\n documentId,\n onFailure: input.onFailure,\n runtime,\n });\n if (!writerProjection || input.isCurrent?.() === false) {\n return null;\n }\n if (writerProjection.documentId !== documentId)\n throw new Error(\"Move replay document id mismatch\");\n const initialLinkedContainerIds =\n readLinkedContainerIdsFromDocumentManifest(writerProjection);\n\n let latestLinkedContainerIds: readonly string[] = initialLinkedContainerIds;\n let latestDocument: RelinkRemoteDocumentResult | null = null;\n for (const linkTarget of new Set([\n ...(input.linkOnly ? [] : [targetContainerId]),\n ...(input.additionalLinkContainerIds ?? []),\n ])) {\n if (latestLinkedContainerIds.includes(linkTarget)) continue;\n const linkedDocument = await linkRemoteContainerDocument({\n documentId,\n isCurrent: input.isCurrent,\n noteId,\n onFailure: input.onFailure,\n resolveProjectionUserKey,\n runtime,\n targetContainerId: linkTarget,\n });\n if (!linkedDocument || input.isCurrent?.() === false) {\n return null;\n }\n\n latestDocument = linkedDocument;\n latestLinkedContainerIds = linkedDocument.linkedContainerIds;\n }\n\n const unlinkContainerIds = resolveContainerDocumentMoveUnlinkIds({\n ...input,\n linkedContainerIds: latestLinkedContainerIds,\n });\n\n const unlinked = await unlinkMovedDocumentSources({\n ...input,\n latestDocument,\n latestLinkedContainerIds,\n unlinkContainerIds,\n });\n if (!unlinked) return null;\n latestDocument = unlinked.latestDocument;\n latestLinkedContainerIds = unlinked.latestLinkedContainerIds;\n\n const nextContainerId = resolveActiveDocumentContainerId(\n latestLinkedContainerIds,\n targetContainerId,\n );\n if (!nextContainerId) {\n return null;\n }\n\n const status =\n unlinked.failedUnlinkContainerIds.length > 0 ? \"partial\" : \"complete\";\n return latestDocument\n ? containerDocumentMoveResult({\n document: latestDocument,\n nextContainerId,\n status,\n })\n : containerDocumentAlreadyMovedResult({\n writerProjection,\n resolveProjectionUserKey,\n runtime,\n isCurrent: input.isCurrent,\n linkedContainerIds: latestLinkedContainerIds,\n nextContainerId,\n status,\n });\n}\n\n// The unlink half of a move: every source is attempted even when an earlier\n// one fails, so one dead source cannot strand the others; a failed unlink\n// leaves the move \"partial\" for the queue to retry.\nasync function unlinkMovedDocumentSources(input: {\n documentId: string;\n isCurrent?: (() => boolean) | undefined;\n latestDocument: RelinkRemoteDocumentResult | null;\n latestLinkedContainerIds: readonly string[];\n noteId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot: Uint8Array;\n runtime: ContainerDocumentLinkRuntime;\n targetContainerId: string;\n unlinkContainerIds: readonly string[];\n}): Promise<{\n failedUnlinkContainerIds: readonly string[];\n latestDocument: RelinkRemoteDocumentResult | null;\n latestLinkedContainerIds: readonly string[];\n} | null> {\n const { noteId, runtime, targetContainerId } = input;\n let { latestDocument, latestLinkedContainerIds } = input;\n const failedUnlinkContainerIds: string[] = [];\n for (const unlinkContainerId of input.unlinkContainerIds) {\n if (input.isCurrent?.() === false) return null;\n const unlinkedDocument = await unlinkRemoteContainerDocument({\n documentId: input.documentId,\n isCurrent: input.isCurrent,\n noteId,\n onFailure: input.onFailure,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n rotationSnapshot: input.rotationSnapshot,\n runtime,\n targetContainerId: unlinkContainerId,\n });\n if (input.isCurrent?.() === false) return null;\n if (!unlinkedDocument) {\n failedUnlinkContainerIds.push(unlinkContainerId);\n runtime.util.log(\n `Container contents: note ${noteId} was linked to ${targetContainerId} but failed to unlink from ${unlinkContainerId}`,\n );\n continue;\n }\n\n latestDocument = unlinkedDocument;\n latestLinkedContainerIds = unlinkedDocument.linkedContainerIds;\n }\n return { failedUnlinkContainerIds, latestDocument, latestLinkedContainerIds };\n}\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { DocumentPurgeResponse } from "../../internal/validators/response/index.js";
|
|
2
2
|
import type { StoredDocumentKind } from "../../data/documents/documentKinds.js";
|
|
3
3
|
import type { ProjectionUserKeyResolver } from "../../data/keyingProjectionVerification.js";
|
|
4
|
-
import { type DocumentsPersistence } from "../documents/index.js";
|
|
4
|
+
import { type DocumentsPersistence, deletePersistedDocument } from "../documents/index.js";
|
|
5
5
|
import type { ContainerContentsWorkflowRuntime } from "./runtime.js";
|
|
6
6
|
type ContainerDocumentPurgeApi = Pick<ContainerContentsWorkflowRuntime["apiClient"], "getPrincipalPolicyPages" | "getProjectionPolicyHistoryPages" | "getDocumentPurgeProof" | "getDocumentWriterProjectionResult" | "purgeDocument">;
|
|
7
7
|
type DocumentPurgeResult = DocumentPurgeResponse | {
|
|
@@ -13,7 +13,10 @@ interface ContainerDocumentPurgeRuntime extends Pick<ContainerContentsWorkflowRu
|
|
|
13
13
|
apiClient: ContainerDocumentPurgeApi;
|
|
14
14
|
}
|
|
15
15
|
interface PurgeRemoteContainerDocumentInput {
|
|
16
|
+
beforeSubmit?: (() => Promise<boolean>) | undefined;
|
|
16
17
|
documentId: string;
|
|
18
|
+
expectedSubtreeRootId?: string | undefined;
|
|
19
|
+
stillCurrent?: (() => boolean) | undefined;
|
|
17
20
|
documentKind: StoredDocumentKind;
|
|
18
21
|
noteId: string;
|
|
19
22
|
persistence?: DocumentsPersistence | undefined;
|
|
@@ -24,7 +27,9 @@ export declare function purgeRemoteContainerDocument(input: PurgeRemoteContainer
|
|
|
24
27
|
interface ContainerDocumentLocalPurgeRuntime extends Pick<ContainerContentsWorkflowRuntime, "infra" | "util"> {
|
|
25
28
|
}
|
|
26
29
|
export declare function purgeLocalContainerDocument(input: {
|
|
30
|
+
beforeDeleteInTransaction?: Parameters<typeof deletePersistedDocument>[0]["beforeDeleteInTransaction"];
|
|
27
31
|
noteId: string;
|
|
32
|
+
expectedContainerId?: string | null | undefined;
|
|
28
33
|
persistence?: DocumentsPersistence | undefined;
|
|
29
34
|
runtime: ContainerDocumentLocalPurgeRuntime;
|
|
30
35
|
}): Promise<DocumentPurgeResult>;
|
|
@@ -51,8 +51,11 @@ export async function purgeRemoteContainerDocument(input) {
|
|
|
51
51
|
let deleted = false;
|
|
52
52
|
const response = await purgeRemoteDocument({
|
|
53
53
|
apiClient: runtime.apiClient,
|
|
54
|
+
beforeSubmit: input.beforeSubmit,
|
|
54
55
|
author,
|
|
55
56
|
documentId,
|
|
57
|
+
expectedSubtreeRootId: input.expectedSubtreeRootId,
|
|
58
|
+
stillCurrent: input.stillCurrent,
|
|
56
59
|
execSql: runtime.infra.execSql,
|
|
57
60
|
onVerifiedPurge: async ({ commitPurgeProof }) => {
|
|
58
61
|
deleted = await commitVerifiedDocumentPurge({
|
|
@@ -71,8 +74,8 @@ export async function purgeRemoteContainerDocument(input) {
|
|
|
71
74
|
runtime.util.log(`Container contents: failed to purge note ${noteId} (document ${documentId})`);
|
|
72
75
|
return null;
|
|
73
76
|
}
|
|
74
|
-
if (deleted
|
|
75
|
-
void reclaimDocumentOrphanBlobs(runtime);
|
|
77
|
+
if (deleted) {
|
|
78
|
+
void reclaimDocumentOrphanBlobs(runtime, persistence);
|
|
76
79
|
}
|
|
77
80
|
runtime.util.log(`Container contents: purged note ${noteId} (document ${documentId})`);
|
|
78
81
|
return response;
|
|
@@ -96,11 +99,15 @@ export async function purgeLocalContainerDocument(input) {
|
|
|
96
99
|
try {
|
|
97
100
|
await persistence.ensureSchema(runtime.infra.execSql);
|
|
98
101
|
const expectedRecord = await persistence.loadDocument(runtime.infra.execSql, noteId);
|
|
99
|
-
if (!expectedRecord ||
|
|
102
|
+
if (!expectedRecord ||
|
|
103
|
+
expectedRecord.documentId !== null ||
|
|
104
|
+
(input.expectedContainerId !== undefined &&
|
|
105
|
+
expectedRecord.containerId !== input.expectedContainerId)) {
|
|
100
106
|
runtime.util.log(`Container contents: local-only purge refused for note ${noteId} because it is absent or remote-backed`);
|
|
101
107
|
return null;
|
|
102
108
|
}
|
|
103
109
|
const deleted = await deletePersistedDocument({
|
|
110
|
+
beforeDeleteInTransaction: input.beforeDeleteInTransaction,
|
|
104
111
|
documentProjectors: runtime.infra.documentProjectors,
|
|
105
112
|
execSql: runtime.infra.execSql,
|
|
106
113
|
expectedRecord,
|
|
@@ -111,8 +118,8 @@ export async function purgeLocalContainerDocument(input) {
|
|
|
111
118
|
runtime.util.log(`Container contents: local-only purge refused for note ${noteId} because its identity changed`);
|
|
112
119
|
return null;
|
|
113
120
|
}
|
|
114
|
-
if (deleted
|
|
115
|
-
void reclaimDocumentOrphanBlobs(runtime);
|
|
121
|
+
if (deleted) {
|
|
122
|
+
void reclaimDocumentOrphanBlobs(runtime, persistence);
|
|
116
123
|
}
|
|
117
124
|
runtime.util.log(`Container contents: purged local-only note ${noteId}`);
|
|
118
125
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documentPurge.js","sourceRoot":"","sources":["documentPurge.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentPurgeResponse } from \"@tearleads/validators/response\";\nimport type { StoredDocumentKind } from \"../../data/documents/documentKinds\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../data/keyingProjectionVerification\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport { rethrowProjectionVerificationCancelled } from \"../../data/keyingProjectionVerification/types\";\nimport {\n type DocumentsPersistence,\n defaultDocumentsPersistence,\n deletePersistedDocument,\n purgeRemoteDocument,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n} from \"../documents\";\nimport { ensureDocumentClientProjectionTables } from \"../documents/persistence\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../principals/runtimePolicyWarmer\";\nimport type { ContainerContentsWorkflowRuntime } from \"./runtime\";\n\ntype ContainerDocumentPurgeApi = Pick<\n ContainerContentsWorkflowRuntime[\"apiClient\"],\n | \"getPrincipalPolicyPages\"\n | \"getProjectionPolicyHistoryPages\"\n | \"getDocumentPurgeProof\"\n | \"getDocumentWriterProjectionResult\"\n | \"purgeDocument\"\n>;\ntype DocumentPurgeResult =\n | DocumentPurgeResponse\n | {\n readonly documentId: string;\n readonly purgedAt: string;\n readonly reclaimedBlobStorageKeys: readonly string[];\n }\n | null;\n\ninterface ContainerDocumentPurgeRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: ContainerDocumentPurgeApi;\n}\n\ninterface PurgeRemoteContainerDocumentInput {\n documentId: string;\n documentKind: StoredDocumentKind;\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentPurgeRuntime;\n}\n\nasync function deleteAbsentDocumentState(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n return input.persistence.deleteDocumentSideRowsIfAbsent(\n input.runtime.infra.execSql,\n input.noteId,\n input.documentId,\n async (transactionExecSql) => {\n await input.runtime.infra.documentProjectors.deleteStoredDocumentClientProjection(\n {\n documentKind: input.documentKind,\n execSql: transactionExecSql,\n localId: input.noteId,\n },\n );\n await input.commitPurgeProof(transactionExecSql);\n },\n );\n}\n\nasync function commitVerifiedDocumentPurge(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n await input.persistence.ensureSchema(input.runtime.infra.execSql);\n await ensureDocumentClientProjectionTables({\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n });\n const expectedRecord = await input.persistence.loadDocument(\n input.runtime.infra.execSql,\n input.noteId,\n );\n if (expectedRecord && expectedRecord.documentId !== input.documentId) {\n throw new Error(\n \"Local document identity changed while its remote purge was committing\",\n );\n }\n const deleted = expectedRecord\n ? await deletePersistedDocument({\n beforeDeleteInTransaction: input.commitPurgeProof,\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n expectedRecord,\n localId: input.noteId,\n persistence: input.persistence,\n })\n : await deleteAbsentDocumentState(input);\n if (!deleted) {\n throw new Error(\n \"Local document state changed while its remote purge was committing\",\n );\n }\n return true;\n}\n\nexport async function purgeRemoteContainerDocument(\n input: PurgeRemoteContainerDocumentInput,\n): Promise<DocumentPurgeResult> {\n const { documentId, noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} because the local signing context is unavailable`,\n );\n return null;\n }\n let deleted = false;\n const response = await purgeRemoteDocument({\n apiClient: runtime.apiClient,\n author,\n documentId,\n execSql: runtime.infra.execSql,\n onVerifiedPurge: async ({ commitPurgeProof }) => {\n deleted = await commitVerifiedDocumentPurge({\n commitPurgeProof,\n documentId,\n documentKind: input.documentKind,\n noteId,\n persistence,\n runtime,\n });\n },\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!response) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId})`,\n );\n return null;\n }\n if (deleted && persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(runtime);\n }\n runtime.util.log(\n `Container contents: purged note ${noteId} (document ${documentId})`,\n );\n return response;\n } catch (error) {\n // A cancelled verification is neither a failure nor a completed purge; the\n // caller that owns the generation decides what to do with it.\n rethrowProjectionVerificationCancelled(error);\n await reportAndRethrowKeyingVerificationError(\n error,\n runtime.util.reportSecurityIncident,\n {\n objectId: documentId,\n objectKind: \"document\",\n operation: \"document.purge\",\n },\n );\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId}): ${errorMessage(error)}`,\n );\n return null;\n }\n}\n\ninterface ContainerDocumentLocalPurgeRuntime\n extends Pick<ContainerContentsWorkflowRuntime, \"infra\" | \"util\"> {}\n\nexport async function purgeLocalContainerDocument(input: {\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n runtime: ContainerDocumentLocalPurgeRuntime;\n}): Promise<DocumentPurgeResult> {\n const { noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n await persistence.ensureSchema(runtime.infra.execSql);\n const expectedRecord = await persistence.loadDocument(\n runtime.infra.execSql,\n noteId,\n );\n if (!expectedRecord || expectedRecord.documentId !== null) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because it is absent or remote-backed`,\n );\n return null;\n }\n const deleted = await deletePersistedDocument({\n documentProjectors: runtime.infra.documentProjectors,\n execSql: runtime.infra.execSql,\n expectedRecord,\n localId: noteId,\n persistence,\n });\n if (!deleted) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because its identity changed`,\n );\n return null;\n }\n if (deleted && persistence === defaultDocumentsPersistence) {\n void reclaimDocumentOrphanBlobs(runtime);\n }\n runtime.util.log(`Container contents: purged local-only note ${noteId}`);\n return {\n documentId: noteId,\n purgedAt: new Date().toISOString(),\n reclaimedBlobStorageKeys: [],\n };\n } catch (error) {\n runtime.util.log(\n `Container contents: failed to purge local-only note ${noteId}: ${errorMessage(error)}`,\n );\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"documentPurge.js","sourceRoot":"","sources":["documentPurge.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentPurgeResponse } from \"@tearleads/validators/response\";\nimport type { StoredDocumentKind } from \"../../data/documents/documentKinds\";\nimport { errorMessage } from \"../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../data/keyingProjectionVerification\";\nimport { reportAndRethrowKeyingVerificationError } from \"../../data/keyingProjectionVerification/error\";\nimport { rethrowProjectionVerificationCancelled } from \"../../data/keyingProjectionVerification/types\";\nimport {\n type DocumentsPersistence,\n defaultDocumentsPersistence,\n deletePersistedDocument,\n purgeRemoteDocument,\n reclaimDocumentOrphanBlobs,\n resolveDocumentCreateAuthor,\n} from \"../documents\";\nimport { ensureDocumentClientProjectionTables } from \"../documents/persistence\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../principals/runtimePolicyWarmer\";\nimport type { ContainerContentsWorkflowRuntime } from \"./runtime\";\n\ntype ContainerDocumentPurgeApi = Pick<\n ContainerContentsWorkflowRuntime[\"apiClient\"],\n | \"getPrincipalPolicyPages\"\n | \"getProjectionPolicyHistoryPages\"\n | \"getDocumentPurgeProof\"\n | \"getDocumentWriterProjectionResult\"\n | \"purgeDocument\"\n>;\ntype DocumentPurgeResult =\n | DocumentPurgeResponse\n | {\n readonly documentId: string;\n readonly purgedAt: string;\n readonly reclaimedBlobStorageKeys: readonly string[];\n }\n | null;\n\ninterface ContainerDocumentPurgeRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n apiClient: ContainerDocumentPurgeApi;\n}\n\ninterface PurgeRemoteContainerDocumentInput {\n beforeSubmit?: (() => Promise<boolean>) | undefined;\n documentId: string;\n expectedSubtreeRootId?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n documentKind: StoredDocumentKind;\n noteId: string;\n persistence?: DocumentsPersistence | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerDocumentPurgeRuntime;\n}\n\nasync function deleteAbsentDocumentState(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n return input.persistence.deleteDocumentSideRowsIfAbsent(\n input.runtime.infra.execSql,\n input.noteId,\n input.documentId,\n async (transactionExecSql) => {\n await input.runtime.infra.documentProjectors.deleteStoredDocumentClientProjection(\n {\n documentKind: input.documentKind,\n execSql: transactionExecSql,\n localId: input.noteId,\n },\n );\n await input.commitPurgeProof(transactionExecSql);\n },\n );\n}\n\nasync function commitVerifiedDocumentPurge(input: {\n readonly commitPurgeProof: (\n transactionExecSql: ContainerDocumentPurgeRuntime[\"infra\"][\"execSql\"],\n ) => Promise<void>;\n readonly documentId: string;\n readonly documentKind: StoredDocumentKind;\n readonly noteId: string;\n readonly persistence: DocumentsPersistence;\n readonly runtime: ContainerDocumentPurgeRuntime;\n}): Promise<boolean> {\n await input.persistence.ensureSchema(input.runtime.infra.execSql);\n await ensureDocumentClientProjectionTables({\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n });\n const expectedRecord = await input.persistence.loadDocument(\n input.runtime.infra.execSql,\n input.noteId,\n );\n if (expectedRecord && expectedRecord.documentId !== input.documentId) {\n throw new Error(\n \"Local document identity changed while its remote purge was committing\",\n );\n }\n const deleted = expectedRecord\n ? await deletePersistedDocument({\n beforeDeleteInTransaction: input.commitPurgeProof,\n documentProjectors: input.runtime.infra.documentProjectors,\n execSql: input.runtime.infra.execSql,\n expectedRecord,\n localId: input.noteId,\n persistence: input.persistence,\n })\n : await deleteAbsentDocumentState(input);\n if (!deleted) {\n throw new Error(\n \"Local document state changed while its remote purge was committing\",\n );\n }\n return true;\n}\n\nexport async function purgeRemoteContainerDocument(\n input: PurgeRemoteContainerDocumentInput,\n): Promise<DocumentPurgeResult> {\n const { documentId, noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n const author = resolveDocumentCreateAuthor(runtime);\n if (!author) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} because the local signing context is unavailable`,\n );\n return null;\n }\n let deleted = false;\n const response = await purgeRemoteDocument({\n apiClient: runtime.apiClient,\n beforeSubmit: input.beforeSubmit,\n author,\n documentId,\n expectedSubtreeRootId: input.expectedSubtreeRootId,\n stillCurrent: input.stillCurrent,\n execSql: runtime.infra.execSql,\n onVerifiedPurge: async ({ commitPurgeProof }) => {\n deleted = await commitVerifiedDocumentPurge({\n commitPurgeProof,\n documentId,\n documentKind: input.documentKind,\n noteId,\n persistence,\n runtime,\n });\n },\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n if (!response) {\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId})`,\n );\n return null;\n }\n if (deleted) {\n void reclaimDocumentOrphanBlobs(runtime, persistence);\n }\n runtime.util.log(\n `Container contents: purged note ${noteId} (document ${documentId})`,\n );\n return response;\n } catch (error) {\n // A cancelled verification is neither a failure nor a completed purge; the\n // caller that owns the generation decides what to do with it.\n rethrowProjectionVerificationCancelled(error);\n await reportAndRethrowKeyingVerificationError(\n error,\n runtime.util.reportSecurityIncident,\n {\n objectId: documentId,\n objectKind: \"document\",\n operation: \"document.purge\",\n },\n );\n runtime.util.log(\n `Container contents: failed to purge note ${noteId} (document ${documentId}): ${errorMessage(error)}`,\n );\n return null;\n }\n}\n\ninterface ContainerDocumentLocalPurgeRuntime\n extends Pick<ContainerContentsWorkflowRuntime, \"infra\" | \"util\"> {}\n\nexport async function purgeLocalContainerDocument(input: {\n beforeDeleteInTransaction?: Parameters<\n typeof deletePersistedDocument\n >[0][\"beforeDeleteInTransaction\"];\n noteId: string;\n expectedContainerId?: string | null | undefined;\n persistence?: DocumentsPersistence | undefined;\n runtime: ContainerDocumentLocalPurgeRuntime;\n}): Promise<DocumentPurgeResult> {\n const { noteId, runtime } = input;\n const persistence = input.persistence ?? defaultDocumentsPersistence;\n try {\n await persistence.ensureSchema(runtime.infra.execSql);\n const expectedRecord = await persistence.loadDocument(\n runtime.infra.execSql,\n noteId,\n );\n if (\n !expectedRecord ||\n expectedRecord.documentId !== null ||\n (input.expectedContainerId !== undefined &&\n expectedRecord.containerId !== input.expectedContainerId)\n ) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because it is absent or remote-backed`,\n );\n return null;\n }\n const deleted = await deletePersistedDocument({\n beforeDeleteInTransaction: input.beforeDeleteInTransaction,\n documentProjectors: runtime.infra.documentProjectors,\n execSql: runtime.infra.execSql,\n expectedRecord,\n localId: noteId,\n persistence,\n });\n if (!deleted) {\n runtime.util.log(\n `Container contents: local-only purge refused for note ${noteId} because its identity changed`,\n );\n return null;\n }\n if (deleted) {\n void reclaimDocumentOrphanBlobs(runtime, persistence);\n }\n runtime.util.log(`Container contents: purged local-only note ${noteId}`);\n return {\n documentId: noteId,\n purgedAt: new Date().toISOString(),\n reclaimedBlobStorageKeys: [],\n };\n } catch (error) {\n runtime.util.log(\n `Container contents: failed to purge local-only note ${noteId}: ${errorMessage(error)}`,\n );\n return null;\n }\n}\n"]}
|
|
@@ -6,6 +6,7 @@ export declare function assertDocumentLinkTargetCapacity(currentTargetCount: num
|
|
|
6
6
|
export declare function assertDocumentLinkAuthorizationPathCapacity(currentPathLengths: readonly number[], targetPathLength: number): void;
|
|
7
7
|
export declare function buildMaterializedDocumentLinkSetMutationPlan(input: {
|
|
8
8
|
author: DocumentCreateAuthor;
|
|
9
|
+
expectedSubtreeRootId?: string | undefined;
|
|
9
10
|
prepareBlobRewraps: (targets: readonly DocumentContentKeyTarget[]) => Promise<BuildDocumentLinkSetMutationPlanInput["blobRewraps"]>;
|
|
10
11
|
contentKey?: Uint8Array | undefined;
|
|
11
12
|
eventId?: string | undefined;
|
|
@@ -9,6 +9,7 @@ import { assertDocumentManifestBundleConsistent, describeDocumentTargetKek, sort
|
|
|
9
9
|
import { projectionVerificationOptions, } from "../../data/documents/shared/types.js";
|
|
10
10
|
import { readCanonicalRecord } from "../../data/keyingCanonicalJson.js";
|
|
11
11
|
import { assertDocumentLinkAuthorAccess, verifyDocumentLinkSetCurrentPolicies, } from "./linkSetAuthority.js";
|
|
12
|
+
import { assertSubtreePurgePath } from "./subtreePurgeScope.js";
|
|
12
13
|
function deriveDocumentLinkSetTargetState(input) {
|
|
13
14
|
const currentTargets = currentDocumentTargets(input.writerProjection);
|
|
14
15
|
const currentLinkedContainerIds = readLinkedContainerIdsFromDocumentManifest(input.writerProjection);
|
|
@@ -189,6 +190,7 @@ export async function buildMaterializedDocumentLinkSetMutationPlan(input) {
|
|
|
189
190
|
writerProjection: input.writerProjection,
|
|
190
191
|
...verificationOptions,
|
|
191
192
|
});
|
|
193
|
+
assertSubtreePurgePath(input.targetContainerProjection.path, input.expectedSubtreeRootId);
|
|
192
194
|
if (input.resolveProjectionUserKey)
|
|
193
195
|
assertDocumentLinkAuthorAccess({
|
|
194
196
|
...input,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkSet.js","sourceRoot":"","sources":["linkSet.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n computeAccessManifestHash,\n computeDocumentContentKeyTargetHash,\n DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n type DocumentContentKeyTarget,\n type DocumentLinkSetManifestState,\n deriveDocumentLinkSetManifest,\n wrapContentKey,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport { isPlainObject as isPlainRecord } from \"@tearleads/validators/isPlainObject\";\nimport type { DocumentContentKeyTargetEnvelope } from \"@tearleads/validators/request\";\nimport type {\n ContainerWriterProjectionResponse,\n DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport {\n MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS,\n MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS,\n} from \"@tearleads/validators/util\";\nimport {\n assertContainerKekPathCurrent,\n assertDocumentKekPathsCurrent,\n} from \"../../data/documents/shared/containerKekCurrency\";\nimport { buildDocumentLinkSetEventPlan } from \"../../data/documents/shared/events\";\nimport {\n assertDocumentWriterProjectionConsistent,\n collectContainerKeksForDocumentSync,\n containerPathRefs,\n currentDocumentTargets,\n deriveDocumentTargetFromProjection,\n mergeTargetEnvelopes,\n readLinkedContainerIdsFromDocumentManifest,\n unwrapContainerKekPath,\n unwrapDocumentContentKeyFromWriterProjection,\n verifiedDocumentWrapTargets,\n} from \"../../data/documents/shared/projection\";\nimport {\n assertDocumentManifestBundleConsistent,\n describeDocumentTargetKek,\n sortDocumentTargets,\n targetKey,\n uniqueSortedStrings,\n} from \"../../data/documents/shared/readers\";\nimport {\n type BuildDocumentLinkSetMutationPlanInput,\n type DocumentCreateAuthor,\n type DocumentLinkSetMutationOperation,\n type DocumentLinkSetMutationPlan,\n type DocumentLinkSetTargetState,\n type MaterializedDocumentLinkSetMutationPlan,\n type ProjectionVerificationOptions,\n projectionVerificationOptions,\n} from \"../../data/documents/shared/types\";\nimport { readCanonicalRecord } from \"../../data/keyingCanonicalJson\";\nimport type { PrincipalPolicyCache } from \"../../data/keyingProjectionVerification\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport {\n assertDocumentLinkAuthorAccess,\n verifyDocumentLinkSetCurrentPolicies,\n} from \"./linkSetAuthority\";\n\nfunction deriveDocumentLinkSetTargetState(input: {\n operation: DocumentLinkSetMutationOperation;\n targetContainerProjection: ContainerWriterProjectionResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}): DocumentLinkSetTargetState {\n const currentTargets = currentDocumentTargets(input.writerProjection);\n const currentLinkedContainerIds = readLinkedContainerIdsFromDocumentManifest(\n input.writerProjection,\n );\n const target = deriveDocumentTargetFromProjection(\n input.targetContainerProjection,\n );\n const currentTarget = currentTargets.find(\n (candidate) => candidate.containerId === target.containerId,\n );\n\n if (input.operation === \"link\") {\n if (currentTarget) {\n throw new Error(\"Document link target is already linked\");\n }\n assertDocumentLinkTargetCapacity(currentTargets.length);\n assertDocumentLinkAuthorizationPathCapacity(\n input.writerProjection.authorizingContainerPaths.map(\n (projection) => projection.path.length,\n ),\n input.targetContainerProjection.path.length,\n );\n\n return {\n currentTargets,\n linkedContainerIds: uniqueSortedStrings([\n ...currentLinkedContainerIds,\n target.containerId,\n ]),\n target,\n targets: sortDocumentTargets([...currentTargets, target]),\n };\n }\n\n if (!currentTarget) {\n throw new Error(\"Document unlink target is not linked\");\n }\n if (targetKey(currentTarget) !== targetKey(target)) {\n throw new Error(\"Document unlink target projection is stale\");\n }\n\n const linkedContainerIds = currentLinkedContainerIds.filter(\n (containerId) => containerId !== target.containerId,\n );\n if (linkedContainerIds.length === 0) {\n throw new Error(\"Document unlink must leave a linked container\");\n }\n\n return {\n currentTargets,\n linkedContainerIds,\n target,\n targets: currentTargets.filter(\n (candidate) => candidate.containerId !== target.containerId,\n ),\n };\n}\n\nexport function assertDocumentLinkTargetCapacity(\n currentTargetCount: number,\n): void {\n if (currentTargetCount >= MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS) {\n throw new RangeError(\n `A document cannot be linked to more than ${MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS} containers`,\n );\n }\n}\n\nexport function assertDocumentLinkAuthorizationPathCapacity(\n currentPathLengths: readonly number[],\n targetPathLength: number,\n): void {\n const totalReferences = currentPathLengths.reduce(\n (total, length) => total + length,\n targetPathLength,\n );\n if (totalReferences > MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS) {\n throw new RangeError(\n `Document links cannot require more than ${MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS} authorization references`,\n );\n }\n}\n\nasync function wrapDocumentContentKeyForTargets(input: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n documentId: string;\n keksByEpochId: ReadonlyMap<string, Uint8Array>;\n targets: readonly DocumentContentKeyTarget[];\n}): Promise<DocumentContentKeyTargetEnvelope[]> {\n return Promise.all(\n sortDocumentTargets(input.targets).map(async (target) => {\n const targetKek = input.keksByEpochId.get(target.containerKeyEpochId);\n if (!targetKek) {\n throw new Error(\n `Document target KEK could not be unwrapped for ${describeDocumentTargetKek(target)}`,\n );\n }\n\n const wrapped = await wrapContentKey(input.contentKey, targetKek, {\n kind: \"Document\",\n objectId: input.documentId,\n contentKeyEpoch: input.contentKeyEpoch,\n containerId: target.containerId,\n containerKeyEpochId: target.containerKeyEpochId,\n });\n return {\n ...target,\n wrappedKey: bytesToBase64(wrapped.ciphertext),\n wrappingMetadata: {\n suite: DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n iv: bytesToBase64(wrapped.iv),\n },\n };\n }),\n );\n}\n\n// Resolve the organization a link/unlink mutation belongs to: the document's\n// own org (from its manifest), which must equal the target container's org —\n// both are owned by the same organization. The acting member's personal org is\n// intentionally not part of this check; a member may link documents within any\n// org they belong to. Returns the document org so the plan stamps it onto the\n// signed event and manifest rather than the author's org.\nasync function resolveDocumentLinkSetMutationOrganization(input: {\n targetContainerProjection: ContainerWriterProjectionResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}): Promise<string> {\n const manifestIdentity = await assertDocumentManifestBundleConsistent({\n bundle: input.writerProjection.documentManifest,\n label: \"Document link-set manifest\",\n });\n if (\n manifestIdentity.organizationId !==\n input.targetContainerProjection.organizationId\n ) {\n throw new Error(\"Document target container organization mismatch\");\n }\n\n return manifestIdentity.organizationId;\n}\n\nfunction readDocumentLinkSetPreviousEpoch(\n writerProjection: DocumentWriterProjectionResponse,\n): number {\n const previousState = writerProjection.documentManifest.state;\n const previousEpoch = isPlainRecord(previousState)\n ? Reflect.get(previousState, \"epoch\")\n : undefined;\n if (\n typeof previousEpoch !== \"number\" ||\n !Number.isInteger(previousEpoch) ||\n previousEpoch <= 0\n ) {\n throw new Error(\"Document link-set previous epoch is invalid\");\n }\n\n return previousEpoch;\n}\n\nasync function buildDocumentLinkSetMutationPlan({\n author,\n blobRewraps,\n contentKeyEpoch,\n eventId = crypto.randomUUID(),\n operation,\n signedAt = new Date().toISOString(),\n targetContainerProjection,\n targetEnvelopes,\n writerProjection,\n}: BuildDocumentLinkSetMutationPlanInput): Promise<DocumentLinkSetMutationPlan> {\n const organizationId = await resolveDocumentLinkSetMutationOrganization({\n targetContainerProjection,\n writerProjection,\n });\n const targetState = deriveDocumentLinkSetTargetState({\n operation,\n targetContainerProjection,\n writerProjection,\n });\n const targetEnvelopesForRequest = mergeTargetEnvelopes(\n targetState.targets,\n targetEnvelopes,\n );\n const targetContainerPathRefs = containerPathRefs(\n targetContainerProjection.path,\n );\n const eventPlan = await buildDocumentLinkSetEventPlan({\n author,\n blobRewraps,\n eventId,\n operation,\n organizationId,\n signedAt,\n targetContainerPathRefs,\n targetState,\n writerProjection,\n });\n const previousEpoch = readDocumentLinkSetPreviousEpoch(writerProjection);\n\n const state: DocumentLinkSetManifestState = {\n version: 1,\n documentId: writerProjection.documentId,\n organizationId,\n epoch: previousEpoch + 1,\n previousManifestHash: writerProjection.documentManifest.manifestHash,\n eventHash: eventPlan.eventHash,\n linkedContainerIds: [...targetState.linkedContainerIds],\n };\n const manifest = await deriveDocumentLinkSetManifest(state);\n const manifestHash = await computeAccessManifestHash(manifest);\n const targetHash = await computeDocumentContentKeyTargetHash(\n targetState.targets,\n );\n\n return {\n body: eventPlan.body,\n contentKeyEpoch,\n documentId: writerProjection.documentId,\n event: eventPlan.event,\n eventHash: eventPlan.eventHash,\n manifest,\n manifestHash,\n operation,\n request: {\n event: readCanonicalRecord(eventPlan.event, \"Document link-set event\"),\n body: readCanonicalRecord(eventPlan.body, \"Document link-set body\"),\n expectedManifestHash: manifestHash,\n manifest: readCanonicalRecord(manifest, \"Document link-set manifest\"),\n targetContainerPathRefs,\n authorizingContainerPathRefs: eventPlan.authorizingContainerPathRefs,\n contentKeyBundle: {\n contentKeyEpoch,\n linkSetManifestHash: manifestHash,\n targetHash,\n targets: targetEnvelopesForRequest,\n },\n },\n state,\n targetHash,\n targets: sortDocumentTargets(targetState.targets),\n };\n}\n\nexport async function buildMaterializedDocumentLinkSetMutationPlan(\n input: {\n author: DocumentCreateAuthor;\n prepareBlobRewraps: (\n targets: readonly DocumentContentKeyTarget[],\n ) => Promise<BuildDocumentLinkSetMutationPlanInput[\"blobRewraps\"]>;\n contentKey?: Uint8Array | undefined;\n eventId?: string | undefined;\n execSql?: ExecSql | undefined;\n operation: DocumentLinkSetMutationOperation;\n signedAt?: string | undefined;\n targetContainerProjection: ContainerWriterProjectionResponse;\n targetSecretKey: Uint8Array;\n writerProjection: DocumentWriterProjectionResponse;\n } & ProjectionVerificationOptions,\n): Promise<MaterializedDocumentLinkSetMutationPlan> {\n const verificationOptions = projectionVerificationOptions(input);\n const principalPolicyCache: PrincipalPolicyCache = new Map();\n await assertDocumentWriterProjectionConsistent(input.writerProjection, {\n execSql: input.execSql,\n principalPolicyCache,\n ...verificationOptions,\n });\n await verifyDocumentLinkSetCurrentPolicies({\n execSql: input.execSql,\n principalPolicyCache,\n targetContainerProjection: input.targetContainerProjection,\n writerProjection: input.writerProjection,\n ...verificationOptions,\n });\n if (input.resolveProjectionUserKey)\n assertDocumentLinkAuthorAccess({\n ...input,\n principalPolicies: [...principalPolicyCache.values()],\n });\n const targetState = deriveDocumentLinkSetTargetState({\n operation: input.operation,\n targetContainerProjection: input.targetContainerProjection,\n writerProjection: input.writerProjection,\n });\n assertLinkSetWriteKeksCurrent(input, targetState.linkedContainerIds);\n const currentContentKey = await unwrapDocumentContentKeyFromWriterProjection({\n execSql: input.execSql,\n secretKey: input.targetSecretKey,\n writerProjection: input.writerProjection,\n ...verificationOptions,\n });\n const contentKeyRotated = input.operation === \"unlink\";\n const contentKey = contentKeyRotated\n ? (input.contentKey ?? crypto.getRandomValues(new Uint8Array(32)))\n : currentContentKey;\n if (contentKey.byteLength !== 32) {\n throw new Error(\"Document content key must be 32 bytes\");\n }\n // A link carries the existing envelopes forward at the same epoch; an\n // unlink rotates to a fresh key at the next one.\n const contentKeyEpoch =\n input.writerProjection.contentKeyBundle.contentKeyEpoch +\n (contentKeyRotated ? 1 : 0);\n const targetEnvelopes = await buildLinkSetTargetEnvelopes(input, {\n contentKey,\n contentKeyEpoch,\n targetState,\n });\n\n const plan = await buildDocumentLinkSetMutationPlan({\n author: input.author,\n blobRewraps: await input.prepareBlobRewraps(targetState.targets),\n contentKeyEpoch,\n eventId: input.eventId,\n operation: input.operation,\n signedAt: input.signedAt,\n targetContainerProjection: input.targetContainerProjection,\n targetEnvelopes,\n writerProjection: input.writerProjection,\n });\n\n return {\n contentKey,\n contentKeyRotated,\n plan,\n };\n}\n\nasync function buildLinkSetTargetEnvelopes(\n input: Parameters<typeof buildMaterializedDocumentLinkSetMutationPlan>[0],\n wrap: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n targetState: ReturnType<typeof deriveDocumentLinkSetTargetState>;\n },\n): Promise<DocumentContentKeyTargetEnvelope[]> {\n const verificationOptions = projectionVerificationOptions(input);\n const sealing = {\n contentKey: wrap.contentKey,\n contentKeyEpoch: wrap.contentKeyEpoch,\n documentId: input.writerProjection.documentId,\n };\n if (input.operation === \"link\") {\n return [\n ...input.writerProjection.contentKeyBundle.targets,\n ...(await wrapDocumentContentKeyForTargets({\n ...sealing,\n keksByEpochId: await unwrapContainerKekPath({\n execSql: input.execSql,\n projection: input.targetContainerProjection,\n secretKey: input.targetSecretKey,\n ...verificationOptions,\n }),\n targets: [wrap.targetState.target],\n })),\n ];\n }\n return wrapDocumentContentKeyForTargets({\n ...sealing,\n keksByEpochId: (\n await collectContainerKeksForDocumentSync({\n execSql: input.execSql,\n secretKey: input.targetSecretKey,\n writerProjection: input.writerProjection,\n ...verificationOptions,\n })\n ).keksByEpochId,\n // The rotated key is wrapped only to verified current heads of the\n // remaining linked containers, never to a server-listed epoch.\n targets: verifiedDocumentWrapTargets({\n linkedContainerIds: wrap.targetState.linkedContainerIds,\n serverTargets: wrap.targetState.targets,\n writerProjection: input.writerProjection,\n }),\n });\n}\n\nfunction assertLinkSetWriteKeksCurrent(\n input: Pick<\n Parameters<typeof buildMaterializedDocumentLinkSetMutationPlan>[0],\n \"writerProjection\" | \"targetContainerProjection\" | \"operation\"\n >,\n linkedContainerIds: readonly string[],\n): void {\n // Unlink can escape a stale branch; only recipients of the successor key must be current.\n assertDocumentKekPathsCurrent(\n input.writerProjection,\n new Set(linkedContainerIds),\n );\n if (input.operation === \"link\") {\n assertContainerKekPathCurrent(\n input.targetContainerProjection.containerKeks,\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"linkSet.js","sourceRoot":"","sources":["linkSet.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n computeAccessManifestHash,\n computeDocumentContentKeyTargetHash,\n DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n type DocumentContentKeyTarget,\n type DocumentLinkSetManifestState,\n deriveDocumentLinkSetManifest,\n wrapContentKey,\n} from \"@tearleads/crypto\";\nimport { bytesToBase64 } from \"@tearleads/encoding\";\nimport { isPlainObject as isPlainRecord } from \"@tearleads/validators/isPlainObject\";\nimport type { DocumentContentKeyTargetEnvelope } from \"@tearleads/validators/request\";\nimport type {\n ContainerWriterProjectionResponse,\n DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport {\n MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS,\n MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS,\n} from \"@tearleads/validators/util\";\nimport {\n assertContainerKekPathCurrent,\n assertDocumentKekPathsCurrent,\n} from \"../../data/documents/shared/containerKekCurrency\";\nimport { buildDocumentLinkSetEventPlan } from \"../../data/documents/shared/events\";\nimport {\n assertDocumentWriterProjectionConsistent,\n collectContainerKeksForDocumentSync,\n containerPathRefs,\n currentDocumentTargets,\n deriveDocumentTargetFromProjection,\n mergeTargetEnvelopes,\n readLinkedContainerIdsFromDocumentManifest,\n unwrapContainerKekPath,\n unwrapDocumentContentKeyFromWriterProjection,\n verifiedDocumentWrapTargets,\n} from \"../../data/documents/shared/projection\";\nimport {\n assertDocumentManifestBundleConsistent,\n describeDocumentTargetKek,\n sortDocumentTargets,\n targetKey,\n uniqueSortedStrings,\n} from \"../../data/documents/shared/readers\";\nimport {\n type BuildDocumentLinkSetMutationPlanInput,\n type DocumentCreateAuthor,\n type DocumentLinkSetMutationOperation,\n type DocumentLinkSetMutationPlan,\n type DocumentLinkSetTargetState,\n type MaterializedDocumentLinkSetMutationPlan,\n type ProjectionVerificationOptions,\n projectionVerificationOptions,\n} from \"../../data/documents/shared/types\";\nimport { readCanonicalRecord } from \"../../data/keyingCanonicalJson\";\nimport type { PrincipalPolicyCache } from \"../../data/keyingProjectionVerification\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport {\n assertDocumentLinkAuthorAccess,\n verifyDocumentLinkSetCurrentPolicies,\n} from \"./linkSetAuthority\";\n\nimport { assertSubtreePurgePath } from \"./subtreePurgeScope\";\n\nfunction deriveDocumentLinkSetTargetState(input: {\n operation: DocumentLinkSetMutationOperation;\n targetContainerProjection: ContainerWriterProjectionResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}): DocumentLinkSetTargetState {\n const currentTargets = currentDocumentTargets(input.writerProjection);\n const currentLinkedContainerIds = readLinkedContainerIdsFromDocumentManifest(\n input.writerProjection,\n );\n const target = deriveDocumentTargetFromProjection(\n input.targetContainerProjection,\n );\n const currentTarget = currentTargets.find(\n (candidate) => candidate.containerId === target.containerId,\n );\n\n if (input.operation === \"link\") {\n if (currentTarget) {\n throw new Error(\"Document link target is already linked\");\n }\n assertDocumentLinkTargetCapacity(currentTargets.length);\n assertDocumentLinkAuthorizationPathCapacity(\n input.writerProjection.authorizingContainerPaths.map(\n (projection) => projection.path.length,\n ),\n input.targetContainerProjection.path.length,\n );\n\n return {\n currentTargets,\n linkedContainerIds: uniqueSortedStrings([\n ...currentLinkedContainerIds,\n target.containerId,\n ]),\n target,\n targets: sortDocumentTargets([...currentTargets, target]),\n };\n }\n\n if (!currentTarget) {\n throw new Error(\"Document unlink target is not linked\");\n }\n if (targetKey(currentTarget) !== targetKey(target)) {\n throw new Error(\"Document unlink target projection is stale\");\n }\n\n const linkedContainerIds = currentLinkedContainerIds.filter(\n (containerId) => containerId !== target.containerId,\n );\n if (linkedContainerIds.length === 0) {\n throw new Error(\"Document unlink must leave a linked container\");\n }\n\n return {\n currentTargets,\n linkedContainerIds,\n target,\n targets: currentTargets.filter(\n (candidate) => candidate.containerId !== target.containerId,\n ),\n };\n}\n\nexport function assertDocumentLinkTargetCapacity(\n currentTargetCount: number,\n): void {\n if (currentTargetCount >= MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS) {\n throw new RangeError(\n `A document cannot be linked to more than ${MAX_DOCUMENT_SYNC_CONTENT_KEY_TARGETS} containers`,\n );\n }\n}\n\nexport function assertDocumentLinkAuthorizationPathCapacity(\n currentPathLengths: readonly number[],\n targetPathLength: number,\n): void {\n const totalReferences = currentPathLengths.reduce(\n (total, length) => total + length,\n targetPathLength,\n );\n if (totalReferences > MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS) {\n throw new RangeError(\n `Document links cannot require more than ${MAX_DOCUMENT_SYNC_AUTHORIZATION_PATH_REFS} authorization references`,\n );\n }\n}\n\nasync function wrapDocumentContentKeyForTargets(input: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n documentId: string;\n keksByEpochId: ReadonlyMap<string, Uint8Array>;\n targets: readonly DocumentContentKeyTarget[];\n}): Promise<DocumentContentKeyTargetEnvelope[]> {\n return Promise.all(\n sortDocumentTargets(input.targets).map(async (target) => {\n const targetKek = input.keksByEpochId.get(target.containerKeyEpochId);\n if (!targetKek) {\n throw new Error(\n `Document target KEK could not be unwrapped for ${describeDocumentTargetKek(target)}`,\n );\n }\n\n const wrapped = await wrapContentKey(input.contentKey, targetKek, {\n kind: \"Document\",\n objectId: input.documentId,\n contentKeyEpoch: input.contentKeyEpoch,\n containerId: target.containerId,\n containerKeyEpochId: target.containerKeyEpochId,\n });\n return {\n ...target,\n wrappedKey: bytesToBase64(wrapped.ciphertext),\n wrappingMetadata: {\n suite: DOCUMENT_CONTENT_KEY_WRAP_SUITE,\n iv: bytesToBase64(wrapped.iv),\n },\n };\n }),\n );\n}\n\n// Resolve the organization a link/unlink mutation belongs to: the document's\n// own org (from its manifest), which must equal the target container's org —\n// both are owned by the same organization. The acting member's personal org is\n// intentionally not part of this check; a member may link documents within any\n// org they belong to. Returns the document org so the plan stamps it onto the\n// signed event and manifest rather than the author's org.\nasync function resolveDocumentLinkSetMutationOrganization(input: {\n targetContainerProjection: ContainerWriterProjectionResponse;\n writerProjection: DocumentWriterProjectionResponse;\n}): Promise<string> {\n const manifestIdentity = await assertDocumentManifestBundleConsistent({\n bundle: input.writerProjection.documentManifest,\n label: \"Document link-set manifest\",\n });\n if (\n manifestIdentity.organizationId !==\n input.targetContainerProjection.organizationId\n ) {\n throw new Error(\"Document target container organization mismatch\");\n }\n\n return manifestIdentity.organizationId;\n}\n\nfunction readDocumentLinkSetPreviousEpoch(\n writerProjection: DocumentWriterProjectionResponse,\n): number {\n const previousState = writerProjection.documentManifest.state;\n const previousEpoch = isPlainRecord(previousState)\n ? Reflect.get(previousState, \"epoch\")\n : undefined;\n if (\n typeof previousEpoch !== \"number\" ||\n !Number.isInteger(previousEpoch) ||\n previousEpoch <= 0\n ) {\n throw new Error(\"Document link-set previous epoch is invalid\");\n }\n\n return previousEpoch;\n}\n\nasync function buildDocumentLinkSetMutationPlan({\n author,\n blobRewraps,\n contentKeyEpoch,\n eventId = crypto.randomUUID(),\n operation,\n signedAt = new Date().toISOString(),\n targetContainerProjection,\n targetEnvelopes,\n writerProjection,\n}: BuildDocumentLinkSetMutationPlanInput): Promise<DocumentLinkSetMutationPlan> {\n const organizationId = await resolveDocumentLinkSetMutationOrganization({\n targetContainerProjection,\n writerProjection,\n });\n const targetState = deriveDocumentLinkSetTargetState({\n operation,\n targetContainerProjection,\n writerProjection,\n });\n const targetEnvelopesForRequest = mergeTargetEnvelopes(\n targetState.targets,\n targetEnvelopes,\n );\n const targetContainerPathRefs = containerPathRefs(\n targetContainerProjection.path,\n );\n const eventPlan = await buildDocumentLinkSetEventPlan({\n author,\n blobRewraps,\n eventId,\n operation,\n organizationId,\n signedAt,\n targetContainerPathRefs,\n targetState,\n writerProjection,\n });\n const previousEpoch = readDocumentLinkSetPreviousEpoch(writerProjection);\n\n const state: DocumentLinkSetManifestState = {\n version: 1,\n documentId: writerProjection.documentId,\n organizationId,\n epoch: previousEpoch + 1,\n previousManifestHash: writerProjection.documentManifest.manifestHash,\n eventHash: eventPlan.eventHash,\n linkedContainerIds: [...targetState.linkedContainerIds],\n };\n const manifest = await deriveDocumentLinkSetManifest(state);\n const manifestHash = await computeAccessManifestHash(manifest);\n const targetHash = await computeDocumentContentKeyTargetHash(\n targetState.targets,\n );\n\n return {\n body: eventPlan.body,\n contentKeyEpoch,\n documentId: writerProjection.documentId,\n event: eventPlan.event,\n eventHash: eventPlan.eventHash,\n manifest,\n manifestHash,\n operation,\n request: {\n event: readCanonicalRecord(eventPlan.event, \"Document link-set event\"),\n body: readCanonicalRecord(eventPlan.body, \"Document link-set body\"),\n expectedManifestHash: manifestHash,\n manifest: readCanonicalRecord(manifest, \"Document link-set manifest\"),\n targetContainerPathRefs,\n authorizingContainerPathRefs: eventPlan.authorizingContainerPathRefs,\n contentKeyBundle: {\n contentKeyEpoch,\n linkSetManifestHash: manifestHash,\n targetHash,\n targets: targetEnvelopesForRequest,\n },\n },\n state,\n targetHash,\n targets: sortDocumentTargets(targetState.targets),\n };\n}\n\nexport async function buildMaterializedDocumentLinkSetMutationPlan(\n input: {\n author: DocumentCreateAuthor;\n expectedSubtreeRootId?: string | undefined;\n prepareBlobRewraps: (\n targets: readonly DocumentContentKeyTarget[],\n ) => Promise<BuildDocumentLinkSetMutationPlanInput[\"blobRewraps\"]>;\n contentKey?: Uint8Array | undefined;\n eventId?: string | undefined;\n execSql?: ExecSql | undefined;\n operation: DocumentLinkSetMutationOperation;\n signedAt?: string | undefined;\n targetContainerProjection: ContainerWriterProjectionResponse;\n targetSecretKey: Uint8Array;\n writerProjection: DocumentWriterProjectionResponse;\n } & ProjectionVerificationOptions,\n): Promise<MaterializedDocumentLinkSetMutationPlan> {\n const verificationOptions = projectionVerificationOptions(input);\n const principalPolicyCache: PrincipalPolicyCache = new Map();\n await assertDocumentWriterProjectionConsistent(input.writerProjection, {\n execSql: input.execSql,\n principalPolicyCache,\n ...verificationOptions,\n });\n await verifyDocumentLinkSetCurrentPolicies({\n execSql: input.execSql,\n principalPolicyCache,\n targetContainerProjection: input.targetContainerProjection,\n writerProjection: input.writerProjection,\n ...verificationOptions,\n });\n assertSubtreePurgePath(\n input.targetContainerProjection.path,\n input.expectedSubtreeRootId,\n );\n if (input.resolveProjectionUserKey)\n assertDocumentLinkAuthorAccess({\n ...input,\n principalPolicies: [...principalPolicyCache.values()],\n });\n const targetState = deriveDocumentLinkSetTargetState({\n operation: input.operation,\n targetContainerProjection: input.targetContainerProjection,\n writerProjection: input.writerProjection,\n });\n assertLinkSetWriteKeksCurrent(input, targetState.linkedContainerIds);\n const currentContentKey = await unwrapDocumentContentKeyFromWriterProjection({\n execSql: input.execSql,\n secretKey: input.targetSecretKey,\n writerProjection: input.writerProjection,\n ...verificationOptions,\n });\n const contentKeyRotated = input.operation === \"unlink\";\n const contentKey = contentKeyRotated\n ? (input.contentKey ?? crypto.getRandomValues(new Uint8Array(32)))\n : currentContentKey;\n if (contentKey.byteLength !== 32) {\n throw new Error(\"Document content key must be 32 bytes\");\n }\n // A link carries the existing envelopes forward at the same epoch; an\n // unlink rotates to a fresh key at the next one.\n const contentKeyEpoch =\n input.writerProjection.contentKeyBundle.contentKeyEpoch +\n (contentKeyRotated ? 1 : 0);\n const targetEnvelopes = await buildLinkSetTargetEnvelopes(input, {\n contentKey,\n contentKeyEpoch,\n targetState,\n });\n\n const plan = await buildDocumentLinkSetMutationPlan({\n author: input.author,\n blobRewraps: await input.prepareBlobRewraps(targetState.targets),\n contentKeyEpoch,\n eventId: input.eventId,\n operation: input.operation,\n signedAt: input.signedAt,\n targetContainerProjection: input.targetContainerProjection,\n targetEnvelopes,\n writerProjection: input.writerProjection,\n });\n\n return {\n contentKey,\n contentKeyRotated,\n plan,\n };\n}\n\nasync function buildLinkSetTargetEnvelopes(\n input: Parameters<typeof buildMaterializedDocumentLinkSetMutationPlan>[0],\n wrap: {\n contentKey: Uint8Array;\n contentKeyEpoch: number;\n targetState: ReturnType<typeof deriveDocumentLinkSetTargetState>;\n },\n): Promise<DocumentContentKeyTargetEnvelope[]> {\n const verificationOptions = projectionVerificationOptions(input);\n const sealing = {\n contentKey: wrap.contentKey,\n contentKeyEpoch: wrap.contentKeyEpoch,\n documentId: input.writerProjection.documentId,\n };\n if (input.operation === \"link\") {\n return [\n ...input.writerProjection.contentKeyBundle.targets,\n ...(await wrapDocumentContentKeyForTargets({\n ...sealing,\n keksByEpochId: await unwrapContainerKekPath({\n execSql: input.execSql,\n projection: input.targetContainerProjection,\n secretKey: input.targetSecretKey,\n ...verificationOptions,\n }),\n targets: [wrap.targetState.target],\n })),\n ];\n }\n return wrapDocumentContentKeyForTargets({\n ...sealing,\n keksByEpochId: (\n await collectContainerKeksForDocumentSync({\n execSql: input.execSql,\n secretKey: input.targetSecretKey,\n writerProjection: input.writerProjection,\n ...verificationOptions,\n })\n ).keksByEpochId,\n // The rotated key is wrapped only to verified current heads of the\n // remaining linked containers, never to a server-listed epoch.\n targets: verifiedDocumentWrapTargets({\n linkedContainerIds: wrap.targetState.linkedContainerIds,\n serverTargets: wrap.targetState.targets,\n writerProjection: input.writerProjection,\n }),\n });\n}\n\nfunction assertLinkSetWriteKeksCurrent(\n input: Pick<\n Parameters<typeof buildMaterializedDocumentLinkSetMutationPlan>[0],\n \"writerProjection\" | \"targetContainerProjection\" | \"operation\"\n >,\n linkedContainerIds: readonly string[],\n): void {\n // Unlink can escape a stale branch; only recipients of the successor key must be current.\n assertDocumentKekPathsCurrent(\n input.writerProjection,\n new Set(linkedContainerIds),\n );\n if (input.operation === \"link\") {\n assertContainerKekPathCurrent(\n input.targetContainerProjection.containerKeks,\n );\n }\n}\n"]}
|
|
@@ -2,10 +2,12 @@ import type { DocumentCreateAuthor, DocumentLinkSetFailureHandler, DocumentLinkS
|
|
|
2
2
|
import { type ProjectionUserKeyResolver, type ReferencedPrincipalPolicyWarmer } from "../../data/keyingProjectionVerification.js";
|
|
3
3
|
import type { ExecSql } from "../../data/sqlite/sqlSchema.js";
|
|
4
4
|
export declare function relinkRemoteDocument(input: {
|
|
5
|
+
beforeSubmit?: (() => Promise<boolean>) | undefined;
|
|
5
6
|
apiClient: DocumentLinkSetMutationApi;
|
|
6
7
|
author: DocumentCreateAuthor;
|
|
7
8
|
contentKey?: Uint8Array | undefined;
|
|
8
9
|
documentId: string;
|
|
10
|
+
expectedSubtreeRootId?: string | undefined;
|
|
9
11
|
eventId?: string | undefined;
|
|
10
12
|
execSql: ExecSql;
|
|
11
13
|
onFailure?: DocumentLinkSetFailureHandler | undefined;
|
|
@@ -120,6 +120,8 @@ export async function relinkRemoteDocument(input) {
|
|
|
120
120
|
signedAt,
|
|
121
121
|
});
|
|
122
122
|
const completedPlan = { ...materializedPlan.plan, request };
|
|
123
|
+
if (input.beforeSubmit && !(await input.beforeSubmit()))
|
|
124
|
+
return null;
|
|
123
125
|
const response = await submitLinkSetMutation({
|
|
124
126
|
apiClient: input.apiClient,
|
|
125
127
|
documentId: completedPlan.documentId,
|
|
@@ -162,6 +164,7 @@ function orderedProjectionFailures(failures) {
|
|
|
162
164
|
function prepareRemoteLinkSetMutation(input, writerProjection, targetContainerProjection) {
|
|
163
165
|
return recordDocumentAuthorAccessFailure(() => nullOnProjectionVerificationCancellation(() => buildMaterializedDocumentLinkSetMutationPlan({
|
|
164
166
|
author: input.author,
|
|
167
|
+
expectedSubtreeRootId: input.expectedSubtreeRootId,
|
|
165
168
|
prepareBlobRewraps: (targets) => prepareDocumentLinkBlobRewraps({
|
|
166
169
|
...input,
|
|
167
170
|
targets,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkSetRemote.js","sourceRoot":"","sources":["linkSetRemote.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,2BAA2B,EAAE,MAAM,qDAAqD,CAAC;AAClG,OAAO,EAAE,iDAAiD,EAAE,MAAM,uCAAuC,CAAC;AAU1G,OAAO,EACL,wCAAwC,EAGxC,gCAAgC,GACjC,MAAM,yCAAyC,CAAC;AAEjD,OAAO,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,4CAA4C,EAAE,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,8BAA8B,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AACtE,OAAO,EAAE,8BAA8B,EAAE,MAAM,oBAAoB,CAAC;AAEpE,KAAK,UAAU,qBAAqB,CAAC,KAQpC;IACC,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAClD,MAAM,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAC5B,+DAA+D;IAC/D,MAAM,QAAQ,GACZ,KAAK,CAAC,SAAS,KAAK,MAAM;QACxB,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC;QAC9C,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,OAAO,EAAE;QAC7D,qCAAqC,EAAE,KAAK,CAAC,sBAAsB;KACpE,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;IACD,KAAK,CAAC,SAAS,EAAE,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC;AAOD,0EAA0E;AAC1E,2EAA2E;AAC3E,sEAAsE;AACtE,KAAK,UAAU,sBAAsB,CAAc,KAKlD;IACC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,qBAAqB,EAAE,CAAC;IACnD,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;QACd,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;IACpD,CAAC;IACD,MAAM,CAAC,MAAM,EAAE,CAAC;IAChB,OAAO;QACL,OAAO,EAAE;YACP,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,MAAM,EAAE,MAAM,CAAC,MAAM;SACtB;QACD,UAAU,EAAE,IAAI;KACjB,CAAC;AACJ,CAAC;AAED,SAAS,8BAA8B,CACrC,SAAqC,EACrC,UAAkB;IAElB,OAAO,sBAAsB,CAAC;QAC5B,qBAAqB,EAAE,SAAS,CAAC,iCAAiC,CAAC,IAAI,CACrE,SAAS,EACT,UAAU,EACV,EAAE,YAAY,EAAE,KAAK,EAAE,CACxB;KACF,CAAC,CAAC;AACL,CAAC;AAED,SAAS,+BAA+B,CACtC,SAAqC,EACrC,WAAmB;IAEnB,OAAO,sBAAsB,CAAC;QAC5B,qBAAqB,EAAE,SAAS,CAAC,kCAAkC,CAAC,IAAI,CACtE,SAAS,EACT,WAAW,EACX,EAAE,YAAY,EAAE,KAAK,EAAE,CACxB;KACF,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,6BAA6B,CAAC,KAe5C;IACC,MAAM,cAAc,GAAG,iDAAiD,CACtE,KAAK,CAAC,aAAa,EACnB,KAAK,CAAC,QAAQ,CACf,CAAC;IACF,MAAM,2BAA2B,CAAC;QAChC,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,EAAE,KAAK,CAAC,aAAa;QACzB,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAC;IACH,MAAM,2BAA2B,CAAC;QAChC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,eAAe,EAAE,KAAK,CAAC,gBAAgB;QACvC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,yBAAyB,EAAE,KAAK,CAAC,yBAAyB;QAC1D,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAC;IACH,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO;QACL,UAAU,EAAE,KAAK,CAAC,gBAAgB,CAAC,UAAU;QAC7C,iBAAiB,EAAE,KAAK,CAAC,gBAAgB,CAAC,iBAAiB;QAC3D,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,EAAE;QAC7B,kBAAkB,EAAE;YAClB,GAAG,KAAK,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,kBAAkB;SACxD;QACD,cAAc;QACd,IAAI,EAAE,KAAK,CAAC,aAAa;QACzB,QAAQ,EAAE,KAAK,CAAC,QAAQ;KACzB,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,KAgB1C;IACC,IAAI,KAAK,CAAC,SAAS,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE,CAAC;QAC5D,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;IACJ,CAAC;IACD,MAAM,wBAAwB,GAAG,gCAAgC,CAC/D,KAAK,CAAC,wBAAwB,EAC9B,mCAAmC,CACpC,CAAC;IACF,MAAM,CAAC,WAAW,EAAE,oBAAoB,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC5D,8BAA8B,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,CAAC;QACjE,+BAA+B,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,iBAAiB,CAAC;KAC1E,CAAC,CAAC;IACH,IAAI,CAAC,WAAW,CAAC,UAAU,IAAI,CAAC,oBAAoB,CAAC,UAAU,EAAE,CAAC;QAChE,KAAK,MAAM,OAAO,IAAI,yBAAyB,CAAC;YAC9C,WAAW,CAAC,OAAO;YACnB,oBAAoB,CAAC,OAAO;SAC7B,CAAC,EAAE,CAAC;YACH,KAAK,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC;QAC7B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,gBAAgB,GAAG,WAAW,CAAC,UAAU,CAAC;IAChD,MAAM,yBAAyB,GAAG,oBAAoB,CAAC,UAAU,CAAC;IAClE,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAElD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5D,MAAM,gBAAgB,GAAG,MAAM,4BAA4B,CACzD,EAAE,GAAG,KAAK,EAAE,wBAAwB,EAAE,QAAQ,EAAE,EAChD,gBAAgB,EAChB,yBAAyB,CAC1B,CAAC;IACF,IAAI,CAAC,gBAAgB;QAAE,OAAO,IAAI,CAAC;IACnC,MAAM,OAAO,GAAG,MAAM,8BAA8B,CAAC;QACnD,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,gBAAgB;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,QAAQ;KACT,CAAC,CAAC;IACH,MAAM,aAAa,GAAG,EAAE,GAAG,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC;IAC5D,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC;QAC3C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,UAAU,EAAE,aAAa,CAAC,UAAU;QACpC,sBAAsB,EAAE,aAAa,CAAC,KAAK,CAAC,cAAc;QAC1D,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,OAAO,EAAE,aAAa,CAAC,OAAO;QAC9B,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,6BAA6B,CAAC;QACnC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa;QACb,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,gBAAgB;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,QAAQ;QACR,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,yBAAyB;QACzB,gBAAgB;KACjB,CAAC,CAAC;AACL,CAAC;AAED,4EAA4E;AAC5E,8EAA8E;AAC9E,wEAAwE;AACxE,4EAA4E;AAC5E,sDAAsD;AACtD,SAAS,yBAAyB,CAChC,QAA4D;IAE5D,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAC7B,CAAC,OAAO,EAA6C,EAAE,CAAC,OAAO,KAAK,IAAI,CACzE,CAAC;IACF,OAAO;QACL,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,GAAG,CAAC;QACtD,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,KAAK,GAAG,CAAC;KACvD,CAAC;AACJ,CAAC;AAED,SAAS,4BAA4B,CACnC,KAGC,EACD,gBAAkD,EAClD,yBAA4D;IAE5D,OAAO,iCAAiC,CACtC,GAAG,EAAE,CACH,wCAAwC,CAAC,GAAG,EAAE,CAC5C,4CAA4C,CAAC;QAC3C,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,kBAAkB,EAAE,CAAC,OAAO,EAAE,EAAE,CAC9B,8BAA8B,CAAC;YAC7B,GAAG,KAAK;YACR,OAAO;YACP,yBAAyB;YACzB,gBAAgB;SACjB,CAAC;QACJ,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;QACxD,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,YAAY,EAAE,KAAK,CAAC,YAAY;QAChC,yBAAyB;QACzB,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,+BAA+B,EAC7B,KAAK,CAAC,+BAA+B;QACvC,gBAAgB;KACjB,CAAC,CACH,EACH,KAAK,CAAC,SAAS,CAChB,CAAC;AACJ,CAAC","sourcesContent":["import type { DocumentLinkSetMutationRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerWriterProjectionResponse,\n DocumentLinkSetMutationResponse,\n DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { acknowledgeDocumentMutation } from \"../../data/documents/shared/mutationAcknowledgement\";\nimport { persistedDocumentLinkSetMutationStateFromResponse } from \"../../data/documents/shared/responses\";\nimport type {\n DocumentCreateAuthor,\n DocumentLinkSetFailureHandler,\n DocumentLinkSetMutationApi,\n DocumentLinkSetMutationFailure,\n DocumentLinkSetMutationOperation,\n DocumentSyncSubmitFailure,\n RelinkRemoteDocumentResult,\n} from \"../../data/documents/shared/types\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../data/keyingProjectionVerification\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { recordDocumentAuthorAccessFailure } from \"./authorAccessFailure\";\nimport { buildMaterializedDocumentLinkSetMutationPlan } from \"./linkSet\";\nimport { prepareDocumentLinkBlobRewraps } from \"./linkSetBlobRewraps\";\nimport { seedLinkSetWriterProjection } from \"./linkSetProjectionSeed\";\nimport { completeLinkSetMutationRequest } from \"./rotationBaseline\";\n\nasync function submitLinkSetMutation(input: {\n apiClient: DocumentLinkSetMutationApi;\n documentId: string;\n expectedOrganizationId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n operation: DocumentLinkSetMutationOperation;\n request: DocumentLinkSetMutationRequest;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<DocumentLinkSetMutationResponse | null> {\n if (input.stillCurrent?.() === false) return null;\n const { apiClient } = input;\n // Preserve the HTTP failure for the durable move-intent queue.\n const resultFn =\n input.operation === \"link\"\n ? apiClient.linkDocumentResult.bind(apiClient)\n : apiClient.unlinkDocumentResult.bind(apiClient);\n const result = await resultFn(input.documentId, input.request, {\n expectedPaymentRequiredOrganizationId: input.expectedOrganizationId,\n });\n if (result.ok) {\n return result.data;\n }\n input.onFailure?.({\n code: result.code,\n message: result.message,\n status: result.status,\n });\n return null;\n}\n\ninterface LinkSetProjectionFetch<TProjection> {\n failure: DocumentLinkSetMutationFailure | null;\n projection: TProjection | null;\n}\n\n// Cold-cache projection fetches must keep their HTTP status: collapsing a\n// 403 to null would leave an access-denied link/unlink routinely retriable\n// instead of parking its move for the access-restored signal (row 7).\nasync function fetchLinkSetProjection<TProjection>(input: {\n fetchProjectionResult: () => Promise<\n | { readonly data: TProjection; readonly ok: true }\n | DocumentSyncSubmitFailure\n >;\n}): Promise<LinkSetProjectionFetch<TProjection>> {\n const result = await input.fetchProjectionResult();\n if (result.ok) {\n return { failure: null, projection: result.data };\n }\n result.report();\n return {\n failure: {\n code: result.code,\n message: result.message,\n status: result.status,\n },\n projection: null,\n };\n}\n\nfunction fetchLinkSetDocumentProjection(\n apiClient: DocumentLinkSetMutationApi,\n documentId: string,\n): Promise<LinkSetProjectionFetch<DocumentWriterProjectionResponse>> {\n return fetchLinkSetProjection({\n fetchProjectionResult: apiClient.getDocumentWriterProjectionResult.bind(\n apiClient,\n documentId,\n { reportErrors: false },\n ),\n });\n}\n\nfunction fetchLinkSetContainerProjection(\n apiClient: DocumentLinkSetMutationApi,\n containerId: string,\n): Promise<LinkSetProjectionFetch<ContainerWriterProjectionResponse>> {\n return fetchLinkSetProjection({\n fetchProjectionResult: apiClient.getContainerWriterProjectionResult.bind(\n apiClient,\n containerId,\n { reportErrors: false },\n ),\n });\n}\n\nasync function completeRemoteDocumentLinkSet(input: {\n completedPlan: Parameters<\n typeof persistedDocumentLinkSetMutationStateFromResponse\n >[0];\n execSql: ExecSql;\n materializedPlan: Awaited<\n ReturnType<typeof buildMaterializedDocumentLinkSetMutationPlan>\n >;\n operation: DocumentLinkSetMutationOperation;\n response: DocumentLinkSetMutationResponse;\n stillCurrent?: (() => boolean) | undefined;\n targetContainerId: string;\n targetContainerProjection: ContainerWriterProjectionResponse;\n writerProjection: DocumentWriterProjectionResponse;\n apiClient: DocumentLinkSetMutationApi;\n}): Promise<RelinkRemoteDocumentResult | null> {\n const persistedState = persistedDocumentLinkSetMutationStateFromResponse(\n input.completedPlan,\n input.response,\n );\n await acknowledgeDocumentMutation({\n execSql: input.execSql,\n plan: input.completedPlan,\n stillCurrent: input.stillCurrent,\n });\n await seedLinkSetWriterProjection({\n apiClient: input.apiClient,\n execSql: input.execSql,\n operation: input.operation,\n priorProjection: input.writerProjection,\n response: input.response,\n targetContainerId: input.targetContainerId,\n targetContainerProjection: input.targetContainerProjection,\n stillCurrent: input.stillCurrent,\n });\n if (input.stillCurrent?.() === false) return null;\n return {\n contentKey: input.materializedPlan.contentKey,\n contentKeyRotated: input.materializedPlan.contentKeyRotated,\n documentId: input.response.id,\n linkedContainerIds: [\n ...input.materializedPlan.plan.state.linkedContainerIds,\n ],\n persistedState,\n plan: input.completedPlan,\n response: input.response,\n };\n}\n\nexport async function relinkRemoteDocument(input: {\n apiClient: DocumentLinkSetMutationApi;\n author: DocumentCreateAuthor;\n contentKey?: Uint8Array | undefined;\n documentId: string;\n eventId?: string | undefined;\n execSql: ExecSql;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n operation: DocumentLinkSetMutationOperation;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot?: Uint8Array | undefined;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetContainerId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<RelinkRemoteDocumentResult | null> {\n if (input.operation === \"unlink\" && !input.rotationSnapshot) {\n throw new Error(\n \"Document unlink requires a proven full-history rotation snapshot\",\n );\n }\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote document link-set mutation\",\n );\n const [writerFetch, targetContainerFetch] = await Promise.all([\n fetchLinkSetDocumentProjection(input.apiClient, input.documentId),\n fetchLinkSetContainerProjection(input.apiClient, input.targetContainerId),\n ]);\n if (!writerFetch.projection || !targetContainerFetch.projection) {\n for (const failure of orderedProjectionFailures([\n writerFetch.failure,\n targetContainerFetch.failure,\n ])) {\n input.onFailure?.(failure);\n }\n return null;\n }\n const writerProjection = writerFetch.projection;\n const targetContainerProjection = targetContainerFetch.projection;\n if (input.stillCurrent?.() === false) return null;\n\n const signedAt = input.signedAt ?? new Date().toISOString();\n const materializedPlan = await prepareRemoteLinkSetMutation(\n { ...input, resolveProjectionUserKey, signedAt },\n writerProjection,\n targetContainerProjection,\n );\n if (!materializedPlan) return null;\n const request = await completeLinkSetMutationRequest({\n author: input.author,\n materializedPlan,\n operation: input.operation,\n rotationSnapshot: input.rotationSnapshot,\n signedAt,\n });\n const completedPlan = { ...materializedPlan.plan, request };\n const response = await submitLinkSetMutation({\n apiClient: input.apiClient,\n documentId: completedPlan.documentId,\n expectedOrganizationId: completedPlan.state.organizationId,\n onFailure: input.onFailure,\n operation: input.operation,\n request: completedPlan.request,\n stillCurrent: input.stillCurrent,\n });\n if (!response) {\n return null;\n }\n if (input.stillCurrent?.() === false) return null;\n return completeRemoteDocumentLinkSet({\n apiClient: input.apiClient,\n completedPlan,\n execSql: input.execSql,\n materializedPlan,\n operation: input.operation,\n response,\n stillCurrent: input.stillCurrent,\n targetContainerId: input.targetContainerId,\n targetContainerProjection,\n writerProjection,\n });\n}\n\n// Every projection failure reaches the handler: the queued-move accumulator\n// classifies a vanished container from ANY coded container 404, so a document\n// fetch failure must not hide a container one (or vice versa). A 403 is\n// delivered last, so a single-slot consumer keeps the denial that parks the\n// move (row 7) while the accumulator still sees both.\nfunction orderedProjectionFailures(\n failures: readonly (DocumentLinkSetMutationFailure | null)[],\n): DocumentLinkSetMutationFailure[] {\n const present = failures.filter(\n (failure): failure is DocumentLinkSetMutationFailure => failure !== null,\n );\n return [\n ...present.filter((failure) => failure.status !== 403),\n ...present.filter((failure) => failure.status === 403),\n ];\n}\n\nfunction prepareRemoteLinkSetMutation(\n input: Parameters<typeof relinkRemoteDocument>[0] & {\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt: string;\n },\n writerProjection: DocumentWriterProjectionResponse,\n targetContainerProjection: ContainerWriterProjectionResponse,\n) {\n return recordDocumentAuthorAccessFailure(\n () =>\n nullOnProjectionVerificationCancellation(() =>\n buildMaterializedDocumentLinkSetMutationPlan({\n author: input.author,\n prepareBlobRewraps: (targets) =>\n prepareDocumentLinkBlobRewraps({\n ...input,\n targets,\n targetContainerProjection,\n writerProjection,\n }),\n contentKey: input.contentKey,\n eventId: input.eventId,\n execSql: input.execSql,\n operation: input.operation,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetContainerProjection,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies:\n input.warmReferencedPrincipalPolicies,\n writerProjection,\n }),\n ),\n input.onFailure,\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"linkSetRemote.js","sourceRoot":"","sources":["linkSetRemote.ts"],"names":[],"mappings":"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","sourcesContent":["import type { DocumentLinkSetMutationRequest } from \"@tearleads/validators/request\";\nimport type {\n ContainerWriterProjectionResponse,\n DocumentLinkSetMutationResponse,\n DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { acknowledgeDocumentMutation } from \"../../data/documents/shared/mutationAcknowledgement\";\nimport { persistedDocumentLinkSetMutationStateFromResponse } from \"../../data/documents/shared/responses\";\nimport type {\n DocumentCreateAuthor,\n DocumentLinkSetFailureHandler,\n DocumentLinkSetMutationApi,\n DocumentLinkSetMutationFailure,\n DocumentLinkSetMutationOperation,\n DocumentSyncSubmitFailure,\n RelinkRemoteDocumentResult,\n} from \"../../data/documents/shared/types\";\nimport {\n nullOnProjectionVerificationCancellation,\n type ProjectionUserKeyResolver,\n type ReferencedPrincipalPolicyWarmer,\n requireProjectionUserKeyResolver,\n} from \"../../data/keyingProjectionVerification\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { recordDocumentAuthorAccessFailure } from \"./authorAccessFailure\";\nimport { buildMaterializedDocumentLinkSetMutationPlan } from \"./linkSet\";\nimport { prepareDocumentLinkBlobRewraps } from \"./linkSetBlobRewraps\";\nimport { seedLinkSetWriterProjection } from \"./linkSetProjectionSeed\";\nimport { completeLinkSetMutationRequest } from \"./rotationBaseline\";\n\nasync function submitLinkSetMutation(input: {\n apiClient: DocumentLinkSetMutationApi;\n documentId: string;\n expectedOrganizationId: string;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n operation: DocumentLinkSetMutationOperation;\n request: DocumentLinkSetMutationRequest;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<DocumentLinkSetMutationResponse | null> {\n if (input.stillCurrent?.() === false) return null;\n const { apiClient } = input;\n // Preserve the HTTP failure for the durable move-intent queue.\n const resultFn =\n input.operation === \"link\"\n ? apiClient.linkDocumentResult.bind(apiClient)\n : apiClient.unlinkDocumentResult.bind(apiClient);\n const result = await resultFn(input.documentId, input.request, {\n expectedPaymentRequiredOrganizationId: input.expectedOrganizationId,\n });\n if (result.ok) {\n return result.data;\n }\n input.onFailure?.({\n code: result.code,\n message: result.message,\n status: result.status,\n });\n return null;\n}\n\ninterface LinkSetProjectionFetch<TProjection> {\n failure: DocumentLinkSetMutationFailure | null;\n projection: TProjection | null;\n}\n\n// Cold-cache projection fetches must keep their HTTP status: collapsing a\n// 403 to null would leave an access-denied link/unlink routinely retriable\n// instead of parking its move for the access-restored signal (row 7).\nasync function fetchLinkSetProjection<TProjection>(input: {\n fetchProjectionResult: () => Promise<\n | { readonly data: TProjection; readonly ok: true }\n | DocumentSyncSubmitFailure\n >;\n}): Promise<LinkSetProjectionFetch<TProjection>> {\n const result = await input.fetchProjectionResult();\n if (result.ok) {\n return { failure: null, projection: result.data };\n }\n result.report();\n return {\n failure: {\n code: result.code,\n message: result.message,\n status: result.status,\n },\n projection: null,\n };\n}\n\nfunction fetchLinkSetDocumentProjection(\n apiClient: DocumentLinkSetMutationApi,\n documentId: string,\n): Promise<LinkSetProjectionFetch<DocumentWriterProjectionResponse>> {\n return fetchLinkSetProjection({\n fetchProjectionResult: apiClient.getDocumentWriterProjectionResult.bind(\n apiClient,\n documentId,\n { reportErrors: false },\n ),\n });\n}\n\nfunction fetchLinkSetContainerProjection(\n apiClient: DocumentLinkSetMutationApi,\n containerId: string,\n): Promise<LinkSetProjectionFetch<ContainerWriterProjectionResponse>> {\n return fetchLinkSetProjection({\n fetchProjectionResult: apiClient.getContainerWriterProjectionResult.bind(\n apiClient,\n containerId,\n { reportErrors: false },\n ),\n });\n}\n\nasync function completeRemoteDocumentLinkSet(input: {\n completedPlan: Parameters<\n typeof persistedDocumentLinkSetMutationStateFromResponse\n >[0];\n execSql: ExecSql;\n materializedPlan: Awaited<\n ReturnType<typeof buildMaterializedDocumentLinkSetMutationPlan>\n >;\n operation: DocumentLinkSetMutationOperation;\n response: DocumentLinkSetMutationResponse;\n stillCurrent?: (() => boolean) | undefined;\n targetContainerId: string;\n targetContainerProjection: ContainerWriterProjectionResponse;\n writerProjection: DocumentWriterProjectionResponse;\n apiClient: DocumentLinkSetMutationApi;\n}): Promise<RelinkRemoteDocumentResult | null> {\n const persistedState = persistedDocumentLinkSetMutationStateFromResponse(\n input.completedPlan,\n input.response,\n );\n await acknowledgeDocumentMutation({\n execSql: input.execSql,\n plan: input.completedPlan,\n stillCurrent: input.stillCurrent,\n });\n await seedLinkSetWriterProjection({\n apiClient: input.apiClient,\n execSql: input.execSql,\n operation: input.operation,\n priorProjection: input.writerProjection,\n response: input.response,\n targetContainerId: input.targetContainerId,\n targetContainerProjection: input.targetContainerProjection,\n stillCurrent: input.stillCurrent,\n });\n if (input.stillCurrent?.() === false) return null;\n return {\n contentKey: input.materializedPlan.contentKey,\n contentKeyRotated: input.materializedPlan.contentKeyRotated,\n documentId: input.response.id,\n linkedContainerIds: [\n ...input.materializedPlan.plan.state.linkedContainerIds,\n ],\n persistedState,\n plan: input.completedPlan,\n response: input.response,\n };\n}\n\nexport async function relinkRemoteDocument(input: {\n beforeSubmit?: (() => Promise<boolean>) | undefined;\n apiClient: DocumentLinkSetMutationApi;\n author: DocumentCreateAuthor;\n contentKey?: Uint8Array | undefined;\n documentId: string;\n expectedSubtreeRootId?: string | undefined;\n eventId?: string | undefined;\n execSql: ExecSql;\n onFailure?: DocumentLinkSetFailureHandler | undefined;\n operation: DocumentLinkSetMutationOperation;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rotationSnapshot?: Uint8Array | undefined;\n signedAt?: string | undefined;\n stillCurrent?: (() => boolean) | undefined;\n targetContainerId: string;\n targetSecretKey: Uint8Array;\n warmReferencedPrincipalPolicies?: ReferencedPrincipalPolicyWarmer | undefined;\n}): Promise<RelinkRemoteDocumentResult | null> {\n if (input.operation === \"unlink\" && !input.rotationSnapshot) {\n throw new Error(\n \"Document unlink requires a proven full-history rotation snapshot\",\n );\n }\n const resolveProjectionUserKey = requireProjectionUserKeyResolver(\n input.resolveProjectionUserKey,\n \"Remote document link-set mutation\",\n );\n const [writerFetch, targetContainerFetch] = await Promise.all([\n fetchLinkSetDocumentProjection(input.apiClient, input.documentId),\n fetchLinkSetContainerProjection(input.apiClient, input.targetContainerId),\n ]);\n if (!writerFetch.projection || !targetContainerFetch.projection) {\n for (const failure of orderedProjectionFailures([\n writerFetch.failure,\n targetContainerFetch.failure,\n ])) {\n input.onFailure?.(failure);\n }\n return null;\n }\n const writerProjection = writerFetch.projection;\n const targetContainerProjection = targetContainerFetch.projection;\n if (input.stillCurrent?.() === false) return null;\n\n const signedAt = input.signedAt ?? new Date().toISOString();\n const materializedPlan = await prepareRemoteLinkSetMutation(\n { ...input, resolveProjectionUserKey, signedAt },\n writerProjection,\n targetContainerProjection,\n );\n if (!materializedPlan) return null;\n const request = await completeLinkSetMutationRequest({\n author: input.author,\n materializedPlan,\n operation: input.operation,\n rotationSnapshot: input.rotationSnapshot,\n signedAt,\n });\n const completedPlan = { ...materializedPlan.plan, request };\n if (input.beforeSubmit && !(await input.beforeSubmit())) return null;\n const response = await submitLinkSetMutation({\n apiClient: input.apiClient,\n documentId: completedPlan.documentId,\n expectedOrganizationId: completedPlan.state.organizationId,\n onFailure: input.onFailure,\n operation: input.operation,\n request: completedPlan.request,\n stillCurrent: input.stillCurrent,\n });\n if (!response) {\n return null;\n }\n if (input.stillCurrent?.() === false) return null;\n return completeRemoteDocumentLinkSet({\n apiClient: input.apiClient,\n completedPlan,\n execSql: input.execSql,\n materializedPlan,\n operation: input.operation,\n response,\n stillCurrent: input.stillCurrent,\n targetContainerId: input.targetContainerId,\n targetContainerProjection,\n writerProjection,\n });\n}\n\n// Every projection failure reaches the handler: the queued-move accumulator\n// classifies a vanished container from ANY coded container 404, so a document\n// fetch failure must not hide a container one (or vice versa). A 403 is\n// delivered last, so a single-slot consumer keeps the denial that parks the\n// move (row 7) while the accumulator still sees both.\nfunction orderedProjectionFailures(\n failures: readonly (DocumentLinkSetMutationFailure | null)[],\n): DocumentLinkSetMutationFailure[] {\n const present = failures.filter(\n (failure): failure is DocumentLinkSetMutationFailure => failure !== null,\n );\n return [\n ...present.filter((failure) => failure.status !== 403),\n ...present.filter((failure) => failure.status === 403),\n ];\n}\n\nfunction prepareRemoteLinkSetMutation(\n input: Parameters<typeof relinkRemoteDocument>[0] & {\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n signedAt: string;\n },\n writerProjection: DocumentWriterProjectionResponse,\n targetContainerProjection: ContainerWriterProjectionResponse,\n) {\n return recordDocumentAuthorAccessFailure(\n () =>\n nullOnProjectionVerificationCancellation(() =>\n buildMaterializedDocumentLinkSetMutationPlan({\n author: input.author,\n expectedSubtreeRootId: input.expectedSubtreeRootId,\n prepareBlobRewraps: (targets) =>\n prepareDocumentLinkBlobRewraps({\n ...input,\n targets,\n targetContainerProjection,\n writerProjection,\n }),\n contentKey: input.contentKey,\n eventId: input.eventId,\n execSql: input.execSql,\n operation: input.operation,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n signedAt: input.signedAt,\n stillCurrent: input.stillCurrent,\n targetContainerProjection,\n targetSecretKey: input.targetSecretKey,\n warmReferencedPrincipalPolicies:\n input.warmReferencedPrincipalPolicies,\n writerProjection,\n }),\n ),\n input.onFailure,\n );\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const states = new WeakMap();
|
|
2
|
+
export function orphanBlobReclaimState(persistence, execSql) {
|
|
3
|
+
let connections = states.get(persistence);
|
|
4
|
+
if (!connections) {
|
|
5
|
+
connections = new WeakMap();
|
|
6
|
+
states.set(persistence, connections);
|
|
7
|
+
}
|
|
8
|
+
let state = connections.get(execSql);
|
|
9
|
+
if (!state) {
|
|
10
|
+
state = {
|
|
11
|
+
deferredStorageKeys: new Map(),
|
|
12
|
+
rerun: false,
|
|
13
|
+
running: undefined,
|
|
14
|
+
sweepKey: `sweep:document-orphans:${crypto.randomUUID()}`,
|
|
15
|
+
};
|
|
16
|
+
connections.set(execSql, state);
|
|
17
|
+
}
|
|
18
|
+
return state;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=orphanBlobReclaimState.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphanBlobReclaimState.js","sourceRoot":"","sources":["orphanBlobReclaimState.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,GAAG,IAAI,OAAO,EAGvB,CAAC;AAEJ,MAAM,UAAU,sBAAsB,CACpC,WAA0C,EAC1C,OAAgB;IAEhB,IAAI,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,IAAI,OAAO,EAAE,CAAC;QAC5B,MAAM,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;IACvC,CAAC;IACD,IAAI,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG;YACN,mBAAmB,EAAE,IAAI,GAAG,EAAE;YAC9B,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,SAAS;YAClB,QAAQ,EAAE,0BAA0B,MAAM,CAAC,UAAU,EAAE,EAAE;SAC1D,CAAC;QACF,WAAW,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["import type { DocumentOrphanBlobPersistence } from \"../../data/persistence/documents/orphanBlobPersistence\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\nexport interface OrphanBlobReclaimState {\n deferredStorageKeys: Map<string, number>;\n rerun: boolean;\n running: Promise<void> | undefined;\n sweepKey: string;\n}\n\nconst states = new WeakMap<\n DocumentOrphanBlobPersistence,\n WeakMap<ExecSql, OrphanBlobReclaimState>\n>();\n\nexport function orphanBlobReclaimState(\n persistence: DocumentOrphanBlobPersistence,\n execSql: ExecSql,\n): OrphanBlobReclaimState {\n let connections = states.get(persistence);\n if (!connections) {\n connections = new WeakMap();\n states.set(persistence, connections);\n }\n let state = connections.get(execSql);\n if (!state) {\n state = {\n deferredStorageKeys: new Map(),\n rerun: false,\n running: undefined,\n sweepKey: `sweep:document-orphans:${crypto.randomUUID()}`,\n };\n connections.set(execSql, state);\n }\n return state;\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import type { DocumentsPersistence } from "../../data/persistence/documents/types.js";
|
|
1
2
|
import type { DocumentsWorkflowRuntimeGroups } from "./runtime.js";
|
|
2
3
|
type DocumentOrphanBlobReclaimRuntime = Pick<DocumentsWorkflowRuntimeGroups, "infra" | "util">;
|
|
3
|
-
/**
|
|
4
|
-
export declare function reclaimDocumentOrphanBlobs(runtime: DocumentOrphanBlobReclaimRuntime): Promise<void>;
|
|
4
|
+
/** Await all requested batches; maintenance failures never fail committed writes. */
|
|
5
|
+
export declare function reclaimDocumentOrphanBlobs(runtime: DocumentOrphanBlobReclaimRuntime, persistence?: DocumentsPersistence): Promise<void>;
|
|
5
6
|
export {};
|