@tearleads/client-sdk 0.1.24 → 0.1.26

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.
Files changed (97) hide show
  1. package/README.md +6 -0
  2. package/client/Tearleads.js +9 -1
  3. package/client/Tearleads.js.map +1 -1
  4. package/data/persistence/container-contents/containerContentsPersistenceTypes.d.ts +2 -0
  5. package/data/persistence/container-contents/containerContentsPersistenceTypes.js.map +1 -1
  6. package/data/persistence/container-contents/containerDeletionPersistence.js +3 -0
  7. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  8. package/data/persistence/container-contents/documentPurgePlacement.js +15 -0
  9. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -0
  10. package/data/persistence/documents/internal/attachmentStagingPersistence.js +3 -1
  11. package/data/persistence/documents/internal/attachmentStagingPersistence.js.map +1 -1
  12. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -0
  13. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  14. package/data/persistence/documents/internal/syncQueuePersistence.js +8 -5
  15. package/data/persistence/documents/internal/syncQueuePersistence.js.map +1 -1
  16. package/data/persistence/documents/types.d.ts +2 -1
  17. package/data/persistence/documents/types.js.map +1 -1
  18. package/data/persistence/principalCurrentAcknowledgementPersistence.d.ts +13 -2
  19. package/data/persistence/principalCurrentAcknowledgementPersistence.js +36 -20
  20. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  21. package/index.d.ts +2 -2
  22. package/index.js +1 -1
  23. package/index.js.map +1 -1
  24. package/internal/api-client/ApiClient.d.ts +6 -2
  25. package/internal/api-client/ApiClient.js +4 -5
  26. package/internal/api-client/ApiClient.js.map +1 -1
  27. package/internal/api-client/apiRequestRuntime.d.ts +1 -2
  28. package/internal/api-client/apiRequestRuntime.js +12 -15
  29. package/internal/api-client/apiRequestRuntime.js.map +1 -1
  30. package/internal/api-client/routes/containers/mutations.js +2 -2
  31. package/internal/api-client/routes/containers/mutations.js.map +1 -1
  32. package/internal/api-client/sessionRefresh.js +14 -6
  33. package/internal/api-client/sessionRefresh.js.map +1 -1
  34. package/internal/api-client/sessionRenewal.js +54 -0
  35. package/internal/api-client/sessionRenewal.js.map +1 -0
  36. package/internal/validators/operation/containerMutations.d.ts +3 -0
  37. package/internal/validators/operation/containerMutations.js +1 -0
  38. package/internal/validators/operation/containerMutations.js.map +1 -1
  39. package/package.json +1 -1
  40. package/stores/documents/documentStore/attachmentStaging.js +24 -13
  41. package/stores/documents/documentStore/attachmentStaging.js.map +1 -1
  42. package/stores/documents/documentStore/attachments.js +6 -4
  43. package/stores/documents/documentStore/attachments.js.map +1 -1
  44. package/workflows/container-contents/container-state/delete.js +2 -0
  45. package/workflows/container-contents/container-state/delete.js.map +1 -1
  46. package/workflows/container-contents/container-state/localPurgeScope.js +49 -0
  47. package/workflows/container-contents/container-state/localPurgeScope.js.map +1 -0
  48. package/workflows/container-contents/container-state/purgeTree.js +54 -7
  49. package/workflows/container-contents/container-state/purgeTree.js.map +1 -1
  50. package/workflows/container-contents/container-state/purgeTreeCollection.js +11 -0
  51. package/workflows/container-contents/container-state/purgeTreeCollection.js.map +1 -1
  52. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js +9 -1
  53. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js.map +1 -1
  54. package/workflows/container-contents/container-state/purgeTreeScope.js +175 -0
  55. package/workflows/container-contents/container-state/purgeTreeScope.js.map +1 -0
  56. package/workflows/container-contents/container-state/readLocalPurgeScope.js +18 -0
  57. package/workflows/container-contents/container-state/readLocalPurgeScope.js.map +1 -0
  58. package/workflows/container-contents/container-state/remote.js +1 -0
  59. package/workflows/container-contents/container-state/remote.js.map +1 -1
  60. package/workflows/container-contents/container-state/types.js.map +1 -1
  61. package/workflows/container-contents/documentLinks.d.ts +5 -0
  62. package/workflows/container-contents/documentLinks.js +2 -0
  63. package/workflows/container-contents/documentLinks.js.map +1 -1
  64. package/workflows/container-contents/documentPurge.d.ts +7 -1
  65. package/workflows/container-contents/documentPurge.js +8 -1
  66. package/workflows/container-contents/documentPurge.js.map +1 -1
  67. package/workflows/documents/linkSet.d.ts +2 -0
  68. package/workflows/documents/linkSet.js +2 -0
  69. package/workflows/documents/linkSet.js.map +1 -1
  70. package/workflows/documents/linkSetRemote.d.ts +3 -0
  71. package/workflows/documents/linkSetRemote.js +3 -0
  72. package/workflows/documents/linkSetRemote.js.map +1 -1
  73. package/workflows/documents/purge.d.ts +4 -0
  74. package/workflows/documents/purge.js +7 -0
  75. package/workflows/documents/purge.js.map +1 -1
  76. package/workflows/documents/subtreePurgeScope.d.ts +8 -0
  77. package/workflows/documents/subtreePurgeScope.js +18 -0
  78. package/workflows/documents/subtreePurgeScope.js.map +1 -0
  79. package/workflows/organizations/acknowledgedPrincipalPredecessor.d.ts +39 -0
  80. package/workflows/organizations/acknowledgedPrincipalPredecessor.js +87 -0
  81. package/workflows/organizations/acknowledgedPrincipalPredecessor.js.map +1 -0
  82. package/workflows/organizations/currentOrganizationMutationRetention.js +13 -2
  83. package/workflows/organizations/currentOrganizationMutationRetention.js.map +1 -1
  84. package/workflows/organizations/index.d.ts +1 -0
  85. package/workflows/organizations/index.js +1 -0
  86. package/workflows/organizations/index.js.map +1 -1
  87. package/workflows/organizations/initialPrincipalCurrentPublication.js +3 -2
  88. package/workflows/organizations/initialPrincipalCurrentPublication.js.map +1 -1
  89. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js +68 -0
  90. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js.map +1 -0
  91. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.d.ts +3 -0
  92. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js +33 -55
  93. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js.map +1 -1
  94. package/workflows/organizations/runtimeCurrentGroupMutation.js +13 -2
  95. package/workflows/organizations/runtimeCurrentGroupMutation.js.map +1 -1
  96. package/workflows/organizations/runtimeCurrentOrganizationMutation.js +2 -2
  97. package/workflows/organizations/runtimeCurrentOrganizationMutation.js.map +1 -1
