@tearleads/client-sdk 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/data/persistence/container-contents/containerContentsPersistenceTypes.d.ts +2 -0
  2. package/data/persistence/container-contents/containerContentsPersistenceTypes.js.map +1 -1
  3. package/data/persistence/container-contents/containerDeletionPersistence.js +3 -0
  4. package/data/persistence/container-contents/containerDeletionPersistence.js.map +1 -1
  5. package/data/persistence/container-contents/documentPurgePlacement.js +15 -0
  6. package/data/persistence/container-contents/documentPurgePlacement.js.map +1 -0
  7. package/data/persistence/documents/documentsPersistence.js +7 -0
  8. package/data/persistence/documents/documentsPersistence.js.map +1 -1
  9. package/data/persistence/documents/internal/documentDeletionPersistence.js +7 -0
  10. package/data/persistence/documents/internal/documentDeletionPersistence.js.map +1 -1
  11. package/data/persistence/documents/orphanBlobPersistence.d.ts +13 -0
  12. package/data/persistence/documents/orphanBlobPersistence.js +2 -0
  13. package/data/persistence/documents/orphanBlobPersistence.js.map +1 -0
  14. package/data/persistence/documents/types.d.ts +4 -1
  15. package/data/persistence/documents/types.js.map +1 -1
  16. package/data/persistence/principalCurrentAcknowledgementPersistence.d.ts +13 -2
  17. package/data/persistence/principalCurrentAcknowledgementPersistence.js +36 -20
  18. package/data/persistence/principalCurrentAcknowledgementPersistence.js.map +1 -1
  19. package/index.d.ts +2 -2
  20. package/index.js +1 -1
  21. package/index.js.map +1 -1
  22. package/internal/api-client/ApiClient.d.ts +6 -2
  23. package/internal/api-client/ApiClient.js +4 -5
  24. package/internal/api-client/ApiClient.js.map +1 -1
  25. package/internal/api-client/apiRequestRuntime.d.ts +1 -2
  26. package/internal/api-client/apiRequestRuntime.js +12 -15
  27. package/internal/api-client/apiRequestRuntime.js.map +1 -1
  28. package/internal/api-client/routes/containers/mutations.js +2 -2
  29. package/internal/api-client/routes/containers/mutations.js.map +1 -1
  30. package/internal/api-client/sessionRefresh.js +14 -6
  31. package/internal/api-client/sessionRefresh.js.map +1 -1
  32. package/internal/api-client/sessionRenewal.js +54 -0
  33. package/internal/api-client/sessionRenewal.js.map +1 -0
  34. package/internal/validators/operation/containerMutations.d.ts +3 -0
  35. package/internal/validators/operation/containerMutations.js +1 -0
  36. package/internal/validators/operation/containerMutations.js.map +1 -1
  37. package/package.json +1 -1
  38. package/stores/documents/documentStore/attachmentHydration.js +2 -2
  39. package/stores/documents/documentStore/attachmentHydration.js.map +1 -1
  40. package/stores/documents/documentStore/attachments.js +7 -5
  41. package/stores/documents/documentStore/attachments.js.map +1 -1
  42. package/stores/documents/documentStore/discard.js +3 -2
  43. package/stores/documents/documentStore/discard.js.map +1 -1
  44. package/stores/documents/documentStore/orphanMaintenance.js +3 -5
  45. package/stores/documents/documentStore/orphanMaintenance.js.map +1 -1
  46. package/stores/documents/documentStore/persistence.js +3 -4
  47. package/stores/documents/documentStore/persistence.js.map +1 -1
  48. package/stores/documents/documentStore/syncDetachedAttachments.js +3 -2
  49. package/stores/documents/documentStore/syncDetachedAttachments.js.map +1 -1
  50. package/stores/documents/documentStore/syncRequest.js +3 -4
  51. package/stores/documents/documentStore/syncRequest.js.map +1 -1
  52. package/workflows/container-contents/container-state/delete.js +2 -0
  53. package/workflows/container-contents/container-state/delete.js.map +1 -1
  54. package/workflows/container-contents/container-state/localPurgeScope.js +49 -0
  55. package/workflows/container-contents/container-state/localPurgeScope.js.map +1 -0
  56. package/workflows/container-contents/container-state/purgeTree.js +42 -7
  57. package/workflows/container-contents/container-state/purgeTree.js.map +1 -1
  58. package/workflows/container-contents/container-state/purgeTreeCollection.js +11 -0
  59. package/workflows/container-contents/container-state/purgeTreeCollection.js.map +1 -1
  60. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js +4 -1
  61. package/workflows/container-contents/container-state/purgeTreeContainerDeletion.js.map +1 -1
  62. package/workflows/container-contents/container-state/purgeTreeScope.js +158 -0
  63. package/workflows/container-contents/container-state/purgeTreeScope.js.map +1 -0
  64. package/workflows/container-contents/container-state/readLocalPurgeScope.js +18 -0
  65. package/workflows/container-contents/container-state/readLocalPurgeScope.js.map +1 -0
  66. package/workflows/container-contents/container-state/remote.js +1 -0
  67. package/workflows/container-contents/container-state/remote.js.map +1 -1
  68. package/workflows/container-contents/container-state/types.js.map +1 -1
  69. package/workflows/container-contents/documentLinks.d.ts +4 -0
  70. package/workflows/container-contents/documentLinks.js +2 -0
  71. package/workflows/container-contents/documentLinks.js.map +1 -1
  72. package/workflows/container-contents/documentPurge.d.ts +6 -1
  73. package/workflows/container-contents/documentPurge.js +12 -5
  74. package/workflows/container-contents/documentPurge.js.map +1 -1
  75. package/workflows/documents/linkSet.d.ts +1 -0
  76. package/workflows/documents/linkSet.js +2 -0
  77. package/workflows/documents/linkSet.js.map +1 -1
  78. package/workflows/documents/linkSetRemote.d.ts +2 -0
  79. package/workflows/documents/linkSetRemote.js +3 -0
  80. package/workflows/documents/linkSetRemote.js.map +1 -1
  81. package/workflows/documents/orphanBlobReclaimState.js +20 -0
  82. package/workflows/documents/orphanBlobReclaimState.js.map +1 -0
  83. package/workflows/documents/orphanBlobReclaims.d.ts +3 -2
  84. package/workflows/documents/orphanBlobReclaims.js +49 -47
  85. package/workflows/documents/orphanBlobReclaims.js.map +1 -1
  86. package/workflows/documents/purge.d.ts +3 -0
  87. package/workflows/documents/purge.js +7 -0
  88. package/workflows/documents/purge.js.map +1 -1
  89. package/workflows/documents/subtreePurgeScope.js +11 -0
  90. package/workflows/documents/subtreePurgeScope.js.map +1 -0
  91. package/workflows/organizations/acknowledgedPrincipalPredecessor.d.ts +39 -0
  92. package/workflows/organizations/acknowledgedPrincipalPredecessor.js +87 -0
  93. package/workflows/organizations/acknowledgedPrincipalPredecessor.js.map +1 -0
  94. package/workflows/organizations/currentOrganizationMutationRetention.js +13 -2
  95. package/workflows/organizations/currentOrganizationMutationRetention.js.map +1 -1
  96. package/workflows/organizations/index.d.ts +1 -0
  97. package/workflows/organizations/index.js +1 -0
  98. package/workflows/organizations/index.js.map +1 -1
  99. package/workflows/organizations/initialPrincipalCurrentPublication.js +3 -2
  100. package/workflows/organizations/initialPrincipalCurrentPublication.js.map +1 -1
  101. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js +63 -0
  102. package/workflows/organizations/reconcileAcknowledgedPrincipalCurrent.js.map +1 -0
  103. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.d.ts +3 -0
  104. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js +33 -55
  105. package/workflows/organizations/retainAcknowledgedPrincipalCurrents.js.map +1 -1
  106. package/workflows/organizations/runtimeCurrentGroupMutation.js +13 -2
  107. package/workflows/organizations/runtimeCurrentGroupMutation.js.map +1 -1
  108. package/workflows/organizations/runtimeCurrentOrganizationMutation.js +2 -2
  109. package/workflows/organizations/runtimeCurrentOrganizationMutation.js.map +1 -1
@@ -1,17 +1,13 @@
1
1
  import { errorMessage } from "../../data/errorMessage.js";
2
- import { acknowledgeDocumentOrphanBlobReclaim, deleteOrphanedDocumentSideRows, isDocumentBlobStorageKeyReferenced, listDocumentOrphanBlobReclaims, } from "../../data/persistence/documents/internal/orphanSideRows.js";
3
2
  import { resolveCanonicalExecSql, runOncePerConnection, runSerializedSqlMutation, } from "../../data/sqlite/sqlSchema.js";
4
3
  import { runSerializedDocumentBlobMutation } from "./blobMutationLock.js";
4
+ import { orphanBlobReclaimState, } from "./orphanBlobReclaimState.js";
5
5
  import { defaultDocumentsPersistence } from "./persistence.js";
6
- const reclaimByExecSql = new WeakMap();
7
- const reclaimRerunByExecSql = new WeakSet();
8
- const deferredStorageKeysByExecSql = new WeakMap();
9
6
  const ORPHAN_BLOB_RECLAIM_BATCH_SIZE = 16;
10
7
  const ORPHAN_BLOB_RECLAIM_TIME_BUDGET_MS = 250;
11
8
  const ORPHAN_BLOB_RECLAIM_RETRY_DELAY_MS = 30_000;
12
9
  const ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE = 3;
13
10
  const ORPHAN_BLOB_RECLAIM_YIELD_MS = 16;
14
- const DOCUMENT_ORPHAN_SWEEP_ONCE_KEY = "sweep:document-orphans";
15
11
  function clearExpiredDeferredStorageKeys(deferredStorageKeys, now) {
16
12
  for (const [storageKey, retryAt] of deferredStorageKeys) {
17
13
  if (retryAt <= now)
@@ -23,38 +19,36 @@ function waitForMaintenanceYield() {
23
19
  setTimeout(resolve, ORPHAN_BLOB_RECLAIM_YIELD_MS);
24
20
  });
25
21
  }