@@ -0,0 +1,175 @@
1
+ import { KeyingVerificationError } from "../../../internal/crypto/index.js";
2
+ import { errorMessage } from "../../../data/errorMessage.js";
3
+ import { verifyContainerWriterProjection } from "../../../data/keyingProjectionVerification/containerProjectionVerification.js";
4
+ import { runWithSecurityIncidentReporting } from "../../../data/keyingProjectionVerification/error.js";
5
+ import { rethrowProjectionVerificationCancelled } from "../../../data/keyingProjectionVerification/types.js";
6
+ import { hasUnsettledDocumentPlacement } from "../../../data/persistence/container-contents/documentPurgePlacement.js";
7
+ import { sqlDocumentsPersistence } from "../../../data/persistence/documents/documentsPersistence.js";
8
+ import { createRuntimePrincipalPolicyWarmer } from "../../principals/runtimePolicyWarmer.js";
9
+ import { assertLocalPurgeScope } from "./localPurgeScope.js";
10
+ import { snapshotContainerStates } from "./purgeTreeCollection.js";
11
+ import { readLocalPurgeScope } from "./readLocalPurgeScope.js";
12
+ async function verifiedRemoteScope(input, current, verifiedAncestorsById) {
13
+ const { runtime, rootContainerId } = input;
14
+ const targetId = current.container.id;
15
+ return runWithSecurityIncidentReporting(runtime.util.reportSecurityIncident, {
16
+ objectId: targetId,
17
+ objectKind: "container",
18
+ operation: "container.purge.scope",
19
+ }, async () => {
20
+ runtime.apiClient.evictContainerWriterProjection(targetId);
21
+ const projection = await runtime.apiClient.getContainerWriterProjection(targetId);
22
+ if (!projection || input.stillCurrent?.() === false)
23
+ return false;
24
+ if (projection.containerId !== targetId ||
25
+ projection.organizationId !== current.container.organizationId) {
26
+ throw new KeyingVerificationError("object_mismatch", "Subtree purge container identity mismatch");
27
+ }
28
+ const path = await verifyContainerWriterProjection({
29
+ execSql: runtime.infra.execSql,
30
+ projection,
31
+ resolveUserKey: input.resolveProjectionUserKey,
32
+ stillCurrent: input.stillCurrent,
33
+ warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(runtime),
34
+ });
35
+ const rootIndex = path.findIndex((head) => head.state.containerId === rootContainerId);
36
+ const latest = await input.persistence.loadContainerMetadataState(runtime.infra.execSql, targetId);
37
+ const pendingMoves = await readPendingMoves(input);
38
+ const allowed = rootIndex >= 0 &&
39
+ path[rootIndex]?.state.parentContainerId ===
40
+ input.containersById.get(rootContainerId)?.container.parentId &&
41
+ latest?.container.parentId === current.container.parentId &&
42
+ latest.record?.documentId === current.record.documentId &&
43
+ !path
44
+ .slice(rootIndex)
45
+ .some((head) => pendingMoves.has(head.state.containerId)) &&
46
+ input.stillCurrent?.() !== false;
47
+ if (allowed)
48
+ verifiedAncestorsById.set(targetId, path.slice(rootIndex).map((head) => head.state.containerId));
49
+ return allowed;
50
+ });
51
+ }
52
+ /**
53
+ * A local-only unit cites no signed request, so a remote ordinary root must
54
+ * prove that it still sits under the parent it was selected from. A system
55
+ * root's signed topology is fixed by its destination role.
56
+ */
57
+ async function rootPlacementIsCurrent(input, verifiedAncestorsById) {
58
+ const root = input.containersById.get(input.rootContainerId);
59
+ if (!root?.record.documentId || (root.container.systemSlot ?? null) !== null)
60
+ return true;
61
+ return verifiedRemoteScope(input, root, verifiedAncestorsById);
62
+ }
63
+ async function allowsContainer(input, containerId, verifiedAncestorsById, localOnlyUnit) {
64
+ const { runtime, persistence, rootContainerId } = input;
65
+ if (input.stillCurrent?.() === false)
66
+ return false;
67
+ const execSql = runtime.infra.execSql;
68
+ const pendingMoves = await readPendingMoves(input);
69
+ if (pendingMoves.has(rootContainerId))
70
+ return false;
71
+ const seen = new Set([rootContainerId]);
72
+ let currentId = containerId;
73
+ let verifiedRemoteAncestry = false;
74
+ while (currentId !== rootContainerId) {
75
+ if (!currentId || seen.has(currentId) || pendingMoves.has(currentId))
76
+ return false;
77
+ seen.add(currentId);
78
+ const expected = input.containersById.get(currentId);
79
+ const current = await persistence.loadContainerMetadataState(execSql, currentId);
80
+ if (!expected ||
81
+ !current?.record ||
82
+ current.container.parentId !== expected.container.parentId ||
83
+ current.container.organizationId !== expected.container.organizationId ||
84
+ current.record.documentId !== expected.record.documentId)
85
+ return false;
86
+ if (current.record.documentId) {
87
+ if (!(await verifiedRemoteScope(input, {
88
+ container: current.container,
89
+ record: current.record,
90
+ }, verifiedAncestorsById)))
91
+ return false;
92
+ verifiedRemoteAncestry = true;
93
+ break;
94
+ }
95
+ currentId = current.container.parentId;
96
+ }
97
+ if (localOnlyUnit &&
98
+ !verifiedRemoteAncestry &&
99
+ !(await rootPlacementIsCurrent(input, verifiedAncestorsById)))
100
+ return false;
101
+ return readLocalPurgeScope({
102
+ ...input,
103
+ containerId,
104
+ execSql,
105
+ verifiedAncestorsById,
106
+ });
107
+ }
108
+ async function readPendingMoves(input) {
109
+ return new Set((await input.persistence.listUnsyncedMoveIntents(input.runtime.infra.execSql)).map((intent) => intent.containerId));
110
+ }
111
+ async function documentPlacementIsCurrent(input, document) {
112
+ const execSql = input.runtime.infra.execSql;
113
+ if (await hasUnsettledDocumentPlacement(execSql, document.id))
114
+ return false;
115
+ const current = await sqlDocumentsPersistence.loadDocument(execSql, document.id);
116
+ return (current !== null &&
117
+ current.documentId === document.documentId &&
118
+ current.containerId === document.containerId &&
119
+ input.stillCurrent?.() !== false);
120
+ }
121
+ /** Listing edges only select candidates; each destructive unit proves its scope. */
122
+ export function createSubtreePurgeScope(request) {
123
+ const input = {
124
+ ...request,
125
+ containersById: snapshotContainerStates(request.containersById),
126
+ };
127
+ const verifiedAncestorsById = new Map();
128
+ return {
129
+ allowsLocalScopeInTransaction: (execSql, containerId) => checkAvailableScope(input, async () => {
130
+ await assertLocalPurgeScope({
131
+ ...input,
132
+ execSql,
133
+ containerId,
134
+ verifiedAncestorsById,
135
+ });
136
+ return true;
137
+ }),
138
+ assertLocalScopeInTransaction: (execSql, containerId, localId) => assertLocalPurgeScope({
139
+ ...input,
140
+ execSql,
141
+ containerId,
142
+ localId,
143
+ verifiedAncestorsById,
144
+ }),
145
+ allowsContainer: (containerId) => checkAvailableScope(input, () => allowsContainer(input, containerId, verifiedAncestorsById, !input.containersById.get(containerId)?.record.documentId)),
146
+ allowsDocument: (document) => checkAvailableScope(input, async () => {
147
+ if (!document.containerId ||
148
+ !(await documentPlacementIsCurrent(input, document)))
149
+ return false;
150
+ if (!(await allowsContainer(input, document.containerId, verifiedAncestorsById, !document.documentId)))
151
+ return false;
152
+ // Freeze local placement checks together after all remote verification.
153
+ return readLocalPurgeScope({
154
+ ...input,
155
+ containerId: document.containerId,
156
+ execSql: input.runtime.infra.execSql,
157
+ localId: document.id,
158
+ verifiedAncestorsById,
159
+ }, document);
160
+ }),
161
+ };
162
+ }
163
+ async function checkAvailableScope(input, check) {
164
+ try {
165
+ return await check();
166
+ }
167
+ catch (error) {
168
+ rethrowProjectionVerificationCancelled(error);
169
+ if (error instanceof KeyingVerificationError)
170
+ throw error;
171
+ input.runtime.util.log(`Container contents: purge scope unavailable: ${errorMessage(error)}`);
172
+ return false;
173
+ }
174
+ }
175
+ //# sourceMappingURL=purgeTreeScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"purgeTreeScope.js","sourceRoot":"","sources":["purgeTreeScope.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { errorMessage } from \"../../../data/errorMessage\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { verifyContainerWriterProjection } from \"../../../data/keyingProjectionVerification/containerProjectionVerification\";\nimport { runWithSecurityIncidentReporting } from \"../../../data/keyingProjectionVerification/error\";\nimport { rethrowProjectionVerificationCancelled } from \"../../../data/keyingProjectionVerification/types\";\nimport { hasUnsettledDocumentPlacement } from \"../../../data/persistence/container-contents/documentPurgePlacement\";\nimport { sqlDocumentsPersistence } from \"../../../data/persistence/documents/documentsPersistence\";\nimport type { ExecSql } from \"../../../data/sqlite/sqlSchema\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport type { ContainerContentsPersistence } from \"../containerPersistence\";\nimport type { ContainerState } from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\nimport { assertLocalPurgeScope } from \"./localPurgeScope\";\nimport { snapshotContainerStates } from \"./purgeTreeCollection\";\nimport { readLocalPurgeScope } from \"./readLocalPurgeScope\";\n\ninterface SubtreePurgeScopeInput {\n containersById: ReadonlyMap<string, ContainerState>;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n rootContainerId: string;\n runtime: ContainerContentsWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}\n\nasync function verifiedRemoteScope(\n input: SubtreePurgeScopeInput,\n current: Pick<ContainerState, \"container\" | \"record\">,\n verifiedAncestorsById: Map<string, readonly string[]>,\n): Promise<boolean> {\n const { runtime, rootContainerId } = input;\n const targetId = current.container.id;\n return runWithSecurityIncidentReporting(\n runtime.util.reportSecurityIncident,\n {\n objectId: targetId,\n objectKind: \"container\",\n operation: \"container.purge.scope\",\n },\n async () => {\n runtime.apiClient.evictContainerWriterProjection(targetId);\n const projection =\n await runtime.apiClient.getContainerWriterProjection(targetId);\n if (!projection || input.stillCurrent?.() === false) return false;\n if (\n projection.containerId !== targetId ||\n projection.organizationId !== current.container.organizationId\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Subtree purge container identity mismatch\",\n );\n }\n const path = await verifyContainerWriterProjection({\n execSql: runtime.infra.execSql,\n projection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies:\n createRuntimePrincipalPolicyWarmer(runtime),\n });\n const rootIndex = path.findIndex(\n (head) => head.state.containerId === rootContainerId,\n );\n const latest = await input.persistence.loadContainerMetadataState(\n runtime.infra.execSql,\n targetId,\n );\n const pendingMoves = await readPendingMoves(input);\n const allowed =\n rootIndex >= 0 &&\n path[rootIndex]?.state.parentContainerId ===\n input.containersById.get(rootContainerId)?.container.parentId &&\n latest?.container.parentId === current.container.parentId &&\n latest.record?.documentId === current.record.documentId &&\n !path\n .slice(rootIndex)\n .some((head) => pendingMoves.has(head.state.containerId)) &&\n input.stillCurrent?.() !== false;\n if (allowed)\n verifiedAncestorsById.set(\n targetId,\n path.slice(rootIndex).map((head) => head.state.containerId),\n );\n return allowed;\n },\n );\n}\n\n/**\n * A local-only unit cites no signed request, so a remote ordinary root must\n * prove that it still sits under the parent it was selected from. A system\n * root's signed topology is fixed by its destination role.\n */\nasync function rootPlacementIsCurrent(\n input: SubtreePurgeScopeInput,\n verifiedAncestorsById: Map<string, readonly string[]>,\n): Promise<boolean> {\n const root = input.containersById.get(input.rootContainerId);\n if (!root?.record.documentId || (root.container.systemSlot ?? null) !== null)\n return true;\n return verifiedRemoteScope(input, root, verifiedAncestorsById);\n}\n\nasync function allowsContainer(\n input: SubtreePurgeScopeInput,\n containerId: string,\n verifiedAncestorsById: Map<string, readonly string[]>,\n localOnlyUnit: boolean,\n): Promise<boolean> {\n const { runtime, persistence, rootContainerId } = input;\n if (input.stillCurrent?.() === false) return false;\n const execSql = runtime.infra.execSql;\n const pendingMoves = await readPendingMoves(input);\n if (pendingMoves.has(rootContainerId)) return false;\n const seen = new Set<string>([rootContainerId]);\n let currentId: string | null = containerId;\n let verifiedRemoteAncestry = false;\n while (currentId !== rootContainerId) {\n if (!currentId || seen.has(currentId) || pendingMoves.has(currentId))\n return false;\n seen.add(currentId);\n const expected = input.containersById.get(currentId);\n const current = await persistence.loadContainerMetadataState(\n execSql,\n currentId,\n );\n if (\n !expected ||\n !current?.record ||\n current.container.parentId !== expected.container.parentId ||\n current.container.organizationId !== expected.container.organizationId ||\n current.record.documentId !== expected.record.documentId\n )\n return false;\n if (current.record.documentId) {\n if (\n !(await verifiedRemoteScope(\n input,\n {\n container: current.container,\n record: current.record,\n },\n verifiedAncestorsById,\n ))\n )\n return false;\n verifiedRemoteAncestry = true;\n break;\n }\n currentId = current.container.parentId;\n }\n if (\n localOnlyUnit &&\n !verifiedRemoteAncestry &&\n !(await rootPlacementIsCurrent(input, verifiedAncestorsById))\n )\n return false;\n return readLocalPurgeScope({\n ...input,\n containerId,\n execSql,\n verifiedAncestorsById,\n });\n}\n\nasync function readPendingMoves(input: SubtreePurgeScopeInput) {\n return new Set(\n (\n await input.persistence.listUnsyncedMoveIntents(\n input.runtime.infra.execSql,\n )\n ).map((intent) => intent.containerId),\n );\n}\n\nasync function documentPlacementIsCurrent(\n input: SubtreePurgeScopeInput,\n document: DocumentSummary,\n): Promise<boolean> {\n const execSql = input.runtime.infra.execSql;\n if (await hasUnsettledDocumentPlacement(execSql, document.id)) return false;\n const current = await sqlDocumentsPersistence.loadDocument(\n execSql,\n document.id,\n );\n return (\n current !== null &&\n current.documentId === document.documentId &&\n current.containerId === document.containerId &&\n input.stillCurrent?.() !== false\n );\n}\n\n/** Listing edges only select candidates; each destructive unit proves its scope. */\nexport function createSubtreePurgeScope(request: SubtreePurgeScopeInput) {\n const input = {\n ...request,\n containersById: snapshotContainerStates(request.containersById),\n };\n const verifiedAncestorsById = new Map<string, readonly string[]>();\n return {\n allowsLocalScopeInTransaction: (execSql: ExecSql, containerId: string) =>\n checkAvailableScope(input, async () => {\n await assertLocalPurgeScope({\n ...input,\n execSql,\n containerId,\n verifiedAncestorsById,\n });\n return true;\n }),\n assertLocalScopeInTransaction: (\n execSql: ExecSql,\n containerId: string | null | undefined,\n localId: string,\n ) =>\n assertLocalPurgeScope({\n ...input,\n execSql,\n containerId,\n localId,\n verifiedAncestorsById,\n }),\n allowsContainer: (containerId: string) =>\n checkAvailableScope(input, () =>\n allowsContainer(\n input,\n containerId,\n verifiedAncestorsById,\n !input.containersById.get(containerId)?.record.documentId,\n ),\n ),\n allowsDocument: (document: DocumentSummary) =>\n checkAvailableScope(input, async () => {\n if (\n !document.containerId ||\n !(await documentPlacementIsCurrent(input, document))\n )\n return false;\n if (\n !(await allowsContainer(\n input,\n document.containerId,\n verifiedAncestorsById,\n !document.documentId,\n ))\n )\n return false;\n // Freeze local placement checks together after all remote verification.\n return readLocalPurgeScope(\n {\n ...input,\n containerId: document.containerId,\n execSql: input.runtime.infra.execSql,\n localId: document.id,\n verifiedAncestorsById,\n },\n document,\n );\n }),\n };\n}\n\nasync function checkAvailableScope(\n input: SubtreePurgeScopeInput,\n check: () => Promise<boolean>,\n): Promise<boolean> {\n try {\n return await check();\n } catch (error) {\n rethrowProjectionVerificationCancelled(error);\n if (error instanceof KeyingVerificationError) throw error;\n input.runtime.util.log(\n `Container contents: purge scope unavailable: ${errorMessage(error)}`,\n );\n return false;\n }\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import { sqlDocumentsPersistence } from "../../../data/persistence/documents/documentsPersistence.js";
2
+ import { getClientSQLitePersistenceRuntime } from "../../../data/sqlite/sqlitePersistenceRuntime.js";
3
+ import { runSerializedSqlMutation } from "../../../data/sqlite/sqlSchema.js";
4
+ import { assertLocalPurgeScope } from "./localPurgeScope.js";
5
+ /** Final local scope check before HTTP dispatch, with no remote I/O under lock. */
6
+ export function readLocalPurgeScope(input, document) {
7
+ return runSerializedSqlMutation(input.execSql, async (lockedExecSql) => getClientSQLitePersistenceRuntime(lockedExecSql).transaction(async () => {
8
+ await assertLocalPurgeScope({ ...input, execSql: lockedExecSql });
9
+ if (!document)
10
+ return true;
11
+ const current = await sqlDocumentsPersistence.loadDocument(lockedExecSql, document.id);
12
+ return (current !== null &&
13
+ current.documentId === document.documentId &&
14
+ current.containerId === document.containerId &&
15
+ input.stillCurrent?.() !== false);
16
+ }, { behavior: "immediate" }));
17
+ }
18
+ //# sourceMappingURL=readLocalPurgeScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"readLocalPurgeScope.js","sourceRoot":"","sources":["readLocalPurgeScope.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,0DAA0D,CAAC;AACnG,OAAO,EAAE,iCAAiC,EAAE,MAAM,+CAA+C,CAAC;AAClG,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,mFAAmF;AACnF,MAAM,UAAU,mBAAmB,CACjC,KAAkD,EAClD,QAA0B;IAE1B,OAAO,wBAAwB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,EAAE,CACrE,iCAAiC,CAAC,aAAa,CAAC,CAAC,WAAW,CAC1D,KAAK,IAAI,EAAE;QACT,MAAM,qBAAqB,CAAC,EAAE,GAAG,KAAK,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,QAAQ;YAAE,OAAO,IAAI,CAAC;QAC3B,MAAM,OAAO,GAAG,MAAM,uBAAuB,CAAC,YAAY,CACxD,aAAa,EACb,QAAQ,CAAC,EAAE,CACZ,CAAC;QACF,OAAO,CACL,OAAO,KAAK,IAAI;YAChB,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,UAAU;YAC1C,OAAO,CAAC,WAAW,KAAK,QAAQ,CAAC,WAAW;YAC5C,KAAK,CAAC,YAAY,EAAE,EAAE,KAAK,KAAK,CACjC,CAAC;IACJ,CAAC,EACD,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { DocumentSummary } from \"../../../data/documents/documentSummary\";\nimport { sqlDocumentsPersistence } from \"../../../data/persistence/documents/documentsPersistence\";\nimport { getClientSQLitePersistenceRuntime } from \"../../../data/sqlite/sqlitePersistenceRuntime\";\nimport { runSerializedSqlMutation } from \"../../../data/sqlite/sqlSchema\";\nimport { assertLocalPurgeScope } from \"./localPurgeScope\";\n\n/** Final local scope check before HTTP dispatch, with no remote I/O under lock. */\nexport function readLocalPurgeScope(\n input: Parameters<typeof assertLocalPurgeScope>[0],\n document?: DocumentSummary,\n): Promise<boolean> {\n return runSerializedSqlMutation(input.execSql, async (lockedExecSql) =>\n getClientSQLitePersistenceRuntime(lockedExecSql).transaction(\n async () => {\n await assertLocalPurgeScope({ ...input, execSql: lockedExecSql });\n if (!document) return true;\n const current = await sqlDocumentsPersistence.loadDocument(\n lockedExecSql,\n document.id,\n );\n return (\n current !== null &&\n current.documentId === document.documentId &&\n current.containerId === document.containerId &&\n input.stillCurrent?.() !== false\n );\n },\n { behavior: \"immediate\" },\n ),\n );\n}\n"]}
@@ -145,6 +145,7 @@ export async function deleteRemoteContainer(input) {
145
145
  const deleteResult = await input.runtime.apiClient.deleteContainerResult(input.containerId, {
146
146
  expectedPaymentRequiredOrganizationId: input.organizationId,
147
147
  reportErrors: false,
148
+ requiredAncestorId: input.requiredAncestorId,
148
149
  });
149
150
  if (!deleteResult.ok && !isContainerNotFoundFailure(deleteResult)) {
150
151
  deleteResult.report();
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","sourceRoot":"","sources":["remote.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { isContainerNotFoundFailure } from \"../../../data/containers/shared/mutationFailures\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport {\n moveRemoteContainer as moveRemoteContainerMutation,\n readContainerMutationMetadataDocumentId,\n referencedPrincipalHeadsFromContainerMutationResponse,\n shareRemoteContainer as shareRemoteContainerMutation,\n} from \"../../containers\";\nimport { createRuntimeCurrentGroupShare } from \"../../containers/child/runtimeCurrentGroupShare\";\nimport { resolveDocumentCreateAuthor } from \"../../documents\";\nimport { createRuntimeGroupMetadataAccess } from \"../../organizations/groupMetadataRuntime\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport {\n type ContainerAlreadyCommitted,\n createRemoteContainerWithMetadataDocument,\n} from \"./createWithMetadata\";\nimport type {\n ContainerWorkflowRuntime,\n CreatedRemoteContainerState,\n RemoteContainer,\n SharedRemoteContainerState,\n} from \"./types\";\n\n/**\n * Resolve the author, api client, executor, and encapsulation secret a remote\n * container mutation needs, logging a skip for the given operation when the\n * writer context is unavailable.\n */\nfunction resolveContainerWriterContext(\n runtime: ContainerWorkflowRuntime,\n operation:\n | \"container create\"\n | \"container group share\"\n | \"container move\"\n | \"container share\",\n) {\n const author = resolveDocumentCreateAuthor(runtime);\n const secretKey = runtime.crypto.encapsulationKeyPair?.secretKey;\n if (!author || !secretKey) {\n runtime.util.log(\n `Container contents: skipped ${operation} because the writer context is unavailable.`,\n );\n return null;\n }\n\n return {\n apiClient: runtime.apiClient,\n author,\n execSql: runtime.infra.execSql,\n secretKey,\n };\n}\n\nexport async function createRemoteContainer(input: {\n systemSlot?: ContainerSystemSlot | null | undefined;\n containerId: string;\n parentContainerId: string;\n parentProjection?: ContainerWriterProjectionResponse | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<CreatedRemoteContainerState | ContainerAlreadyCommitted | null> {\n return createRemoteContainerWithMetadataDocument(input);\n}\n\nfunction sharedRemoteContainerStateFromMutation(\n previousProjection: ContainerWriterProjectionResponse,\n response: ContainerMutationResponse,\n): SharedRemoteContainerState {\n if (\n response.containerId !== previousProjection.containerId ||\n response.organizationId !== previousProjection.organizationId\n )\n throw new Error(\"Container mutation response projection mismatch\");\n return {\n accessManifestHash: response.manifestHead.manifestHash,\n accessEpoch: response.manifestHead.epoch,\n createdAt: response.createdAt,\n metadataDocumentId: readContainerMutationMetadataDocumentId({ response }),\n referencedPrincipalHeads:\n referencedPrincipalHeadsFromContainerMutationResponse({ response }),\n updatedAt: response.updatedAt,\n // The next read obtains the post-commit proof. A failed read cannot undo\n // acknowledgement of this successful mutation.\n writerProjection: null,\n };\n}\n\nexport async function shareRemoteContainer(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n previousProjection: ContainerWriterProjectionResponse;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shared = await shareRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n previousProjection: input.previousProjection,\n recipientUserId: input.recipientUserId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function shareRemoteContainerWithGroup(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n expectedGroupName?: string | undefined;\n knownContainerKeks?: ReadonlyMap<string, Uint8Array> | undefined;\n previousProjection: ContainerWriterProjectionResponse;\n recipientGroupId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container group share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shareWithGroup = createRuntimeCurrentGroupShare(input.runtime);\n if (!shareWithGroup)\n throw new ProjectionDependencyUnavailableError(\n \"Group sharing requires private paged recovery\",\n );\n const shared = await shareWithGroup({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n expectedGroupName: input.expectedGroupName ?? null,\n readEncryptedName: (bundle) =>\n createRuntimeGroupMetadataAccess(\n input.runtime,\n author.organizationId,\n input.stillCurrent,\n ).readName(bundle),\n knownContainerKeks: input.knownContainerKeks,\n previousProjection: input.previousProjection,\n recipientGroupId: input.recipientGroupId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n signingKeyPair: input.runtime.crypto.signingKeyPair,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function moveRemoteContainer(input: {\n containerId: string;\n parentContainerId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<RemoteContainer | null> {\n const writer = resolveContainerWriterContext(input.runtime, \"container move\");\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const moved = await moveRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n apiClient,\n author,\n containerId: input.containerId,\n destinationParentContainerId: input.parentContainerId,\n execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!moved) {\n return null;\n }\n\n return {\n createdAt: moved.response.createdAt,\n effectiveAccessLevel: \"admin\",\n id: moved.response.containerId,\n organizationId: moved.response.organizationId,\n parentId: moved.response.parentId,\n metadataDocumentId: readContainerMutationMetadataDocumentId({\n response: moved.response,\n }),\n metadataAccessEpoch: moved.response.manifestHead.epoch,\n metadataAccessStateHash: moved.response.manifestHead.manifestHash,\n metadataReferencedPrincipals:\n referencedPrincipalHeadsFromContainerMutationResponse({\n response: moved.response,\n }),\n updatedAt: moved.response.updatedAt,\n };\n}\n\nexport async function deleteRemoteContainer(input: {\n containerId: string;\n organizationId: string;\n runtime: ContainerWorkflowRuntime;\n}): Promise<{ deletedAt: string } | null> {\n const deleteResult = await input.runtime.apiClient.deleteContainerResult(\n input.containerId,\n {\n expectedPaymentRequiredOrganizationId: input.organizationId,\n reportErrors: false,\n },\n );\n if (!deleteResult.ok && !isContainerNotFoundFailure(deleteResult)) {\n deleteResult.report();\n return null;\n }\n\n if (!deleteResult.ok) {\n // A 404 proves this immutable id is permanently absent, but it does not\n // carry the server deletion time. A maximal fence prevents an older\n // in-flight listing page from resurrecting it.\n return { deletedAt: \"9999-12-31T23:59:59.999Z\" };\n }\n\n return { deletedAt: deleteResult.data.deletedAt };\n}\n"]}
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["remote.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerMutationResponse,\n ContainerWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport { isContainerNotFoundFailure } from \"../../../data/containers/shared/mutationFailures\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport {\n moveRemoteContainer as moveRemoteContainerMutation,\n readContainerMutationMetadataDocumentId,\n referencedPrincipalHeadsFromContainerMutationResponse,\n shareRemoteContainer as shareRemoteContainerMutation,\n} from \"../../containers\";\nimport { createRuntimeCurrentGroupShare } from \"../../containers/child/runtimeCurrentGroupShare\";\nimport { resolveDocumentCreateAuthor } from \"../../documents\";\nimport { createRuntimeGroupMetadataAccess } from \"../../organizations/groupMetadataRuntime\";\nimport { createRuntimePrincipalPolicyWarmer } from \"../../principals/runtimePolicyWarmer\";\nimport {\n type ContainerAlreadyCommitted,\n createRemoteContainerWithMetadataDocument,\n} from \"./createWithMetadata\";\nimport type {\n ContainerWorkflowRuntime,\n CreatedRemoteContainerState,\n RemoteContainer,\n SharedRemoteContainerState,\n} from \"./types\";\n\n/**\n * Resolve the author, api client, executor, and encapsulation secret a remote\n * container mutation needs, logging a skip for the given operation when the\n * writer context is unavailable.\n */\nfunction resolveContainerWriterContext(\n runtime: ContainerWorkflowRuntime,\n operation:\n | \"container create\"\n | \"container group share\"\n | \"container move\"\n | \"container share\",\n) {\n const author = resolveDocumentCreateAuthor(runtime);\n const secretKey = runtime.crypto.encapsulationKeyPair?.secretKey;\n if (!author || !secretKey) {\n runtime.util.log(\n `Container contents: skipped ${operation} because the writer context is unavailable.`,\n );\n return null;\n }\n\n return {\n apiClient: runtime.apiClient,\n author,\n execSql: runtime.infra.execSql,\n secretKey,\n };\n}\n\nexport async function createRemoteContainer(input: {\n systemSlot?: ContainerSystemSlot | null | undefined;\n containerId: string;\n parentContainerId: string;\n parentProjection?: ContainerWriterProjectionResponse | undefined;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<CreatedRemoteContainerState | ContainerAlreadyCommitted | null> {\n return createRemoteContainerWithMetadataDocument(input);\n}\n\nfunction sharedRemoteContainerStateFromMutation(\n previousProjection: ContainerWriterProjectionResponse,\n response: ContainerMutationResponse,\n): SharedRemoteContainerState {\n if (\n response.containerId !== previousProjection.containerId ||\n response.organizationId !== previousProjection.organizationId\n )\n throw new Error(\"Container mutation response projection mismatch\");\n return {\n accessManifestHash: response.manifestHead.manifestHash,\n accessEpoch: response.manifestHead.epoch,\n createdAt: response.createdAt,\n metadataDocumentId: readContainerMutationMetadataDocumentId({ response }),\n referencedPrincipalHeads:\n referencedPrincipalHeadsFromContainerMutationResponse({ response }),\n updatedAt: response.updatedAt,\n // The next read obtains the post-commit proof. A failed read cannot undo\n // acknowledgement of this successful mutation.\n writerProjection: null,\n };\n}\n\nexport async function shareRemoteContainer(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n previousProjection: ContainerWriterProjectionResponse;\n recipientUserId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shared = await shareRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n previousProjection: input.previousProjection,\n recipientUserId: input.recipientUserId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function shareRemoteContainerWithGroup(input: {\n accessLevel: \"read\" | \"write\" | \"admin\";\n containerId: string;\n expectedGroupName?: string | undefined;\n knownContainerKeks?: ReadonlyMap<string, Uint8Array> | undefined;\n previousProjection: ContainerWriterProjectionResponse;\n recipientGroupId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<SharedRemoteContainerState | null> {\n const writer = resolveContainerWriterContext(\n input.runtime,\n \"container group share\",\n );\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const shareWithGroup = createRuntimeCurrentGroupShare(input.runtime);\n if (!shareWithGroup)\n throw new ProjectionDependencyUnavailableError(\n \"Group sharing requires private paged recovery\",\n );\n const shared = await shareWithGroup({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n accessLevel: input.accessLevel,\n apiClient,\n author,\n containerId: input.containerId,\n execSql,\n expectedGroupName: input.expectedGroupName ?? null,\n readEncryptedName: (bundle) =>\n createRuntimeGroupMetadataAccess(\n input.runtime,\n author.organizationId,\n input.stillCurrent,\n ).readName(bundle),\n knownContainerKeks: input.knownContainerKeks,\n previousProjection: input.previousProjection,\n recipientGroupId: input.recipientGroupId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n resolveTrustedUserIdentity: input.runtime.resolveTrustedUserIdentity,\n signingKeyPair: input.runtime.crypto.signingKeyPair,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!shared) {\n return null;\n }\n\n return sharedRemoteContainerStateFromMutation(\n input.previousProjection,\n shared.response,\n );\n}\n\nexport async function moveRemoteContainer(input: {\n containerId: string;\n parentContainerId: string;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n stillCurrent?: (() => boolean) | undefined;\n}): Promise<RemoteContainer | null> {\n const writer = resolveContainerWriterContext(input.runtime, \"container move\");\n if (!writer) {\n return null;\n }\n const { apiClient, author, execSql, secretKey: targetSecretKey } = writer;\n\n const moved = await moveRemoteContainerMutation({\n reportSecurityIncident: input.runtime.util.reportSecurityIncident,\n apiClient,\n author,\n containerId: input.containerId,\n destinationParentContainerId: input.parentContainerId,\n execSql,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n targetSecretKey,\n warmReferencedPrincipalPolicies: createRuntimePrincipalPolicyWarmer(\n input.runtime,\n ),\n });\n if (!moved) {\n return null;\n }\n\n return {\n createdAt: moved.response.createdAt,\n effectiveAccessLevel: \"admin\",\n id: moved.response.containerId,\n organizationId: moved.response.organizationId,\n parentId: moved.response.parentId,\n metadataDocumentId: readContainerMutationMetadataDocumentId({\n response: moved.response,\n }),\n metadataAccessEpoch: moved.response.manifestHead.epoch,\n metadataAccessStateHash: moved.response.manifestHead.manifestHash,\n metadataReferencedPrincipals:\n referencedPrincipalHeadsFromContainerMutationResponse({\n response: moved.response,\n }),\n updatedAt: moved.response.updatedAt,\n };\n}\n\nexport async function deleteRemoteContainer(input: {\n containerId: string;\n organizationId: string;\n requiredAncestorId?: string | undefined;\n runtime: ContainerWorkflowRuntime;\n}): Promise<{ deletedAt: string } | null> {\n const deleteResult = await input.runtime.apiClient.deleteContainerResult(\n input.containerId,\n {\n expectedPaymentRequiredOrganizationId: input.organizationId,\n reportErrors: false,\n requiredAncestorId: input.requiredAncestorId,\n },\n );\n if (!deleteResult.ok && !isContainerNotFoundFailure(deleteResult)) {\n deleteResult.report();\n return null;\n }\n\n if (!deleteResult.ok) {\n // A 404 proves this immutable id is permanently absent, but it does not\n // carry the server deletion time. A maximal fence prevents an older\n // in-flight listing page from resurrecting it.\n return { deletedAt: \"9999-12-31T23:59:59.999Z\" };\n }\n\n return { deletedAt: deleteResult.data.deletedAt };\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerDeleteResponse,\n ContainerWriterProjectionResponse,\n PrincipalPolicyBundleResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { createInitializedContainerMetadataDocument } from \"../../../data/containers/containerMetadataDocument\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../../data/sqlite/documentPersistence\";\nimport type {\n createRemoteContainer,\n moveRemoteContainer,\n shareRemoteContainer,\n shareRemoteContainerWithGroup,\n} from \"../../containers\";\nimport type { createRemoteDocument } from \"../../documents\";\nimport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerMoveIntentRecord,\n} from \"../containerPersistence\";\nimport type {\n ContainerState,\n RemoteContainer as HydratedRemoteContainer,\n RemoteContainerHydrationHost,\n} from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ContainerWorkflowApi = Parameters<\n typeof createRemoteContainer\n>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainerWithGroup>[0][\"apiClient\"] &\n Parameters<typeof moveRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof createRemoteDocument>[0][\"apiClient\"] &\n Partial<\n Pick<\n ApiClient,\n \"getPrincipalPolicyPages\" | \"getProjectionPolicyHistoryPages\"\n >\n > & {\n deleteContainerResult(\n containerId: string,\n options?: {\n expectedPaymentRequiredOrganizationId?: string;\n reportErrors?: boolean;\n },\n ): Promise<\n | { data: ContainerDeleteResponse; ok: true }\n | {\n ok: false;\n report: () => void;\n status: number | null;\n }\n >;\n primeContainerWriterProjection(\n containerId: string,\n projection: ContainerWriterProjectionResponse,\n ): void;\n getCurrentPrincipalPolicy(\n principalType: \"group\" | \"organization\",\n principalId: string,\n ): Promise<PrincipalPolicyBundleResponse | null>;\n };\n\nexport interface ContainerWorkflowRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n readonly apiClient: ContainerWorkflowApi;\n}\n\nexport interface CreatedRemoteContainerState {\n accessManifestHash: string;\n /** The slot this client signed into the create; never the server's echo. */\n systemSlot: ContainerSystemSlot | null;\n containerId: string;\n createdAt: string;\n metadataDocumentId: string;\n organizationId: string;\n /** A created container is a child; the acknowledgement echoed this parent. */\n parentId: string;\n persistedMetadataState: Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n >;\n updatedAt: string;\n}\n\nexport interface SharedRemoteContainerState {\n accessEpoch: number;\n accessManifestHash: string;\n createdAt: string;\n metadataDocumentId: string;\n referencedPrincipalHeads: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n writerProjection: ContainerWriterProjectionResponse | null;\n}\n\nexport interface SharedContainerState {\n container: ContainerState[\"container\"];\n record: ContainerState[\"record\"];\n}\n\nexport type SharedContainerStateResult =\n | { status: \"missing\" }\n | { status: \"confirmed\" }\n | (SharedContainerState & {\n status: \"identity-superseded\" | \"persisted\";\n });\n\nexport type RemoteContainer = HydratedRemoteContainer;\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createInitializedContainerMetadataDocument>\n>[\"doc\"];\n\nexport interface CreatedChildContainerState {\n containerState: ContainerState;\n initialUpdate: Uint8Array;\n shouldRequestSync: boolean;\n}\n\nexport interface ContainerIntentSyncState {\n containersById: ReadonlyMap<string, ContainerState>;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n /**\n * Verifies that a listed container is this device's pending create before it\n * is adopted and returns where it sits; `assertContainerCreateAdoptable`\n * unless a test replaces it.\n */\n verifyCreateAdoption?: (input: {\n readonly containerId: string;\n readonly expectedOrganizationId: string;\n }) => Promise<AdoptedContainerPlacement>;\n}\n\n/** Where a verified listed create committed, and where its container is now. */\nexport interface AdoptedContainerPlacement {\n /** The parent its signed epoch-1 `container.create` named. */\n readonly createdParentId: string;\n /** The verified head's parent; another writer may have moved it since. */\n readonly currentParentId: string;\n}\n\nexport type ContainerCreateIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerCreateIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\"\n>;\n\nexport interface ContainerCreateIntentSyncInput {\n host: ContainerCreateIntentSyncHost;\n /** A parent whose own create has not settled takes no child create. */\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerCreateIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerCreateIntentSyncState;\n}\n\nexport type ContainerMoveIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerMoveIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\" | \"updateSnapshot\"\n>;\n\nexport interface ContainerMoveIntentSyncInput {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n /** A folder whose create has not settled takes no structural writes. */\n isCreatePending: (containerId: string) => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerMoveIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerMoveIntentSyncState;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { ContainerSystemSlot } from \"@tearleads/validators/containerSystemSlot\";\nimport type {\n ContainerDeleteResponse,\n ContainerWriterProjectionResponse,\n PrincipalPolicyBundleResponse,\n ReferencedPrincipalStateResponse,\n} from \"@tearleads/validators/response\";\nimport type { createInitializedContainerMetadataDocument } from \"../../../data/containers/containerMetadataDocument\";\nimport type { ProjectionUserKeyResolver } from \"../../../data/keyingProjectionVerification\";\nimport type { DocumentRecord } from \"../../../data/sqlite/documentPersistence\";\nimport type {\n createRemoteContainer,\n moveRemoteContainer,\n shareRemoteContainer,\n shareRemoteContainerWithGroup,\n} from \"../../containers\";\nimport type { createRemoteDocument } from \"../../documents\";\nimport type {\n ContainerContentsPersistence,\n ContainerCreateIntentRecord,\n ContainerMoveIntentRecord,\n} from \"../containerPersistence\";\nimport type {\n ContainerState,\n RemoteContainer as HydratedRemoteContainer,\n RemoteContainerHydrationHost,\n} from \"../remoteHydration\";\nimport type { ContainerContentsWorkflowRuntime } from \"../runtime\";\n\nexport type ContainerWorkflowApi = Parameters<\n typeof createRemoteContainer\n>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof shareRemoteContainerWithGroup>[0][\"apiClient\"] &\n Parameters<typeof moveRemoteContainer>[0][\"apiClient\"] &\n Parameters<typeof createRemoteDocument>[0][\"apiClient\"] &\n Partial<\n Pick<\n ApiClient,\n \"getPrincipalPolicyPages\" | \"getProjectionPolicyHistoryPages\"\n >\n > & {\n deleteContainerResult(\n containerId: string,\n options?: {\n expectedPaymentRequiredOrganizationId?: string;\n reportErrors?: boolean;\n requiredAncestorId?: string | undefined;\n },\n ): Promise<\n | { data: ContainerDeleteResponse; ok: true }\n | {\n ok: false;\n report: () => void;\n status: number | null;\n }\n >;\n primeContainerWriterProjection(\n containerId: string,\n projection: ContainerWriterProjectionResponse,\n ): void;\n getCurrentPrincipalPolicy(\n principalType: \"group\" | \"organization\",\n principalId: string,\n ): Promise<PrincipalPolicyBundleResponse | null>;\n };\n\nexport interface ContainerWorkflowRuntime\n extends Pick<\n ContainerContentsWorkflowRuntime,\n | \"auth\"\n | \"crypto\"\n | \"infra\"\n | \"resolveTrustedUserIdentity\"\n | \"state\"\n | \"util\"\n > {\n readonly apiClient: ContainerWorkflowApi;\n}\n\nexport interface CreatedRemoteContainerState {\n accessManifestHash: string;\n /** The slot this client signed into the create; never the server's echo. */\n systemSlot: ContainerSystemSlot | null;\n containerId: string;\n createdAt: string;\n metadataDocumentId: string;\n organizationId: string;\n /** A created container is a child; the acknowledgement echoed this parent. */\n parentId: string;\n persistedMetadataState: Pick<\n DocumentRecord,\n | \"documentId\"\n | \"contentKeyBundle\"\n | \"documentKekTargets\"\n | \"documentManifestBundle\"\n >;\n updatedAt: string;\n}\n\nexport interface SharedRemoteContainerState {\n accessEpoch: number;\n accessManifestHash: string;\n createdAt: string;\n metadataDocumentId: string;\n referencedPrincipalHeads: ReferencedPrincipalStateResponse[];\n updatedAt: string;\n writerProjection: ContainerWriterProjectionResponse | null;\n}\n\nexport interface SharedContainerState {\n container: ContainerState[\"container\"];\n record: ContainerState[\"record\"];\n}\n\nexport type SharedContainerStateResult =\n | { status: \"missing\" }\n | { status: \"confirmed\" }\n | (SharedContainerState & {\n status: \"identity-superseded\" | \"persisted\";\n });\n\nexport type RemoteContainer = HydratedRemoteContainer;\n\nexport type ContainerMetadataDocumentState = Awaited<\n ReturnType<typeof createInitializedContainerMetadataDocument>\n>[\"doc\"];\n\nexport interface CreatedChildContainerState {\n containerState: ContainerState;\n initialUpdate: Uint8Array;\n shouldRequestSync: boolean;\n}\n\nexport interface ContainerIntentSyncState {\n containersById: ReadonlyMap<string, ContainerState>;\n persistence: ContainerContentsPersistence;\n resolveProjectionUserKey: ProjectionUserKeyResolver;\n runtime: ContainerWorkflowRuntime;\n /**\n * Verifies that a listed container is this device's pending create before it\n * is adopted and returns where it sits; `assertContainerCreateAdoptable`\n * unless a test replaces it.\n */\n verifyCreateAdoption?: (input: {\n readonly containerId: string;\n readonly expectedOrganizationId: string;\n }) => Promise<AdoptedContainerPlacement>;\n}\n\n/** Where a verified listed create committed, and where its container is now. */\nexport interface AdoptedContainerPlacement {\n /** The parent its signed epoch-1 `container.create` named. */\n readonly createdParentId: string;\n /** The verified head's parent; another writer may have moved it since. */\n readonly currentParentId: string;\n}\n\nexport type ContainerCreateIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerCreateIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\"\n>;\n\nexport interface ContainerCreateIntentSyncInput {\n host: ContainerCreateIntentSyncHost;\n /** A parent whose own create has not settled takes no child create. */\n isCreatePending: (containerId: string) => boolean;\n isCurrent: () => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerCreateIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerCreateIntentSyncState;\n}\n\nexport type ContainerMoveIntentSyncState = ContainerIntentSyncState;\n\nexport type ContainerMoveIntentSyncHost = Pick<\n RemoteContainerHydrationHost,\n \"persistContainerState\" | \"updateSnapshot\"\n>;\n\nexport interface ContainerMoveIntentSyncInput {\n host: ContainerMoveIntentSyncHost;\n isCurrent: () => boolean;\n /** A folder whose create has not settled takes no structural writes. */\n isCreatePending: (containerId: string) => boolean;\n isRemoteSyncBlocked: (organizationId: string) => boolean;\n intent: ContainerMoveIntentRecord;\n requestRemoteReconciliation: (parentContainerId: string | null) => void;\n state: ContainerMoveIntentSyncState;\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import type { ProjectionUserKeyResolver } from "../../data/keyingProjectionVerification.js";
2
2
  import type { ExecSql } from "../../data/sqlite/sqlSchema.js";
3
3
  import { type DocumentLinkSetFailureHandler, type RelinkRemoteDocumentResult, relinkRemoteDocument } from "../documents/index.js";
4
+ import type { SubtreePurgeRoot } from "../documents/subtreePurgeScope.js";
4
5
  import type { ContainerContentsWorkflowRuntime } from "./runtime.js";
5
6
  type ContainerDocumentLinkApi = Parameters<typeof relinkRemoteDocument>[0]["apiClient"];
6
7
  type ContainerDocumentLinkOperation = Parameters<typeof relinkRemoteDocument>[0]["operation"];
@@ -22,7 +23,9 @@ export declare function initializeDocumentLinksSchema(execSql: ExecSql): Promise
22
23
  export declare function listDocumentLinkedContainerIds(execSql: ExecSql, documentId: string): Promise<ReadonlyArray<string>>;
23
24
  export declare function replaceDocumentLinks(execSql: ExecSql, documentId: string, containerIds: ReadonlyArray<string>): Promise<void>;
24
25
  export declare function relinkRemoteContainerDocument(input: {
26
+ beforeSubmit?: (() => Promise<boolean>) | undefined;
25
27
  documentId: string;
28
+ expectedSubtreeRoot?: SubtreePurgeRoot | undefined;
26
29
  isCurrent?: (() => boolean) | undefined;
27
30
  noteId: string;
28
31
  onFailure?: DocumentLinkSetFailureHandler | undefined;
@@ -33,7 +36,9 @@ export declare function relinkRemoteContainerDocument(input: {
33
36
  targetContainerId: string;
34
37
  }): Promise<RelinkRemoteDocumentResult | null>;
35
38
  export declare function unlinkRemoteContainerDocument(input: {
39
+ beforeSubmit?: (() => Promise<boolean>) | undefined;
36
40
  documentId: string;
41
+ expectedSubtreeRoot?: SubtreePurgeRoot | undefined;
37
42
  isCurrent?: (() => boolean) | undefined;
38
43
  noteId: string;
39
44
  onFailure?: DocumentLinkSetFailureHandler | undefined;
@@ -104,9 +104,11 @@ export async function relinkRemoteContainerDocument(input) {
104
104
  }
105
105
  try {
106
106
  const result = await relinkRemoteDocument({
107
+ beforeSubmit: input.beforeSubmit,
107
108
  apiClient: runtime.apiClient,
108
109
  author,
109
110
  documentId,
111
+ expectedSubtreeRoot: input.expectedSubtreeRoot,
110
112
  execSql,
111
113
  onFailure: input.onFailure,
112
114
  operation,
@@ -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;AAEtB,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,mBAAmB,EAAE,KAAK,CAAC,mBAAmB;YAC9C,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 type { SubtreePurgeRoot } from \"../documents/subtreePurgeScope\";\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 expectedSubtreeRoot?: SubtreePurgeRoot | 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 expectedSubtreeRoot: input.expectedSubtreeRoot,\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 expectedSubtreeRoot?: SubtreePurgeRoot | 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,8 @@
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
+ import type { SubtreePurgeRoot } from "../documents/subtreePurgeScope.js";
5
6
  import type { ContainerContentsWorkflowRuntime } from "./runtime.js";
6
7
  type ContainerDocumentPurgeApi = Pick<ContainerContentsWorkflowRuntime["apiClient"], "getPrincipalPolicyPages" | "getProjectionPolicyHistoryPages" | "getDocumentPurgeProof" | "getDocumentWriterProjectionResult" | "purgeDocument">;
7
8
  type DocumentPurgeResult = DocumentPurgeResponse | {
@@ -13,7 +14,10 @@ interface ContainerDocumentPurgeRuntime extends Pick<ContainerContentsWorkflowRu
13
14
  apiClient: ContainerDocumentPurgeApi;
14
15
  }
15
16
  interface PurgeRemoteContainerDocumentInput {
17
+ beforeSubmit?: (() => Promise<boolean>) | undefined;
16
18
  documentId: string;
19
+ expectedSubtreeRoot?: SubtreePurgeRoot | undefined;
20
+ stillCurrent?: (() => boolean) | undefined;
17
21
  documentKind: StoredDocumentKind;
18
22
  noteId: string;
19
23
  persistence?: DocumentsPersistence | undefined;
@@ -24,7 +28,9 @@ export declare function purgeRemoteContainerDocument(input: PurgeRemoteContainer
24
28
  interface ContainerDocumentLocalPurgeRuntime extends Pick<ContainerContentsWorkflowRuntime, "infra" | "util"> {
25
29
  }
26
30
  export declare function purgeLocalContainerDocument(input: {
31
+ beforeDeleteInTransaction?: Parameters<typeof deletePersistedDocument>[0]["beforeDeleteInTransaction"];
27
32
  noteId: string;
33
+ expectedContainerId?: string | null | undefined;
28
34
  persistence?: DocumentsPersistence | undefined;
29
35
  runtime: ContainerDocumentLocalPurgeRuntime;
30
36
  }): 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
+ expectedSubtreeRoot: input.expectedSubtreeRoot,
58
+ stillCurrent: input.stillCurrent,
56
59
  execSql: runtime.infra.execSql,
57
60
  onVerifiedPurge: async ({ commitPurgeProof }) => {
58
61
  deleted = await commitVerifiedDocumentPurge({
@@ -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 || expectedRecord.documentId !== null) {
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,