26
- async function sweepAgedDocumentOrphans(execSql) {
22
+ async function sweepAgedDocumentOrphans(execSql, persistence, state) {
27
23
  let swept = false;
28
- await runOncePerConnection(execSql, DOCUMENT_ORPHAN_SWEEP_ONCE_KEY, async () => {
24
+ await runOncePerConnection(execSql, state.sweepKey, async () => {
29
25
  swept = true;
30
- while (await deleteOrphanedDocumentSideRows(execSql)) {
26
+ while (await persistence.orphanBlobs.sweep(execSql)) {
31
27
  await waitForMaintenanceYield();
32
28
  }
33
29
  });
34
30
  return swept;
35
31
  }
36
- async function reclaimQueuedBlobs(runtime) {
32
+ async function reclaimQueuedBlobs(runtime, persistence, state) {
37
33
  const execSql = runtime.infra.execSql;
38
- await defaultDocumentsPersistence.ensureSchema(execSql);
34
+ await persistence.ensureSchema(execSql);
39
35
  let shouldContinue = false;
40
36
  const startedAt = Date.now();
41
- const canonicalExecSql = resolveCanonicalExecSql(execSql);
42
- const deferredStorageKeys = deferredStorageKeysByExecSql.get(canonicalExecSql) ??
43
- new Map();
44
- deferredStorageKeysByExecSql.set(canonicalExecSql, deferredStorageKeys);
37
+ const { deferredStorageKeys } = state;
45
38
  const failedStorageKeys = [];
46
39
  let warmedBlobStore = false;
47
40
  let sweptAgedOrphans = false;
48
41
  reclaimQueue: while (true) {
49
42
  clearExpiredDeferredStorageKeys(deferredStorageKeys, Date.now());
50
- const queuedStorageKeys = await listDocumentOrphanBlobReclaims(execSql, ORPHAN_BLOB_RECLAIM_BATCH_SIZE + deferredStorageKeys.size);
43
+ const queuedStorageKeys = await persistence.orphanBlobs.list(execSql, ORPHAN_BLOB_RECLAIM_BATCH_SIZE + deferredStorageKeys.size);
51
44
  const storageKeys = queuedStorageKeys
52
45
  .filter((storageKey) => !deferredStorageKeys.has(storageKey))
53
46
  .slice(0, ORPHAN_BLOB_RECLAIM_BATCH_SIZE);
54
47
  if (storageKeys.length === 0) {
55
48
  // Explicit delete paths get priority. Once their queue is drained, sweep
56
49
  // crash residue once per connection and immediately drain what it queues.
57
- if (!sweptAgedOrphans && (await sweepAgedDocumentOrphans(execSql))) {
50
+ if (!sweptAgedOrphans &&
51
+ (await sweepAgedDocumentOrphans(execSql, persistence, state))) {
58
52
  sweptAgedOrphans = true;
59
53
  continue;
60
54
  }
@@ -72,8 +66,8 @@ async function reclaimQueuedBlobs(runtime) {
72
66
  for (const storageKey of storageKeys) {
73
67
  await runSerializedDocumentBlobMutation(execSql, storageKey, async () => {
74
68
  const shouldDelete = await runSerializedSqlMutation(execSql, async (lockedExecSql) => {
75
- if (await isDocumentBlobStorageKeyReferenced(lockedExecSql, storageKey)) {
76
- await acknowledgeDocumentOrphanBlobReclaim(lockedExecSql, storageKey);
69
+ if (await persistence.orphanBlobs.isReferenced(lockedExecSql, storageKey)) {
70
+ await persistence.orphanBlobs.acknowledge(lockedExecSql, storageKey);
77
71
  return false;
78
72
  }
79
73
  return true;
@@ -82,7 +76,7 @@ async function reclaimQueuedBlobs(runtime) {
82
76
  return;
83
77
  try {
84
78
  await runtime.infra.blobStore.deleteBytes(storageKey);
85
- await runSerializedSqlMutation(execSql, (lockedExecSql) => acknowledgeDocumentOrphanBlobReclaim(lockedExecSql, storageKey));
79
+ await runSerializedSqlMutation(execSql, (lockedExecSql) => persistence.orphanBlobs.acknowledge(lockedExecSql, storageKey));
86
80
  }
87
81
  catch {
88
82
  failedStorageKeys.push(storageKey);
@@ -95,9 +89,6 @@ async function reclaimQueuedBlobs(runtime) {
95
89
  }
96
90
  }
97
91
  }
98
- if (deferredStorageKeys.size === 0) {
99
- deferredStorageKeysByExecSql.delete(canonicalExecSql);
100
- }
101
92
  if (failedStorageKeys.length > 0) {
102
93
  const sample = failedStorageKeys
103
94
  .slice(0, ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE)
@@ -106,36 +97,47 @@ async function reclaimQueuedBlobs(runtime) {
106
97
  }
107
98
  return shouldContinue;
108
99
  }
109
- /** Sweep orphan rows and reclaim their queued local attachment bytes. */
110
- export function reclaimDocumentOrphanBlobs(runtime) {
100
+ function runDocumentOrphanReclaims(runtime, persistence = defaultDocumentsPersistence) {
111
101
  if (runtime.infra.dbStatus !== "ready") {
112
102
  return Promise.resolve();
113
103
  }
114
- const execSql = resolveCanonicalExecSql(runtime.infra.execSql);
115
- const existing = reclaimByExecSql.get(execSql);
116
- if (existing) {
117
- reclaimRerunByExecSql.add(execSql);
118
- return existing;
104
+ const state = orphanBlobReclaimState(persistence.orphanBlobs, resolveCanonicalExecSql(runtime.infra.execSql));
105
+ if (state.running) {
106
+ state.rerun = true;
107
+ return state.running;
119
108
  }
120
- let shouldContinue = false;
121
- const reclaim = reclaimQueuedBlobs(runtime)
122
- .then((hasMore) => {
123
- shouldContinue = hasMore;
124
- })
125
- .catch((error) => {
126
- const message = errorMessage(error);
127
- runtime.util.log(`Documents: orphan maintenance failed: ${message}`);
128
- })
129
- .finally(() => {
130
- reclaimByExecSql.delete(execSql);
131
- const wasRerunRequested = reclaimRerunByExecSql.delete(execSql);
132
- if (shouldContinue || wasRerunRequested) {
133
- setTimeout(() => {
134
- void reclaimDocumentOrphanBlobs(runtime);
135
- }, ORPHAN_BLOB_RECLAIM_YIELD_MS);
109
+ const reclaim = (async () => {
110
+ try {
111
+ let hasMore;
112
+ do {
113
+ state.rerun = false;
114
+ hasMore = false;
115
+ try {
116
+ hasMore = await reclaimQueuedBlobs(runtime, persistence, state);
117
+ }
118
+ catch (error) {
119
+ const message = errorMessage(error);
120
+ runtime.util.log(`Documents: orphan maintenance failed: ${message}`);
121
+ }
122
+ if (hasMore || state.rerun)
123
+ await waitForMaintenanceYield();
124
+ } while (hasMore || state.rerun);
136
125
  }
137
- });
138
- reclaimByExecSql.set(execSql, reclaim);
126
+ finally {
127
+ state.running = undefined;
128
+ state.rerun = false;
129
+ }
130
+ })();
131
+ state.running = reclaim;
139
132
  return reclaim;
140
133
  }
134
+ /** Await all requested batches; maintenance failures never fail committed writes. */
135
+ export async function reclaimDocumentOrphanBlobs(runtime, persistence = defaultDocumentsPersistence) {
136
+ try {
137
+ await runDocumentOrphanReclaims(runtime, persistence);
138
+ }
139
+ catch (error) {
140
+ runtime.util.log(`Documents: orphan maintenance failed: ${errorMessage(error)}`);
141
+ }
142
+ }
141
143
  //# sourceMappingURL=orphanBlobReclaims.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"orphanBlobReclaims.js","sourceRoot":"","sources":["orphanBlobReclaims.ts"],"names":[],"mappings":"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","sourcesContent":["import { errorMessage } from \"../../data/errorMessage\";\nimport {\n acknowledgeDocumentOrphanBlobReclaim,\n deleteOrphanedDocumentSideRows,\n isDocumentBlobStorageKeyReferenced,\n listDocumentOrphanBlobReclaims,\n} from \"../../data/persistence/documents/internal/orphanSideRows\";\nimport {\n type ExecSql,\n resolveCanonicalExecSql,\n runOncePerConnection,\n runSerializedSqlMutation,\n} from \"../../data/sqlite/sqlSchema\";\nimport { runSerializedDocumentBlobMutation } from \"./blobMutationLock\";\nimport { defaultDocumentsPersistence } from \"./persistence\";\nimport type { DocumentsWorkflowRuntimeGroups } from \"./runtime\";\n\nconst reclaimByExecSql = new WeakMap<ExecSql, Promise<void>>();\nconst reclaimRerunByExecSql = new WeakSet<ExecSql>();\nconst deferredStorageKeysByExecSql = new WeakMap<\n ExecSql,\n Map<string, number>\n>();\nconst ORPHAN_BLOB_RECLAIM_BATCH_SIZE = 16;\nconst ORPHAN_BLOB_RECLAIM_TIME_BUDGET_MS = 250;\nconst ORPHAN_BLOB_RECLAIM_RETRY_DELAY_MS = 30_000;\nconst ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE = 3;\nconst ORPHAN_BLOB_RECLAIM_YIELD_MS = 16;\nconst DOCUMENT_ORPHAN_SWEEP_ONCE_KEY = \"sweep:document-orphans\";\n\ntype DocumentOrphanBlobReclaimRuntime = Pick<\n DocumentsWorkflowRuntimeGroups,\n \"infra\" | \"util\"\n>;\n\nfunction clearExpiredDeferredStorageKeys(\n deferredStorageKeys: Map<string, number>,\n now: number,\n): void {\n for (const [storageKey, retryAt] of deferredStorageKeys) {\n if (retryAt <= now) deferredStorageKeys.delete(storageKey);\n }\n}\n\nfunction waitForMaintenanceYield(): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ORPHAN_BLOB_RECLAIM_YIELD_MS);\n });\n}\n\nasync function sweepAgedDocumentOrphans(execSql: ExecSql): Promise<boolean> {\n let swept = false;\n await runOncePerConnection(\n execSql,\n DOCUMENT_ORPHAN_SWEEP_ONCE_KEY,\n async () => {\n swept = true;\n while (await deleteOrphanedDocumentSideRows(execSql)) {\n await waitForMaintenanceYield();\n }\n },\n );\n return swept;\n}\n\nasync function reclaimQueuedBlobs(\n runtime: DocumentOrphanBlobReclaimRuntime,\n): Promise<boolean> {\n const execSql = runtime.infra.execSql;\n await defaultDocumentsPersistence.ensureSchema(execSql);\n let shouldContinue = false;\n\n const startedAt = Date.now();\n const canonicalExecSql = resolveCanonicalExecSql(execSql);\n const deferredStorageKeys =\n deferredStorageKeysByExecSql.get(canonicalExecSql) ??\n new Map<string, number>();\n deferredStorageKeysByExecSql.set(canonicalExecSql, deferredStorageKeys);\n const failedStorageKeys: string[] = [];\n let warmedBlobStore = false;\n let sweptAgedOrphans = false;\n\n reclaimQueue: while (true) {\n clearExpiredDeferredStorageKeys(deferredStorageKeys, Date.now());\n const queuedStorageKeys = await listDocumentOrphanBlobReclaims(\n execSql,\n ORPHAN_BLOB_RECLAIM_BATCH_SIZE + deferredStorageKeys.size,\n );\n const storageKeys = queuedStorageKeys\n .filter((storageKey) => !deferredStorageKeys.has(storageKey))\n .slice(0, ORPHAN_BLOB_RECLAIM_BATCH_SIZE);\n if (storageKeys.length === 0) {\n // Explicit delete paths get priority. Once their queue is drained, sweep\n // crash residue once per connection and immediately drain what it queues.\n if (!sweptAgedOrphans && (await sweepAgedDocumentOrphans(execSql))) {\n sweptAgedOrphans = true;\n continue;\n }\n break;\n }\n\n const firstStorageKey = storageKeys[0];\n if (!warmedBlobStore && firstStorageKey) {\n // Initialize lazy keyring/OPFS-backed stores before taking any blob-key\n // lock. A failed preview is harmless: deletion can still reclaim the key.\n await runtime.infra.blobStore\n .openByteSource(firstStorageKey)\n .catch(() => undefined);\n warmedBlobStore = true;\n }\n\n for (const storageKey of storageKeys) {\n await runSerializedDocumentBlobMutation(execSql, storageKey, async () => {\n const shouldDelete = await runSerializedSqlMutation(\n execSql,\n async (lockedExecSql) => {\n if (\n await isDocumentBlobStorageKeyReferenced(\n lockedExecSql,\n storageKey,\n )\n ) {\n await acknowledgeDocumentOrphanBlobReclaim(\n lockedExecSql,\n storageKey,\n );\n return false;\n }\n return true;\n },\n );\n if (!shouldDelete) return;\n try {\n await runtime.infra.blobStore.deleteBytes(storageKey);\n await runSerializedSqlMutation(execSql, (lockedExecSql) =>\n acknowledgeDocumentOrphanBlobReclaim(lockedExecSql, storageKey),\n );\n } catch {\n failedStorageKeys.push(storageKey);\n deferredStorageKeys.set(\n storageKey,\n Date.now() + ORPHAN_BLOB_RECLAIM_RETRY_DELAY_MS,\n );\n }\n });\n if (Date.now() - startedAt >= ORPHAN_BLOB_RECLAIM_TIME_BUDGET_MS) {\n shouldContinue = true;\n break reclaimQueue;\n }\n }\n }\n\n if (deferredStorageKeys.size === 0) {\n deferredStorageKeysByExecSql.delete(canonicalExecSql);\n }\n if (failedStorageKeys.length > 0) {\n const sample = failedStorageKeys\n .slice(0, ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE)\n .join(\", \");\n runtime.util.log(\n `Documents: orphan maintenance deferred ${failedStorageKeys.length} blob(s): ${sample}`,\n );\n }\n return shouldContinue;\n}\n\n/** Sweep orphan rows and reclaim their queued local attachment bytes. */\nexport function reclaimDocumentOrphanBlobs(\n runtime: DocumentOrphanBlobReclaimRuntime,\n): Promise<void> {\n if (runtime.infra.dbStatus !== \"ready\") {\n return Promise.resolve();\n }\n const execSql = resolveCanonicalExecSql(runtime.infra.execSql);\n const existing = reclaimByExecSql.get(execSql);\n if (existing) {\n reclaimRerunByExecSql.add(execSql);\n return existing;\n }\n\n let shouldContinue = false;\n const reclaim = reclaimQueuedBlobs(runtime)\n .then((hasMore) => {\n shouldContinue = hasMore;\n })\n .catch((error: unknown) => {\n const message = errorMessage(error);\n runtime.util.log(`Documents: orphan maintenance failed: ${message}`);\n })\n .finally(() => {\n reclaimByExecSql.delete(execSql);\n const wasRerunRequested = reclaimRerunByExecSql.delete(execSql);\n if (shouldContinue || wasRerunRequested) {\n setTimeout(() => {\n void reclaimDocumentOrphanBlobs(runtime);\n }, ORPHAN_BLOB_RECLAIM_YIELD_MS);\n }\n });\n reclaimByExecSql.set(execSql, reclaim);\n return reclaim;\n}\n"]}
1
+ {"version":3,"file":"orphanBlobReclaims.js","sourceRoot":"","sources":["orphanBlobReclaims.ts"],"names":[],"mappings":"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","sourcesContent":["import { errorMessage } from \"../../data/errorMessage\";\nimport type { DocumentsPersistence } from \"../../data/persistence/documents/types\";\nimport {\n type ExecSql,\n resolveCanonicalExecSql,\n runOncePerConnection,\n runSerializedSqlMutation,\n} from \"../../data/sqlite/sqlSchema\";\nimport { runSerializedDocumentBlobMutation } from \"./blobMutationLock\";\nimport {\n type OrphanBlobReclaimState,\n orphanBlobReclaimState,\n} from \"./orphanBlobReclaimState\";\nimport { defaultDocumentsPersistence } from \"./persistence\";\nimport type { DocumentsWorkflowRuntimeGroups } from \"./runtime\";\n\nconst ORPHAN_BLOB_RECLAIM_BATCH_SIZE = 16;\nconst ORPHAN_BLOB_RECLAIM_TIME_BUDGET_MS = 250;\nconst ORPHAN_BLOB_RECLAIM_RETRY_DELAY_MS = 30_000;\nconst ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE = 3;\nconst ORPHAN_BLOB_RECLAIM_YIELD_MS = 16;\n\ntype DocumentOrphanBlobReclaimRuntime = Pick<\n DocumentsWorkflowRuntimeGroups,\n \"infra\" | \"util\"\n>;\n\nfunction clearExpiredDeferredStorageKeys(\n deferredStorageKeys: Map<string, number>,\n now: number,\n): void {\n for (const [storageKey, retryAt] of deferredStorageKeys) {\n if (retryAt <= now) deferredStorageKeys.delete(storageKey);\n }\n}\n\nfunction waitForMaintenanceYield(): Promise<void> {\n return new Promise((resolve) => {\n setTimeout(resolve, ORPHAN_BLOB_RECLAIM_YIELD_MS);\n });\n}\n\nasync function sweepAgedDocumentOrphans(\n execSql: ExecSql,\n persistence: DocumentsPersistence,\n state: OrphanBlobReclaimState,\n): Promise<boolean> {\n let swept = false;\n await runOncePerConnection(execSql, state.sweepKey, async () => {\n swept = true;\n while (await persistence.orphanBlobs.sweep(execSql)) {\n await waitForMaintenanceYield();\n }\n });\n return swept;\n}\n\nasync function reclaimQueuedBlobs(\n runtime: DocumentOrphanBlobReclaimRuntime,\n persistence: DocumentsPersistence,\n state: OrphanBlobReclaimState,\n): Promise<boolean> {\n const execSql = runtime.infra.execSql;\n await persistence.ensureSchema(execSql);\n let shouldContinue = false;\n\n const startedAt = Date.now();\n const { deferredStorageKeys } = state;\n const failedStorageKeys: string[] = [];\n let warmedBlobStore = false;\n let sweptAgedOrphans = false;\n\n reclaimQueue: while (true) {\n clearExpiredDeferredStorageKeys(deferredStorageKeys, Date.now());\n const queuedStorageKeys = await persistence.orphanBlobs.list(\n execSql,\n ORPHAN_BLOB_RECLAIM_BATCH_SIZE + deferredStorageKeys.size,\n );\n const storageKeys = queuedStorageKeys\n .filter((storageKey) => !deferredStorageKeys.has(storageKey))\n .slice(0, ORPHAN_BLOB_RECLAIM_BATCH_SIZE);\n if (storageKeys.length === 0) {\n // Explicit delete paths get priority. Once their queue is drained, sweep\n // crash residue once per connection and immediately drain what it queues.\n if (\n !sweptAgedOrphans &&\n (await sweepAgedDocumentOrphans(execSql, persistence, state))\n ) {\n sweptAgedOrphans = true;\n continue;\n }\n break;\n }\n\n const firstStorageKey = storageKeys[0];\n if (!warmedBlobStore && firstStorageKey) {\n // Initialize lazy keyring/OPFS-backed stores before taking any blob-key\n // lock. A failed preview is harmless: deletion can still reclaim the key.\n await runtime.infra.blobStore\n .openByteSource(firstStorageKey)\n .catch(() => undefined);\n warmedBlobStore = true;\n }\n\n for (const storageKey of storageKeys) {\n await runSerializedDocumentBlobMutation(execSql, storageKey, async () => {\n const shouldDelete = await runSerializedSqlMutation(\n execSql,\n async (lockedExecSql) => {\n if (\n await persistence.orphanBlobs.isReferenced(\n lockedExecSql,\n storageKey,\n )\n ) {\n await persistence.orphanBlobs.acknowledge(\n lockedExecSql,\n storageKey,\n );\n return false;\n }\n return true;\n },\n );\n if (!shouldDelete) return;\n try {\n await runtime.infra.blobStore.deleteBytes(storageKey);\n await runSerializedSqlMutation(execSql, (lockedExecSql) =>\n persistence.orphanBlobs.acknowledge(lockedExecSql, storageKey),\n );\n } catch {\n failedStorageKeys.push(storageKey);\n deferredStorageKeys.set(\n storageKey,\n Date.now() + ORPHAN_BLOB_RECLAIM_RETRY_DELAY_MS,\n );\n }\n });\n if (Date.now() - startedAt >= ORPHAN_BLOB_RECLAIM_TIME_BUDGET_MS) {\n shouldContinue = true;\n break reclaimQueue;\n }\n }\n }\n\n if (failedStorageKeys.length > 0) {\n const sample = failedStorageKeys\n .slice(0, ORPHAN_BLOB_RECLAIM_LOG_SAMPLE_SIZE)\n .join(\", \");\n runtime.util.log(\n `Documents: orphan maintenance deferred ${failedStorageKeys.length} blob(s): ${sample}`,\n );\n }\n return shouldContinue;\n}\n\nfunction runDocumentOrphanReclaims(\n runtime: DocumentOrphanBlobReclaimRuntime,\n persistence: DocumentsPersistence = defaultDocumentsPersistence,\n): Promise<void> {\n if (runtime.infra.dbStatus !== \"ready\") {\n return Promise.resolve();\n }\n const state = orphanBlobReclaimState(\n persistence.orphanBlobs,\n resolveCanonicalExecSql(runtime.infra.execSql),\n );\n if (state.running) {\n state.rerun = true;\n return state.running;\n }\n\n const reclaim = (async () => {\n try {\n let hasMore: boolean;\n do {\n state.rerun = false;\n hasMore = false;\n try {\n hasMore = await reclaimQueuedBlobs(runtime, persistence, state);\n } catch (error) {\n const message = errorMessage(error);\n runtime.util.log(`Documents: orphan maintenance failed: ${message}`);\n }\n if (hasMore || state.rerun) await waitForMaintenanceYield();\n } while (hasMore || state.rerun);\n } finally {\n state.running = undefined;\n state.rerun = false;\n }\n })();\n state.running = reclaim;\n return reclaim;\n}\n\n/** Await all requested batches; maintenance failures never fail committed writes. */\nexport async function reclaimDocumentOrphanBlobs(\n runtime: DocumentOrphanBlobReclaimRuntime,\n persistence: DocumentsPersistence = defaultDocumentsPersistence,\n): Promise<void> {\n try {\n await runDocumentOrphanReclaims(runtime, persistence);\n } catch (error) {\n runtime.util.log(\n `Documents: orphan maintenance failed: ${errorMessage(error)}`,\n );\n }\n}\n"]}
@@ -16,6 +16,7 @@ interface DocumentPurgeApi {
16
16
  }
17
17
  export declare function buildDocumentPurgeRequest(input: {
18
18
  readonly author: DocumentCreateAuthor;
19
+ readonly expectedSubtreeRootId?: string | undefined;
19
20
  readonly eventId?: string | undefined;
20
21
  readonly signedAt?: string | undefined;
21
22
  readonly writerProjection: DocumentWriterProjectionResponse;
@@ -35,9 +36,11 @@ export declare function createVerifiedRemoteDocumentDeletionHandler(input: {
35
36
  readonly documentId: string;
36
37
  }) => Promise<void>;
37
38
  export declare function purgeRemoteDocument(input: {
39
+ readonly beforeSubmit?: (() => Promise<boolean>) | undefined;
38
40
  readonly apiClient: DocumentPurgeApi;
39
41
  readonly author: DocumentCreateAuthor;
40
42
  readonly documentId: string;
43
+ readonly expectedSubtreeRootId?: string | undefined;
41
44
  readonly execSql: ExecSql;
42
45
  readonly onVerifiedPurge: (input: {
43
46
  readonly commitPurgeProof: (transactionExecSql: ExecSql) => Promise<void>;
@@ -6,6 +6,7 @@ import { readCanonicalJson, readCanonicalRecord, } from "../../data/keyingCanoni
6
6
  import { verifyDocumentPurgeProof, verifyDocumentPurgeProofBaseline, verifyDocumentWriterProjection, } from "../../data/keyingProjectionVerification.js";
7
7
  import { ProjectionDependencyUnavailableError } from "../../data/keyingProjectionVerification/dependencyUnavailable.js";
8
8
  import { documentContainerProjections } from "../../data/keyingProjectionVerification/documentContainerProjections.js";
9
+ import { assertSubtreePurgePath } from "./subtreePurgeScope.js";
9
10
  const REMOTE_DOCUMENT_ALREADY_PURGED = Symbol("remoteDocumentAlreadyPurged");
10
11
  async function loadAuthenticatedDocumentPurgeProof(input) {
11
12
  if (!input.apiClient.getDocumentPurgeProof) {
@@ -69,6 +70,7 @@ export async function buildDocumentPurgeRequest(input) {
69
70
  if (!authorizingProjection || !containerId || !containerManifestHash) {
70
71
  throw new Error("Document purge authorization path is unavailable");
71
72
  }
73
+ assertSubtreePurgePath(authorizingProjection.path, input.expectedSubtreeRootId);
72
74
  const authorizingContainerManifestHashes = authorizingProjection.path.map((bundle) => bundle.manifestHash);
73
75
  const body = {
74
76
  authorizingContainerManifestHashes,
@@ -172,8 +174,13 @@ export async function purgeRemoteDocument(input) {
172
174
  });
173
175
  const request = await buildDocumentPurgeRequest({
174
176
  author: input.author,
177
+ expectedSubtreeRootId: input.expectedSubtreeRootId,
175
178
  writerProjection,
176
179
  });
180
+ if (input.beforeSubmit && !(await input.beforeSubmit()))
181
+ return null;
182
+ if (input.stillCurrent?.() === false)
183
+ return null;
177
184
  const response = await input.apiClient.purgeDocument(input.documentId, request, {
178
185
  expectedPaymentRequiredOrganizationId: input.author.organizationId,
179
186
  });
@@ -1 +1 @@
1
- {"version":3,"file":"purge.js","sourceRoot":"","sources":["purge.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n computeAccessEventBodyHash,\n type DocumentPurgeAccessEventBody,\n KeyingVerificationError,\n signAccessEvent,\n type UnsignedAccessEvent,\n} from \"@tearleads/crypto\";\nimport type { DocumentPurgeRequest } from \"@tearleads/validators/request\";\nimport {\n DOCUMENT_NOT_FOUND_ERROR_CODE,\n type DocumentPurgeProofResponse,\n type DocumentPurgeResponse,\n type DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport {\n containerPathRefs,\n readLinkedContainerIdsFromDocumentManifest,\n} from \"../../data/documents/shared/projection\";\nimport {\n assertDocumentManifestBundleConsistent,\n readManifestContainerId,\n} from \"../../data/documents/shared/readers\";\nimport type {\n DocumentCreateAuthor,\n DocumentSyncApi,\n} from \"../../data/documents/shared/types\";\nimport {\n readCanonicalJson,\n readCanonicalRecord,\n} from \"../../data/keyingCanonicalJson\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../data/keyingProjectionVerification\";\nimport {\n verifyDocumentPurgeProof,\n verifyDocumentPurgeProofBaseline,\n verifyDocumentWriterProjection,\n} from \"../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { documentContainerProjections } from \"../../data/keyingProjectionVerification/documentContainerProjections\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\ninterface DocumentPurgeApi {\n getDocumentPurgeProof(\n documentId: string,\n options?: {\n readonly documentCheckpointManifestHash?: string;\n },\n requestOptions?: {\n readonly expectedPaymentRequiredOrganizationId?: string;\n },\n ): Promise<DocumentPurgeProofResponse | null>;\n getDocumentWriterProjectionResult: NonNullable<\n DocumentSyncApi[\"getDocumentWriterProjectionResult\"]\n >;\n purgeDocument(\n documentId: string,\n request: DocumentPurgeRequest,\n options?: {\n readonly expectedPaymentRequiredOrganizationId?: string;\n },\n ): Promise<DocumentPurgeResponse | null>;\n}\n\nconst REMOTE_DOCUMENT_ALREADY_PURGED = Symbol(\"remoteDocumentAlreadyPurged\");\n\nasync function loadAuthenticatedDocumentPurgeProof(input: {\n readonly apiClient: Pick<DocumentSyncApi, \"getDocumentPurgeProof\">;\n readonly documentId: string;\n readonly execSql: ExecSql;\n readonly expectedOrganizationId: string;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}): Promise<DocumentPurgeProofResponse | null> {\n if (!input.apiClient.getDocumentPurgeProof) {\n throw new ProjectionDependencyUnavailableError(\n \"Remote document deletion is missing a purge-proof endpoint\",\n );\n }\n const initialProof = await input.apiClient.getDocumentPurgeProof(\n input.documentId,\n undefined,\n {\n expectedPaymentRequiredOrganizationId: input.expectedOrganizationId,\n },\n );\n if (!initialProof) {\n // ApiClient returns null for transport, HTTP and response-shape failures.\n // Even after a deletion hint, absence cannot prove signed-data tampering.\n // Keep local data and defer deletion until an actual proof verifies.\n return null;\n }\n await verifyDocumentPurgeProofBaseline({\n execSql: input.execSql,\n expectedDocumentId: input.documentId,\n expectedOrganizationId: input.expectedOrganizationId,\n proof: initialProof,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n // The current API returns complete history to recorded readers. Otherwise\n // its terminal snapshot needs an exact local pin; a floor refetch cannot\n // grant missing observation authority.\n return initialProof;\n}\n\nasync function resolveDocumentPurgeWriterProjection(input: {\n readonly apiClient: DocumentPurgeApi;\n readonly documentId: string;\n}): Promise<\n | DocumentWriterProjectionResponse\n | typeof REMOTE_DOCUMENT_ALREADY_PURGED\n | null\n> {\n const result = await input.apiClient.getDocumentWriterProjectionResult(\n input.documentId,\n { reportErrors: false },\n );\n if (result.ok) {\n return result.data;\n }\n if (result.status === 404 && result.code === DOCUMENT_NOT_FOUND_ERROR_CODE) {\n return REMOTE_DOCUMENT_ALREADY_PURGED;\n }\n result.report();\n return null;\n}\n\nexport async function buildDocumentPurgeRequest(input: {\n readonly author: DocumentCreateAuthor;\n readonly eventId?: string | undefined;\n readonly signedAt?: string | undefined;\n readonly writerProjection: DocumentWriterProjectionResponse;\n}): Promise<DocumentPurgeRequest> {\n const identity = await assertDocumentManifestBundleConsistent({\n bundle: input.writerProjection.documentManifest,\n label: \"Document purge predecessor\",\n });\n if (identity.documentId !== input.writerProjection.documentId) {\n throw new Error(\"Document purge projection identity mismatch\");\n }\n if (identity.organizationId !== input.author.organizationId) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Document purge author organization mismatch\",\n );\n }\n const linkedContainerIds = readLinkedContainerIdsFromDocumentManifest(\n input.writerProjection,\n );\n if (linkedContainerIds.length !== 1) {\n throw new Error(\"Document purge requires exactly one linked container\");\n }\n const [containerId] = linkedContainerIds;\n const authorizingProjection = documentContainerProjections(\n input.writerProjection,\n ).find((projection) => {\n const leaf = projection.path.at(-1);\n return leaf && readManifestContainerId(leaf) === containerId;\n });\n const containerManifestHash =\n authorizingProjection?.path.at(-1)?.manifestHash;\n if (!authorizingProjection || !containerId || !containerManifestHash) {\n throw new Error(\"Document purge authorization path is unavailable\");\n }\n const authorizingContainerManifestHashes = authorizingProjection.path.map(\n (bundle) => bundle.manifestHash,\n );\n const body: DocumentPurgeAccessEventBody = {\n authorizingContainerManifestHashes,\n containerId,\n containerManifestHash,\n documentManifestHash: input.writerProjection.documentManifest.manifestHash,\n eventType: \"document.purge\",\n };\n const unsignedEvent: UnsignedAccessEvent = {\n version: 1,\n eventId: input.eventId ?? crypto.randomUUID(),\n eventType: \"document.purge\",\n objectKind: \"document\",\n objectId: input.writerProjection.documentId,\n organizationId: input.author.organizationId,\n previousManifestHash: input.writerProjection.documentManifest.manifestHash,\n dependencyManifestHashes: authorizingContainerManifestHashes,\n bodyHash: await computeAccessEventBodyHash(\n readCanonicalJson(body, \"Document purge body\"),\n ),\n signerUserId: input.author.signerUserId,\n signerDeviceId: input.author.signerDeviceId,\n signerKeyFingerprint: input.author.signerKeyFingerprint,\n signedAt: input.signedAt ?? new Date().toISOString(),\n };\n const event = await signAccessEvent(\n unsignedEvent,\n input.author.signerPrivateKey,\n );\n\n return {\n authorizingContainerPathRefs: containerPathRefs(authorizingProjection.path),\n body: readCanonicalRecord(body, \"Document purge body\"),\n event: readCanonicalRecord(event, \"Document purge event\"),\n };\n}\n\nexport function createVerifiedRemoteDocumentDeletionHandler(input: {\n readonly apiClient: Pick<DocumentSyncApi, \"getDocumentPurgeProof\">;\n readonly execSql: ExecSql;\n readonly expectedOrganizationId: string;\n readonly onVerifiedDeletion: (input: {\n readonly commitPurgeProof: (transactionExecSql: ExecSql) => Promise<void>;\n readonly documentId: string;\n }) => Promise<void> | void;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}): (deleted: { readonly documentId: string }) => Promise<void> {\n return async ({ documentId }) => {\n const proof = await loadAuthenticatedDocumentPurgeProof({\n apiClient: input.apiClient,\n documentId,\n execSql: input.execSql,\n expectedOrganizationId: input.expectedOrganizationId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n if (!proof) {\n throw new ProjectionDependencyUnavailableError(\n \"Remote document deletion is missing its signed purge proof\",\n );\n }\n const verified = await verifyDocumentPurgeProof({\n execSql: input.execSql,\n expectedDocumentId: documentId,\n expectedOrganizationId: input.expectedOrganizationId,\n proof,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n await input.onVerifiedDeletion({\n commitPurgeProof: verified.commitCheckpoints,\n documentId,\n });\n };\n}\n\nexport async function purgeRemoteDocument(input: {\n readonly apiClient: DocumentPurgeApi;\n readonly author: DocumentCreateAuthor;\n readonly documentId: string;\n readonly execSql: ExecSql;\n readonly onVerifiedPurge: (input: {\n readonly commitPurgeProof: (transactionExecSql: ExecSql) => Promise<void>;\n }) => Promise<void> | void;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}): Promise<DocumentPurgeResponse | null> {\n const writerProjection = await resolveDocumentPurgeWriterProjection({\n apiClient: input.apiClient,\n documentId: input.documentId,\n });\n if (!writerProjection) {\n return null;\n }\n if (writerProjection === REMOTE_DOCUMENT_ALREADY_PURGED) {\n const proof = await loadAuthenticatedDocumentPurgeProof({\n apiClient: input.apiClient,\n documentId: input.documentId,\n execSql: input.execSql,\n expectedOrganizationId: input.author.organizationId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n if (!proof) {\n throw new ProjectionDependencyUnavailableError(\n \"Purged remote document is missing its signed purge proof\",\n );\n }\n const verified = await verifyDocumentPurgeProof({\n execSql: input.execSql,\n expectedDocumentId: input.documentId,\n expectedOrganizationId: input.author.organizationId,\n proof,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n await input.onVerifiedPurge({\n commitPurgeProof: verified.commitCheckpoints,\n });\n return { ...proof, reclaimedBlobStorageKeys: [] };\n }\n await verifyDocumentWriterProjection({\n execSql: input.execSql,\n projection: writerProjection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n const request = await buildDocumentPurgeRequest({\n author: input.author,\n writerProjection,\n });\n const response = await input.apiClient.purgeDocument(\n input.documentId,\n request,\n {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n },\n );\n if (!response) {\n return null;\n }\n const verified = await verifyDocumentPurgeProof({\n execSql: input.execSql,\n expectedDocumentId: input.documentId,\n expectedOrganizationId: input.author.organizationId,\n proof: response,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n await input.onVerifiedPurge({\n commitPurgeProof: verified.commitCheckpoints,\n });\n return response;\n}\n"]}
1
+ {"version":3,"file":"purge.js","sourceRoot":"","sources":["purge.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n computeAccessEventBodyHash,\n type DocumentPurgeAccessEventBody,\n KeyingVerificationError,\n signAccessEvent,\n type UnsignedAccessEvent,\n} from \"@tearleads/crypto\";\nimport type { DocumentPurgeRequest } from \"@tearleads/validators/request\";\nimport {\n DOCUMENT_NOT_FOUND_ERROR_CODE,\n type DocumentPurgeProofResponse,\n type DocumentPurgeResponse,\n type DocumentWriterProjectionResponse,\n} from \"@tearleads/validators/response\";\nimport {\n containerPathRefs,\n readLinkedContainerIdsFromDocumentManifest,\n} from \"../../data/documents/shared/projection\";\nimport {\n assertDocumentManifestBundleConsistent,\n readManifestContainerId,\n} from \"../../data/documents/shared/readers\";\nimport type {\n DocumentCreateAuthor,\n DocumentSyncApi,\n} from \"../../data/documents/shared/types\";\nimport {\n readCanonicalJson,\n readCanonicalRecord,\n} from \"../../data/keyingCanonicalJson\";\nimport type {\n ProjectionUserKeyResolver,\n ReferencedPrincipalPolicyWarmer,\n} from \"../../data/keyingProjectionVerification\";\nimport {\n verifyDocumentPurgeProof,\n verifyDocumentPurgeProofBaseline,\n verifyDocumentWriterProjection,\n} from \"../../data/keyingProjectionVerification\";\nimport { ProjectionDependencyUnavailableError } from \"../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { documentContainerProjections } from \"../../data/keyingProjectionVerification/documentContainerProjections\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { assertSubtreePurgePath } from \"./subtreePurgeScope\";\n\ninterface DocumentPurgeApi {\n getDocumentPurgeProof(\n documentId: string,\n options?: {\n readonly documentCheckpointManifestHash?: string;\n },\n requestOptions?: {\n readonly expectedPaymentRequiredOrganizationId?: string;\n },\n ): Promise<DocumentPurgeProofResponse | null>;\n getDocumentWriterProjectionResult: NonNullable<\n DocumentSyncApi[\"getDocumentWriterProjectionResult\"]\n >;\n purgeDocument(\n documentId: string,\n request: DocumentPurgeRequest,\n options?: {\n readonly expectedPaymentRequiredOrganizationId?: string;\n },\n ): Promise<DocumentPurgeResponse | null>;\n}\n\nconst REMOTE_DOCUMENT_ALREADY_PURGED = Symbol(\"remoteDocumentAlreadyPurged\");\n\nasync function loadAuthenticatedDocumentPurgeProof(input: {\n readonly apiClient: Pick<DocumentSyncApi, \"getDocumentPurgeProof\">;\n readonly documentId: string;\n readonly execSql: ExecSql;\n readonly expectedOrganizationId: string;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}): Promise<DocumentPurgeProofResponse | null> {\n if (!input.apiClient.getDocumentPurgeProof) {\n throw new ProjectionDependencyUnavailableError(\n \"Remote document deletion is missing a purge-proof endpoint\",\n );\n }\n const initialProof = await input.apiClient.getDocumentPurgeProof(\n input.documentId,\n undefined,\n {\n expectedPaymentRequiredOrganizationId: input.expectedOrganizationId,\n },\n );\n if (!initialProof) {\n // ApiClient returns null for transport, HTTP and response-shape failures.\n // Even after a deletion hint, absence cannot prove signed-data tampering.\n // Keep local data and defer deletion until an actual proof verifies.\n return null;\n }\n await verifyDocumentPurgeProofBaseline({\n execSql: input.execSql,\n expectedDocumentId: input.documentId,\n expectedOrganizationId: input.expectedOrganizationId,\n proof: initialProof,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n // The current API returns complete history to recorded readers. Otherwise\n // its terminal snapshot needs an exact local pin; a floor refetch cannot\n // grant missing observation authority.\n return initialProof;\n}\n\nasync function resolveDocumentPurgeWriterProjection(input: {\n readonly apiClient: DocumentPurgeApi;\n readonly documentId: string;\n}): Promise<\n | DocumentWriterProjectionResponse\n | typeof REMOTE_DOCUMENT_ALREADY_PURGED\n | null\n> {\n const result = await input.apiClient.getDocumentWriterProjectionResult(\n input.documentId,\n { reportErrors: false },\n );\n if (result.ok) {\n return result.data;\n }\n if (result.status === 404 && result.code === DOCUMENT_NOT_FOUND_ERROR_CODE) {\n return REMOTE_DOCUMENT_ALREADY_PURGED;\n }\n result.report();\n return null;\n}\n\nexport async function buildDocumentPurgeRequest(input: {\n readonly author: DocumentCreateAuthor;\n readonly expectedSubtreeRootId?: string | undefined;\n readonly eventId?: string | undefined;\n readonly signedAt?: string | undefined;\n readonly writerProjection: DocumentWriterProjectionResponse;\n}): Promise<DocumentPurgeRequest> {\n const identity = await assertDocumentManifestBundleConsistent({\n bundle: input.writerProjection.documentManifest,\n label: \"Document purge predecessor\",\n });\n if (identity.documentId !== input.writerProjection.documentId) {\n throw new Error(\"Document purge projection identity mismatch\");\n }\n if (identity.organizationId !== input.author.organizationId) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Document purge author organization mismatch\",\n );\n }\n const linkedContainerIds = readLinkedContainerIdsFromDocumentManifest(\n input.writerProjection,\n );\n if (linkedContainerIds.length !== 1) {\n throw new Error(\"Document purge requires exactly one linked container\");\n }\n const [containerId] = linkedContainerIds;\n const authorizingProjection = documentContainerProjections(\n input.writerProjection,\n ).find((projection) => {\n const leaf = projection.path.at(-1);\n return leaf && readManifestContainerId(leaf) === containerId;\n });\n const containerManifestHash =\n authorizingProjection?.path.at(-1)?.manifestHash;\n if (!authorizingProjection || !containerId || !containerManifestHash) {\n throw new Error(\"Document purge authorization path is unavailable\");\n }\n assertSubtreePurgePath(\n authorizingProjection.path,\n input.expectedSubtreeRootId,\n );\n const authorizingContainerManifestHashes = authorizingProjection.path.map(\n (bundle) => bundle.manifestHash,\n );\n const body: DocumentPurgeAccessEventBody = {\n authorizingContainerManifestHashes,\n containerId,\n containerManifestHash,\n documentManifestHash: input.writerProjection.documentManifest.manifestHash,\n eventType: \"document.purge\",\n };\n const unsignedEvent: UnsignedAccessEvent = {\n version: 1,\n eventId: input.eventId ?? crypto.randomUUID(),\n eventType: \"document.purge\",\n objectKind: \"document\",\n objectId: input.writerProjection.documentId,\n organizationId: input.author.organizationId,\n previousManifestHash: input.writerProjection.documentManifest.manifestHash,\n dependencyManifestHashes: authorizingContainerManifestHashes,\n bodyHash: await computeAccessEventBodyHash(\n readCanonicalJson(body, \"Document purge body\"),\n ),\n signerUserId: input.author.signerUserId,\n signerDeviceId: input.author.signerDeviceId,\n signerKeyFingerprint: input.author.signerKeyFingerprint,\n signedAt: input.signedAt ?? new Date().toISOString(),\n };\n const event = await signAccessEvent(\n unsignedEvent,\n input.author.signerPrivateKey,\n );\n\n return {\n authorizingContainerPathRefs: containerPathRefs(authorizingProjection.path),\n body: readCanonicalRecord(body, \"Document purge body\"),\n event: readCanonicalRecord(event, \"Document purge event\"),\n };\n}\n\nexport function createVerifiedRemoteDocumentDeletionHandler(input: {\n readonly apiClient: Pick<DocumentSyncApi, \"getDocumentPurgeProof\">;\n readonly execSql: ExecSql;\n readonly expectedOrganizationId: string;\n readonly onVerifiedDeletion: (input: {\n readonly commitPurgeProof: (transactionExecSql: ExecSql) => Promise<void>;\n readonly documentId: string;\n }) => Promise<void> | void;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}): (deleted: { readonly documentId: string }) => Promise<void> {\n return async ({ documentId }) => {\n const proof = await loadAuthenticatedDocumentPurgeProof({\n apiClient: input.apiClient,\n documentId,\n execSql: input.execSql,\n expectedOrganizationId: input.expectedOrganizationId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n if (!proof) {\n throw new ProjectionDependencyUnavailableError(\n \"Remote document deletion is missing its signed purge proof\",\n );\n }\n const verified = await verifyDocumentPurgeProof({\n execSql: input.execSql,\n expectedDocumentId: documentId,\n expectedOrganizationId: input.expectedOrganizationId,\n proof,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n await input.onVerifiedDeletion({\n commitPurgeProof: verified.commitCheckpoints,\n documentId,\n });\n };\n}\n\nexport async function purgeRemoteDocument(input: {\n readonly beforeSubmit?: (() => Promise<boolean>) | undefined;\n readonly apiClient: DocumentPurgeApi;\n readonly author: DocumentCreateAuthor;\n readonly documentId: string;\n readonly expectedSubtreeRootId?: string | undefined;\n readonly execSql: ExecSql;\n readonly onVerifiedPurge: (input: {\n readonly commitPurgeProof: (transactionExecSql: ExecSql) => Promise<void>;\n }) => Promise<void> | void;\n readonly resolveProjectionUserKey: ProjectionUserKeyResolver;\n readonly stillCurrent?: (() => boolean) | undefined;\n readonly warmReferencedPrincipalPolicies?:\n | ReferencedPrincipalPolicyWarmer\n | undefined;\n}): Promise<DocumentPurgeResponse | null> {\n const writerProjection = await resolveDocumentPurgeWriterProjection({\n apiClient: input.apiClient,\n documentId: input.documentId,\n });\n if (!writerProjection) {\n return null;\n }\n if (writerProjection === REMOTE_DOCUMENT_ALREADY_PURGED) {\n const proof = await loadAuthenticatedDocumentPurgeProof({\n apiClient: input.apiClient,\n documentId: input.documentId,\n execSql: input.execSql,\n expectedOrganizationId: input.author.organizationId,\n resolveProjectionUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n if (!proof) {\n throw new ProjectionDependencyUnavailableError(\n \"Purged remote document is missing its signed purge proof\",\n );\n }\n const verified = await verifyDocumentPurgeProof({\n execSql: input.execSql,\n expectedDocumentId: input.documentId,\n expectedOrganizationId: input.author.organizationId,\n proof,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n await input.onVerifiedPurge({\n commitPurgeProof: verified.commitCheckpoints,\n });\n return { ...proof, reclaimedBlobStorageKeys: [] };\n }\n await verifyDocumentWriterProjection({\n execSql: input.execSql,\n projection: writerProjection,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n const request = await buildDocumentPurgeRequest({\n author: input.author,\n expectedSubtreeRootId: input.expectedSubtreeRootId,\n writerProjection,\n });\n if (input.beforeSubmit && !(await input.beforeSubmit())) return null;\n if (input.stillCurrent?.() === false) return null;\n const response = await input.apiClient.purgeDocument(\n input.documentId,\n request,\n {\n expectedPaymentRequiredOrganizationId: input.author.organizationId,\n },\n );\n if (!response) {\n return null;\n }\n const verified = await verifyDocumentPurgeProof({\n execSql: input.execSql,\n expectedDocumentId: input.documentId,\n expectedOrganizationId: input.author.organizationId,\n proof: response,\n resolveUserKey: input.resolveProjectionUserKey,\n stillCurrent: input.stillCurrent,\n warmReferencedPrincipalPolicies: input.warmReferencedPrincipalPolicies,\n });\n await input.onVerifiedPurge({\n commitPurgeProof: verified.commitCheckpoints,\n });\n return response;\n}\n"]}
@@ -0,0 +1,11 @@
1
+ import { readManifestContainerId } from "../../data/documents/shared/readers.js";
2
+ /** Check the exact verified path that the destructive request will cite. */
3
+ export function assertSubtreePurgePath(path, rootContainerId) {
4
+ if (rootContainerId === undefined)
5
+ return;
6
+ if (!path.some((bundle) => readManifestContainerId(bundle) === rootContainerId)) {
7
+ // A valid move out of the selected subtree is ordinary concurrent work.
8
+ throw new Error("Document placement is outside the requested purge subtree");
9
+ }
10
+ }
11
+ //# sourceMappingURL=subtreePurgeScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subtreePurgeScope.js","sourceRoot":"","sources":["subtreePurgeScope.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,qCAAqC,CAAC;AAE9E,4EAA4E;AAC5E,MAAM,UAAU,sBAAsB,CACpC,IAAiD,EACjD,eAAmC;IAEnC,IAAI,eAAe,KAAK,SAAS;QAAE,OAAO;IAC1C,IACE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,uBAAuB,CAAC,MAAM,CAAC,KAAK,eAAe,CAAC,EAC3E,CAAC;QACD,wEAAwE;QACxE,MAAM,IAAI,KAAK,CACb,2DAA2D,CAC5D,CAAC;IACJ,CAAC;AACH,CAAC","sourcesContent":["import type { AccessManifestBundleWireResponse } from \"@tearleads/validators/response\";\nimport { readManifestContainerId } from \"../../data/documents/shared/readers\";\n\n/** Check the exact verified path that the destructive request will cite. */\nexport function assertSubtreePurgePath(\n path: readonly AccessManifestBundleWireResponse[],\n rootContainerId: string | undefined,\n): void {\n if (rootContainerId === undefined) return;\n if (\n !path.some((bundle) => readManifestContainerId(bundle) === rootContainerId)\n ) {\n // A valid move out of the selected subtree is ordinary concurrent work.\n throw new Error(\n \"Document placement is outside the requested purge subtree\",\n );\n }\n}\n"]}
@@ -0,0 +1,39 @@
1
+ import { type PrincipalHistoryPrefix } from "../../data/persistence/principalHistoryPrefixPersistence.js";
2
+ import type { RecoverPrincipalPolicyHistoryOptions } from "../principals/principalHistoryRecoveryTypes.js";
3
+ /** Sealed predecessor evidence, owned by a mutation before its request is sent. */
4
+ export type AcknowledgedPrincipalPredecessor = Readonly<PrincipalHistoryPrefix>;
5
+ /** Authenticate the entire prefix, including its scope and current artifacts. */
6
+ export declare function restoreAcknowledgedPrincipalPrefix(options: RecoverPrincipalPolicyHistoryOptions, saved: AcknowledgedPrincipalPredecessor | null): Promise<{
7
+ scopeId: string;
8
+ saved: Readonly<{
9
+ version: number;
10
+ organizationId: string;
11
+ scopeId: string;
12
+ headJson: string;
13
+ currentJson: string;
14
+ progress: string;
15
+ }>;
16
+ artifacts: import("../../internal/api-client/index.js").PrincipalPolicyPageCurrent;
17
+ verifier: import("../../internal/crypto/index.js").PrincipalPolicyHistoryVerifier;
18
+ previous: import("../../internal/crypto/index.js").VerifiedPrincipalPolicyCurrent;
19
+ history: import("../../internal/crypto/index.js").VerifiedPrincipalPolicyHistory;
20
+ predecessorIndexRootHash: string;
21
+ }>;
22
+ /** Capture exact predecessor evidence while authoring, before HTTP can commit. */
23
+ export declare function captureAcknowledgedPrincipalPredecessor(input: RecoverPrincipalPolicyHistoryOptions): Promise<AcknowledgedPrincipalPredecessor>;
24
+ export declare function restoreAcknowledgedPrincipalPredecessor(options: RecoverPrincipalPolicyHistoryOptions, saved: AcknowledgedPrincipalPredecessor | null): Promise<{
25
+ scopeId: string;
26
+ saved: Readonly<{
27
+ version: number;
28
+ organizationId: string;
29
+ scopeId: string;
30
+ headJson: string;
31
+ currentJson: string;
32
+ progress: string;
33
+ }>;
34
+ artifacts: import("../../internal/api-client/index.js").PrincipalPolicyPageCurrent;
35
+ verifier: import("../../internal/crypto/index.js").PrincipalPolicyHistoryVerifier;
36
+ previous: import("../../internal/crypto/index.js").VerifiedPrincipalPolicyCurrent;
37
+ history: import("../../internal/crypto/index.js").VerifiedPrincipalPolicyHistory;
38
+ predecessorIndexRootHash: string;
39
+ }>;
@@ -0,0 +1,87 @@
1
+ import { KeyingVerificationError, restorePrincipalPolicyHistoryVerifier, verifyPrincipalPolicyCurrent, } from "../../internal/crypto/index.js";
2
+ import { assertProjectionVerificationCurrent } from "../../data/keyingProjectionVerification/types.js";
3
+ import { loadPrincipalHistoryPrefix, } from "../../data/persistence/principalHistoryPrefixPersistence.js";
4
+ import { principalHeadMatchesReference } from "../../data/principals/organizationAuthorityDescriptor.js";
5
+ import { principalHistoryEvidenceScopeId, principalHistoryPrefixProtection, } from "../../data/principals/principalHistoryPrefixProtection.js";
6
+ import { ownPrincipalHistoryProtection } from "../../data/principals/principalHistoryProtection.js";
7
+ import { parsePrincipalHistoryStageCurrent } from "../../data/principals/principalHistoryStageProtection.js";
8
+ import { principalPolicyReferenceFromBundle } from "../../data/principals/principalPolicyAdminSigners.js";
9
+ import { principalHistoryVerificationContext } from "../principals/principalHistoryRecoveryVerification.js";
10
+ /** Authenticate the entire prefix, including its scope and current artifacts. */
11
+ export async function restoreAcknowledgedPrincipalPrefix(options, saved) {
12
+ const scopeId = await principalHistoryEvidenceScopeId({
13
+ organizationId: options.organizationId,
14
+ head: options.expectedHead,
15
+ protection: options.protection,
16
+ });
17
+ if (!saved ||
18
+ saved.scopeId !== scopeId ||
19
+ saved.organizationId !== options.organizationId)
20
+ throw new KeyingVerificationError("missing_dependency", "Acknowledgement requires its authenticated predecessor prefix");
21
+ const restored = await restorePrincipalPolicyHistoryVerifier({
22
+ principalId: options.expectedHead.principalId,
23
+ principalType: options.expectedHead.principalType,
24
+ }, saved.progress, await principalHistoryPrefixProtection(options.protection, saved));
25
+ if (!restored.ok)
26
+ throw restored.error;
27
+ const artifacts = parsePrincipalHistoryStageCurrent({
28
+ currentJson: saved.currentJson,
29
+ afterVersion: saved.version - 1,
30
+ });
31
+ if (!artifacts)
32
+ throw new KeyingVerificationError("invalid_shape", "Acknowledged predecessor artifacts are malformed");
33
+ const history = restored.value.finish(principalPolicyReferenceFromBundle(artifacts));
34
+ if (!history.ok)
35
+ throw history.error;
36
+ const previous = await verifyPrincipalPolicyCurrent({
37
+ current: artifacts,
38
+ history: history.value,
39
+ });
40
+ if (!previous.ok)
41
+ throw previous.error;
42
+ return {
43
+ scopeId,
44
+ saved,
45
+ artifacts,
46
+ verifier: restored.value,
47
+ previous: previous.value,
48
+ history: history.value,
49
+ predecessorIndexRootHash: history.value.indexRootHash,
50
+ };
51
+ }
52
+ /** Capture exact predecessor evidence while authoring, before HTTP can commit. */
53
+ export async function captureAcknowledgedPrincipalPredecessor(input) {
54
+ const protection = ownPrincipalHistoryProtection(input.protection);
55
+ const options = {
56
+ ...input,
57
+ expectedHead: structuredClone(input.expectedHead),
58
+ protection: {
59
+ ...protection,
60
+ context: principalHistoryVerificationContext(input),
61
+ },
62
+ };
63
+ const current = () => !options.signal?.aborted && options.stillCurrent();
64
+ try {
65
+ assertProjectionVerificationCurrent(current);
66
+ const scopeId = await principalHistoryEvidenceScopeId({
67
+ organizationId: options.organizationId,
68
+ head: options.expectedHead,
69
+ protection: options.protection,
70
+ });
71
+ const restored = await restoreAcknowledgedPrincipalPrefix(options, await loadPrincipalHistoryPrefix(options.execSql, scopeId));
72
+ assertProjectionVerificationCurrent(current);
73
+ if (!principalHeadMatchesReference(restored.previous.state, options.expectedHead))
74
+ throw new Error("Principal policy changed before submission; retry the mutation");
75
+ return structuredClone(restored.saved);
76
+ }
77
+ finally {
78
+ protection.localKey.fill(0);
79
+ }
80
+ }
81
+ export async function restoreAcknowledgedPrincipalPredecessor(options, saved) {
82
+ const restored = await restoreAcknowledgedPrincipalPrefix(options, saved);
83
+ if (!principalHeadMatchesReference(restored.previous.state, options.expectedHead))
84
+ throw new KeyingVerificationError("object_mismatch", "Acknowledgement predecessor does not match the authored request");
85
+ return restored;
86
+ }
87
+ //# sourceMappingURL=acknowledgedPrincipalPredecessor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"acknowledgedPrincipalPredecessor.js","sourceRoot":"","sources":["acknowledgedPrincipalPredecessor.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n KeyingVerificationError,\n restorePrincipalPolicyHistoryVerifier,\n verifyPrincipalPolicyCurrent,\n} from \"@tearleads/crypto\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport {\n loadPrincipalHistoryPrefix,\n type PrincipalHistoryPrefix,\n} from \"../../data/persistence/principalHistoryPrefixPersistence\";\nimport { principalHeadMatchesReference } from \"../../data/principals/organizationAuthorityDescriptor\";\nimport {\n principalHistoryEvidenceScopeId,\n principalHistoryPrefixProtection,\n} from \"../../data/principals/principalHistoryPrefixProtection\";\nimport { ownPrincipalHistoryProtection } from \"../../data/principals/principalHistoryProtection\";\nimport { parsePrincipalHistoryStageCurrent } from \"../../data/principals/principalHistoryStageProtection\";\nimport { principalPolicyReferenceFromBundle } from \"../../data/principals/principalPolicyAdminSigners\";\nimport type { RecoverPrincipalPolicyHistoryOptions } from \"../principals/principalHistoryRecoveryTypes\";\nimport { principalHistoryVerificationContext } from \"../principals/principalHistoryRecoveryVerification\";\n\n/** Sealed predecessor evidence, owned by a mutation before its request is sent. */\nexport type AcknowledgedPrincipalPredecessor = Readonly<PrincipalHistoryPrefix>;\n\n/** Authenticate the entire prefix, including its scope and current artifacts. */\nexport async function restoreAcknowledgedPrincipalPrefix(\n options: RecoverPrincipalPolicyHistoryOptions,\n saved: AcknowledgedPrincipalPredecessor | null,\n) {\n const scopeId = await principalHistoryEvidenceScopeId({\n organizationId: options.organizationId,\n head: options.expectedHead,\n protection: options.protection,\n });\n if (\n !saved ||\n saved.scopeId !== scopeId ||\n saved.organizationId !== options.organizationId\n )\n throw new KeyingVerificationError(\n \"missing_dependency\",\n \"Acknowledgement requires its authenticated predecessor prefix\",\n );\n const restored = await restorePrincipalPolicyHistoryVerifier(\n {\n principalId: options.expectedHead.principalId,\n principalType: options.expectedHead.principalType,\n },\n saved.progress,\n await principalHistoryPrefixProtection(options.protection, saved),\n );\n if (!restored.ok) throw restored.error;\n const artifacts = parsePrincipalHistoryStageCurrent({\n currentJson: saved.currentJson,\n afterVersion: saved.version - 1,\n });\n if (!artifacts)\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"Acknowledged predecessor artifacts are malformed\",\n );\n const history = restored.value.finish(\n principalPolicyReferenceFromBundle(artifacts),\n );\n if (!history.ok) throw history.error;\n const previous = await verifyPrincipalPolicyCurrent({\n current: artifacts,\n history: history.value,\n });\n if (!previous.ok) throw previous.error;\n return {\n scopeId,\n saved,\n artifacts,\n verifier: restored.value,\n previous: previous.value,\n history: history.value,\n predecessorIndexRootHash: history.value.indexRootHash,\n };\n}\n\n/** Capture exact predecessor evidence while authoring, before HTTP can commit. */\nexport async function captureAcknowledgedPrincipalPredecessor(\n input: RecoverPrincipalPolicyHistoryOptions,\n): Promise<AcknowledgedPrincipalPredecessor> {\n const protection = ownPrincipalHistoryProtection(input.protection);\n const options = {\n ...input,\n expectedHead: structuredClone(input.expectedHead),\n protection: {\n ...protection,\n context: principalHistoryVerificationContext(input),\n },\n };\n const current = () => !options.signal?.aborted && options.stillCurrent();\n try {\n assertProjectionVerificationCurrent(current);\n const scopeId = await principalHistoryEvidenceScopeId({\n organizationId: options.organizationId,\n head: options.expectedHead,\n protection: options.protection,\n });\n const restored = await restoreAcknowledgedPrincipalPrefix(\n options,\n await loadPrincipalHistoryPrefix(options.execSql, scopeId),\n );\n assertProjectionVerificationCurrent(current);\n if (\n !principalHeadMatchesReference(\n restored.previous.state,\n options.expectedHead,\n )\n )\n throw new Error(\n \"Principal policy changed before submission; retry the mutation\",\n );\n return structuredClone(restored.saved);\n } finally {\n protection.localKey.fill(0);\n }\n}\n\nexport async function restoreAcknowledgedPrincipalPredecessor(\n options: RecoverPrincipalPolicyHistoryOptions,\n saved: AcknowledgedPrincipalPredecessor | null,\n) {\n const restored = await restoreAcknowledgedPrincipalPrefix(options, saved);\n if (\n !principalHeadMatchesReference(\n restored.previous.state,\n options.expectedHead,\n )\n )\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"Acknowledgement predecessor does not match the authored request\",\n );\n return restored;\n}\n"]}
@@ -1,9 +1,16 @@
1
1
  import { assertProjectionVerificationCurrent } from "../../data/keyingProjectionVerification/types.js";
2
2
  import { scopedGroupHistoryProtection } from "../../data/principals/principalHistoryScopeProtection.js";
3
3
  import { principalPolicyReferenceFromBundle } from "../../data/principals/principalPolicyAdminSigners.js";
4
+ import { captureAcknowledgedPrincipalPredecessor } from "./acknowledgedPrincipalPredecessor.js";
4
5
  import { retainAcknowledgedPrincipalCurrents, } from "./retainAcknowledgedPrincipalCurrents.js";
5
6
  /** Couple directory acknowledgements to any newly created group under the same lease. */
6
- export function createCurrentOrganizationMutationRetention(input) {
7
+ export async function createCurrentOrganizationMutationRetention(input) {
8
+ const predecessor = await captureAcknowledgedPrincipalPredecessor({
9
+ ...input.directoryRecovery,
10
+ execSql: input.runtime.infra.execSql,
11
+ organizationId: input.organizationId,
12
+ stillCurrent: input.stillCurrent,
13
+ });
7
14
  const retain = async (entries) => {
8
15
  assertProjectionVerificationCurrent(input.stillCurrent);
9
16
  await retainAcknowledgedPrincipalCurrents({
@@ -14,7 +21,9 @@ export function createCurrentOrganizationMutationRetention(input) {
14
21
  });
15
22
  };
16
23
  return {
17
- retainDirectory: (request, response) => retain([{ request, response, recovery: input.directoryRecovery }]),
24
+ retainDirectory: (request, response) => retain([
25
+ { predecessor, request, response, recovery: input.directoryRecovery },
26
+ ]),
18
27
  retainCreatedGroup: (receipt) => {
19
28
  const adminHead = {
20
29
  ...principalPolicyReferenceFromBundle(input.authority.admins.current),
@@ -23,6 +32,7 @@ export function createCurrentOrganizationMutationRetention(input) {
23
32
  return retain([
24
33
  {
25
34
  initialGroup: true,
35
+ predecessor: null,
26
36
  request: receipt.request,
27
37
  response: receipt.response,
28
38
  recovery: {
@@ -42,6 +52,7 @@ export function createCurrentOrganizationMutationRetention(input) {
42
52
  },
43
53
  },
44
54
  {
55
+ predecessor,
45
56
  request: receipt.organizationRequest,
46
57
  response: receipt.organizationResponse,
47
58
  recovery: input.directoryRecovery,
@@ -1 +1 @@
1
- {"version":3,"file":"currentOrganizationMutationRetention.js","sourceRoot":"","sources":["currentOrganizationMutationRetention.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mCAAmC,EAAE,MAAM,+CAA+C,CAAC;AACpG,OAAO,EAAE,4BAA4B,EAAE,MAAM,uDAAuD,CAAC;AACrG,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AAGvG,OAAO,EAEL,mCAAmC,GACpC,MAAM,uCAAuC,CAAC;AAE/C,yFAAyF;AACzF,MAAM,UAAU,0CAA0C,CAAC,KAU1D;IACC,MAAM,MAAM,GAAG,KAAK,EAClB,OAAqD,EACrD,EAAE;QACF,mCAAmC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,mCAAmC,CAAC;YACxC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;YACpC,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,OAAO;YACP,YAAY,EAAE,KAAK,CAAC,YAAY;SACjC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,OAAO;QACL,eAAe,EAAE,CACf,OAAkC,EAClC,QAAyC,EACzC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC;QACvE,kBAAkB,EAAE,CAAC,OAKpB,EAAE,EAAE;YACH,MAAM,SAAS,GAAG;gBAChB,GAAG,kCAAkC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;gBACrE,aAAa,EAAE,OAAgB;aAChC,CAAC;YACF,OAAO,MAAM,CAAC;gBACZ;oBACE,YAAY,EAAE,IAAI;oBAClB,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,QAAQ,EAAE;wBACR,SAAS,EAAE,EAAE,uBAAuB,EAAE,KAAK,CAAC,SAAS,EAAE;wBACvD,YAAY,EAAE,kCAAkC,CAAC,OAAO,CAAC,QAAQ,CAAC;wBAClE,UAAU,EAAE,4BAA4B,CACtC,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,YAAY,CACxC;wBACD,0BAA0B,EACxB,KAAK,CAAC,OAAO,CAAC,0BAA0B;wBAC1C,qBAAqB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;4BAClC,WAAW,EAAE,SAAS;4BACtB,MAAM,EAAE;gCACN;oCACE,IAAI,EAAE,SAAS;oCACf,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU;iCACrD;6BACF;yBACF,CAAC;qBACH;iBACF;gBACD;oBACE,OAAO,EAAE,OAAO,CAAC,mBAAmB;oBACpC,QAAQ,EAAE,OAAO,CAAC,oBAAoB;oBACtC,QAAQ,EAAE,KAAK,CAAC,iBAAiB;iBAClC;aACF,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { PrincipalPolicyHistoryProgressOptions } from \"@tearleads/crypto\";\nimport type { PutPrincipalPolicyRequest } from \"@tearleads/validators/request\";\nimport type { PrincipalPolicyMutationResponse } from \"@tearleads/validators/response\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport { scopedGroupHistoryProtection } from \"../../data/principals/principalHistoryScopeProtection\";\nimport { principalPolicyReferenceFromBundle } from \"../../data/principals/principalPolicyAdminSigners\";\nimport type { PrincipalPolicyRecoveryRuntime } from \"../principals/runtimePolicyRecovery\";\nimport type { loadCurrentOrganizationAuthority } from \"./currentOrganizationAuthority\";\nimport {\n type AcknowledgedPrincipalCurrentInput,\n retainAcknowledgedPrincipalCurrents,\n} from \"./retainAcknowledgedPrincipalCurrents\";\n\n/** Couple directory acknowledgements to any newly created group under the same lease. */\nexport function createCurrentOrganizationMutationRetention(input: {\n readonly runtime: PrincipalPolicyRecoveryRuntime;\n readonly authority: Awaited<\n ReturnType<typeof loadCurrentOrganizationAuthority>\n >;\n readonly protection: PrincipalPolicyHistoryProgressOptions;\n readonly readPages: ApiClient[\"getPrincipalPolicyPages\"];\n readonly organizationId: string;\n readonly directoryRecovery: AcknowledgedPrincipalCurrentInput[\"recovery\"];\n readonly stillCurrent: () => boolean;\n}) {\n const retain = async (\n entries: readonly AcknowledgedPrincipalCurrentInput[],\n ) => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n await retainAcknowledgedPrincipalCurrents({\n execSql: input.runtime.infra.execSql,\n organizationId: input.organizationId,\n entries,\n stillCurrent: input.stillCurrent,\n });\n };\n return {\n retainDirectory: (\n request: PutPrincipalPolicyRequest,\n response: PrincipalPolicyMutationResponse,\n ) => retain([{ request, response, recovery: input.directoryRecovery }]),\n retainCreatedGroup: (receipt: {\n readonly request: PutPrincipalPolicyRequest;\n readonly response: PrincipalPolicyMutationResponse;\n readonly organizationRequest: PutPrincipalPolicyRequest;\n readonly organizationResponse: PrincipalPolicyMutationResponse;\n }) => {\n const adminHead = {\n ...principalPolicyReferenceFromBundle(input.authority.admins.current),\n principalType: \"group\" as const,\n };\n return retain([\n {\n initialGroup: true,\n request: receipt.request,\n response: receipt.response,\n recovery: {\n apiClient: { getPrincipalPolicyPages: input.readPages },\n expectedHead: principalPolicyReferenceFromBundle(receipt.response),\n protection: scopedGroupHistoryProtection(\n input.protection,\n input.organizationId,\n input.authority.descriptor.adminGroupId,\n ),\n resolveTrustedUserIdentity:\n input.runtime.resolveTrustedUserIdentity,\n loadExternalAuthority: async () => ({\n currentHead: adminHead,\n states: [\n {\n head: adminHead,\n projection: input.authority.admins.policy.projection,\n },\n ],\n }),\n },\n },\n {\n request: receipt.organizationRequest,\n response: receipt.organizationResponse,\n recovery: input.directoryRecovery,\n },\n ]);\n },\n };\n}\n"]}
1
+ {"version":3,"file":"currentOrganizationMutationRetention.js","sourceRoot":"","sources":["currentOrganizationMutationRetention.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,mCAAmC,EAAE,MAAM,+CAA+C,CAAC;AACpG,OAAO,EAAE,4BAA4B,EAAE,MAAM,uDAAuD,CAAC;AACrG,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AAEvG,OAAO,EAAE,uCAAuC,EAAE,MAAM,oCAAoC,CAAC;AAE7F,OAAO,EAEL,mCAAmC,GACpC,MAAM,uCAAuC,CAAC;AAE/C,yFAAyF;AACzF,MAAM,CAAC,KAAK,UAAU,0CAA0C,CAAC,KAUhE;IACC,MAAM,WAAW,GAAG,MAAM,uCAAuC,CAAC;QAChE,GAAG,KAAK,CAAC,iBAAiB;QAC1B,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;QACpC,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC,CAAC;IACH,MAAM,MAAM,GAAG,KAAK,EAClB,OAAqD,EACrD,EAAE;QACF,mCAAmC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,mCAAmC,CAAC;YACxC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;YACpC,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,OAAO;YACP,YAAY,EAAE,KAAK,CAAC,YAAY;SACjC,CAAC,CAAC;IACL,CAAC,CAAC;IACF,OAAO;QACL,eAAe,EAAE,CACf,OAAkC,EAClC,QAAyC,EACzC,EAAE,CACF,MAAM,CAAC;YACL,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,iBAAiB,EAAE;SACtE,CAAC;QACJ,kBAAkB,EAAE,CAAC,OAKpB,EAAE,EAAE;YACH,MAAM,SAAS,GAAG;gBAChB,GAAG,kCAAkC,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC;gBACrE,aAAa,EAAE,OAAgB;aAChC,CAAC;YACF,OAAO,MAAM,CAAC;gBACZ;oBACE,YAAY,EAAE,IAAI;oBAClB,WAAW,EAAE,IAAI;oBACjB,OAAO,EAAE,OAAO,CAAC,OAAO;oBACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ;oBAC1B,QAAQ,EAAE;wBACR,SAAS,EAAE,EAAE,uBAAuB,EAAE,KAAK,CAAC,SAAS,EAAE;wBACvD,YAAY,EAAE,kCAAkC,CAAC,OAAO,CAAC,QAAQ,CAAC;wBAClE,UAAU,EAAE,4BAA4B,CACtC,KAAK,CAAC,UAAU,EAChB,KAAK,CAAC,cAAc,EACpB,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,YAAY,CACxC;wBACD,0BAA0B,EACxB,KAAK,CAAC,OAAO,CAAC,0BAA0B;wBAC1C,qBAAqB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;4BAClC,WAAW,EAAE,SAAS;4BACtB,MAAM,EAAE;gCACN;oCACE,IAAI,EAAE,SAAS;oCACf,UAAU,EAAE,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU;iCACrD;6BACF;yBACF,CAAC;qBACH;iBACF;gBACD;oBACE,WAAW;oBACX,OAAO,EAAE,OAAO,CAAC,mBAAmB;oBACpC,QAAQ,EAAE,OAAO,CAAC,oBAAoB;oBACtC,QAAQ,EAAE,KAAK,CAAC,iBAAiB;iBAClC;aACF,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC","sourcesContent":["import type { ApiClient } from \"@tearleads/api-client\";\nimport type { PrincipalPolicyHistoryProgressOptions } from \"@tearleads/crypto\";\nimport type { PutPrincipalPolicyRequest } from \"@tearleads/validators/request\";\nimport type { PrincipalPolicyMutationResponse } from \"@tearleads/validators/response\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport { scopedGroupHistoryProtection } from \"../../data/principals/principalHistoryScopeProtection\";\nimport { principalPolicyReferenceFromBundle } from \"../../data/principals/principalPolicyAdminSigners\";\nimport type { PrincipalPolicyRecoveryRuntime } from \"../principals/runtimePolicyRecovery\";\nimport { captureAcknowledgedPrincipalPredecessor } from \"./acknowledgedPrincipalPredecessor\";\nimport type { loadCurrentOrganizationAuthority } from \"./currentOrganizationAuthority\";\nimport {\n type AcknowledgedPrincipalCurrentInput,\n retainAcknowledgedPrincipalCurrents,\n} from \"./retainAcknowledgedPrincipalCurrents\";\n\n/** Couple directory acknowledgements to any newly created group under the same lease. */\nexport async function createCurrentOrganizationMutationRetention(input: {\n readonly runtime: PrincipalPolicyRecoveryRuntime;\n readonly authority: Awaited<\n ReturnType<typeof loadCurrentOrganizationAuthority>\n >;\n readonly protection: PrincipalPolicyHistoryProgressOptions;\n readonly readPages: ApiClient[\"getPrincipalPolicyPages\"];\n readonly organizationId: string;\n readonly directoryRecovery: AcknowledgedPrincipalCurrentInput[\"recovery\"];\n readonly stillCurrent: () => boolean;\n}) {\n const predecessor = await captureAcknowledgedPrincipalPredecessor({\n ...input.directoryRecovery,\n execSql: input.runtime.infra.execSql,\n organizationId: input.organizationId,\n stillCurrent: input.stillCurrent,\n });\n const retain = async (\n entries: readonly AcknowledgedPrincipalCurrentInput[],\n ) => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n await retainAcknowledgedPrincipalCurrents({\n execSql: input.runtime.infra.execSql,\n organizationId: input.organizationId,\n entries,\n stillCurrent: input.stillCurrent,\n });\n };\n return {\n retainDirectory: (\n request: PutPrincipalPolicyRequest,\n response: PrincipalPolicyMutationResponse,\n ) =>\n retain([\n { predecessor, request, response, recovery: input.directoryRecovery },\n ]),\n retainCreatedGroup: (receipt: {\n readonly request: PutPrincipalPolicyRequest;\n readonly response: PrincipalPolicyMutationResponse;\n readonly organizationRequest: PutPrincipalPolicyRequest;\n readonly organizationResponse: PrincipalPolicyMutationResponse;\n }) => {\n const adminHead = {\n ...principalPolicyReferenceFromBundle(input.authority.admins.current),\n principalType: \"group\" as const,\n };\n return retain([\n {\n initialGroup: true,\n predecessor: null,\n request: receipt.request,\n response: receipt.response,\n recovery: {\n apiClient: { getPrincipalPolicyPages: input.readPages },\n expectedHead: principalPolicyReferenceFromBundle(receipt.response),\n protection: scopedGroupHistoryProtection(\n input.protection,\n input.organizationId,\n input.authority.descriptor.adminGroupId,\n ),\n resolveTrustedUserIdentity:\n input.runtime.resolveTrustedUserIdentity,\n loadExternalAuthority: async () => ({\n currentHead: adminHead,\n states: [\n {\n head: adminHead,\n projection: input.authority.admins.policy.projection,\n },\n ],\n }),\n },\n },\n {\n predecessor,\n request: receipt.organizationRequest,\n response: receipt.organizationResponse,\n recovery: input.directoryRecovery,\n },\n ]);\n },\n };\n}\n"]}