@tearleads/client-sdk 0.1.111 → 0.1.113

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 (81) hide show
  1. package/Tearleads.js +0 -2
  2. package/Tearleads.js.map +1 -1
  3. package/client/containerContents/index.js +5 -1
  4. package/client/containerContents/index.js.map +1 -1
  5. package/client/deviceFirst/index.js +5 -1
  6. package/client/deviceFirst/index.js.map +1 -1
  7. package/client/identity/userIdentities.d.ts +13 -8
  8. package/client/identity/userIdentities.js +4 -22
  9. package/client/identity/userIdentities.js.map +1 -1
  10. package/client/index.d.ts +2 -2
  11. package/client/index.js +1 -0
  12. package/client/index.js.map +1 -1
  13. package/client/organizations/index.d.ts +1 -1
  14. package/client/organizations/index.js +12 -9
  15. package/client/organizations/index.js.map +1 -1
  16. package/client/organizations/organizationsTypes.d.ts +1 -2
  17. package/client/organizations/organizationsTypes.js.map +1 -1
  18. package/client/runtime/workflowRuntime.d.ts +1 -1
  19. package/client/runtime/workflowRuntime.js.map +1 -1
  20. package/data/persistence/keyingCheckpoints/keyingCheckpointAdvancePersistence.js +16 -11
  21. package/data/persistence/keyingCheckpoints/keyingCheckpointAdvancePersistence.js.map +1 -1
  22. package/data/persistence/keyingCheckpoints/keyingCheckpointPersistence.js +150 -2
  23. package/data/persistence/keyingCheckpoints/keyingCheckpointPersistence.js.map +1 -1
  24. package/data/persistence/keyingCheckpoints/principalAuthorizationCheckpoints.js +3 -2
  25. package/data/persistence/keyingCheckpoints/principalAuthorizationCheckpoints.js.map +1 -1
  26. package/data/persistence/principalHistory/principalHistoryEvidencePersistence.d.ts +14 -1
  27. package/data/persistence/principalHistory/principalHistoryEvidencePersistence.js +128 -34
  28. package/data/persistence/principalHistory/principalHistoryEvidencePersistence.js.map +1 -1
  29. package/data/persistence/principalHistory/principalHistoryNodeRetention.js +64 -24
  30. package/data/persistence/principalHistory/principalHistoryNodeRetention.js.map +1 -1
  31. package/data/persistence/principalHistory/principalHistoryPrefixPersistence.js +6 -1
  32. package/data/persistence/principalHistory/principalHistoryPrefixPersistence.js.map +1 -1
  33. package/data/persistence/principalHistory/principalHistoryRootOwnership.d.ts +45 -0
  34. package/data/persistence/principalHistory/principalHistoryRootOwnership.js +41 -4
  35. package/data/persistence/principalHistory/principalHistoryRootOwnership.js.map +1 -1
  36. package/data/persistence/principalHistory/principalHistoryStagePersistence.js +12 -8
  37. package/data/persistence/principalHistory/principalHistoryStagePersistence.js.map +1 -1
  38. package/data/persistence/principalKeys/principalGrantRetirementPersistence.js +35 -1
  39. package/data/persistence/principalKeys/principalGrantRetirementPersistence.js.map +1 -1
  40. package/data/persistence/principalPolicy/principalPolicyOrganizationPersistence.js +62 -1
  41. package/data/persistence/principalPolicy/principalPolicyOrganizationPersistence.js.map +1 -1
  42. package/data/persistence/trustedUserIdentityPinPersistence.js +70 -21
  43. package/data/persistence/trustedUserIdentityPinPersistence.js.map +1 -1
  44. package/data/persistence/verifiedManifestBundlePersistence.js +27 -10
  45. package/data/persistence/verifiedManifestBundlePersistence.js.map +1 -1
  46. package/data/sqlite/schema/index.d.ts +45 -5
  47. package/data/sqlite/schema/index.js +14 -6
  48. package/data/sqlite/schema/index.js.map +1 -1
  49. package/data/sqlite/schema/principalHistoryNodeRetentionSchema.d.ts +222 -0
  50. package/data/trustedUserIdentity/admission.d.ts +10 -0
  51. package/data/trustedUserIdentity/admission.js +15 -0
  52. package/data/trustedUserIdentity/admission.js.map +1 -0
  53. package/data/trustedUserIdentity/index.d.ts +1 -0
  54. package/data/trustedUserIdentity/index.js +1 -0
  55. package/data/trustedUserIdentity/index.js.map +1 -1
  56. package/data/trustedUserIdentity/service.d.ts +1 -5
  57. package/data/trustedUserIdentity/service.js +119 -46
  58. package/data/trustedUserIdentity/service.js.map +1 -1
  59. package/data/trustedUserIdentity/types.d.ts +12 -1
  60. package/data/trustedUserIdentity/types.js.map +1 -1
  61. package/index.d.ts +3 -3
  62. package/index.js +2 -2
  63. package/index.js.map +1 -1
  64. package/package.json +1 -1
  65. package/stores/container-contents/containerContentsStore.js +10 -1
  66. package/stores/container-contents/containerContentsStore.js.map +1 -1
  67. package/workflows/container-contents/runtime.d.ts +7 -1
  68. package/workflows/container-contents/runtime.js +2 -1
  69. package/workflows/container-contents/runtime.js.map +1 -1
  70. package/workflows/organizations/index.d.ts +0 -1
  71. package/workflows/organizations/index.js +0 -1
  72. package/workflows/organizations/index.js.map +1 -1
  73. package/workflows/principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js +13 -12
  74. package/workflows/principals/historyRecovery/loadRecoveredPrincipalHistoryPage.js.map +1 -1
  75. package/workflows/principals/historyRecovery/principalHistoryRecoveryReferences.js +17 -19
  76. package/workflows/principals/historyRecovery/principalHistoryRecoveryReferences.js.map +1 -1
  77. package/workflows/principals/publicHistory/selectPublicPrincipalHistory.js +8 -10
  78. package/workflows/principals/publicHistory/selectPublicPrincipalHistory.js.map +1 -1
  79. package/workflows/organizations/organizationUserImport.d.ts +0 -8
  80. package/workflows/organizations/organizationUserImport.js +0 -10
  81. package/workflows/organizations/organizationUserImport.js.map +0 -1
@@ -1,13 +1,14 @@
1
- import { and, asc, eq, inArray } from "drizzle-orm";
1
+ import { and, asc, eq, inArray, sql } from "drizzle-orm";
2
2
  import { principalHistoryNodes } from "../../sqlite/schema/principalHistoryEvidenceSchema.js";
3
3
  import { principalHistoryNodeReferences } from "../../sqlite/schema/principalHistoryNodeRetentionSchema.js";
4
4
  import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../../sqlite/sqliteBindBudget.js";
5
5
  import { excludedValue } from "../../sqlite/sqliteUpsert.js";
6
- import { decrementPrincipalHistoryNodeReference, } from "./principalHistoryRootOwnership.js";
7
6
  const REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodeReferences);
8
7
  const NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);
9
8
  // The scope id binds once per lookup.
10
9
  const HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
10
+ // The scope and organization ids bind once per statement.
11
+ const SCOPED_HASH_BATCH_SIZE = sqliteRowsPerStatement(1, 3);
11
12
  function batches(items, size) {
12
13
  const result = [];
13
14
  for (let index = 0; index < items.length; index += size)
@@ -43,8 +44,9 @@ async function loadStoredNodeHashes(tx, scope, hashes) {
43
44
  * Replays the per-node edge bookkeeping in memory: increment inserts or adds
44
45
  * one, decrement floors at zero within the scope's organization, and an edge
45
46
  * change keeps the count while marking nodes first stored here as managed.
47
+ * Folded root edges replay last, in the order root ownership applies them.
46
48
  */
47
- function replayNodeEdges(scope, nodes, loaded, storedNodeHashes) {
49
+ function replayNodeEdges(scope, nodes, loaded, storedNodeHashes, rootEdges) {
48
50
  const references = new Map(loaded);
49
51
  const read = (hash) => {
50
52
  const reference = references.get(hash);
@@ -97,6 +99,12 @@ function replayNodeEdges(scope, nodes, loaded, storedNodeHashes) {
97
99
  }
98
100
  storedNodeHashes.add(node.hash);
99
101
  }
102
+ // Root ownership's edges follow the page's nodes, as its statements did.
103
+ if (rootEdges) {
104
+ increment(rootEdges.increment);
105
+ if (rootEdges.decrement)
106
+ decrement(rootEdges.decrement);
107
+ }
100
108
  return references;
101
109
  }
102
110
  function isSameNodeReference(left, right) {
@@ -108,21 +116,26 @@ function isSameNodeReference(left, right) {
108
116
  }
109
117
  /**
110
118
  * Register edges and store proof nodes in the given order, with the same
111
- * result as recording each node's edges and then upserting it. Reads the
112
- * touched liveness rows once, replays the bookkeeping, and writes the changed
113
- * rows and the nodes as multi-row upserts.
119
+ * result as recording each node's edges and then upserting it, followed by
120
+ * any folded root ownership edges. Reads the touched liveness rows once,
121
+ * replays the bookkeeping, and writes the changed rows and the nodes as
122
+ * multi-row upserts.
114
123
  */
115
- export async function writePrincipalHistoryNodes(tx, scope, nodes) {
116
- if (nodes.length === 0)
124
+ export async function writePrincipalHistoryNodes(tx, scope, nodes, rootEdges) {
125
+ if (nodes.length === 0 && !rootEdges)
117
126
  return;
118
127
  const loaded = new Map();
119
- await loadNodeReferences(tx, scope, nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]), loaded);
128
+ await loadNodeReferences(tx, scope, [
129
+ ...nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]),
130
+ ...(rootEdges ? [rootEdges.increment] : []),
131
+ ...(rootEdges?.decrement ? [rootEdges.decrement] : []),
132
+ ], loaded);
120
133
  // An edge change releases the stored row's previous children.
121
134
  await loadNodeReferences(tx, scope, nodes.flatMap((node) => {
122
135
  const stored = loaded.get(node.hash);
123
136
  return [stored?.leftHash, stored?.rightHash].filter((hash) => Boolean(hash));
124
137
  }), loaded);
125
- const replayed = replayNodeEdges(scope, nodes, loaded, await loadStoredNodeHashes(tx, scope, nodes.map((node) => node.hash)));
138
+ const replayed = replayNodeEdges(scope, nodes, loaded, await loadStoredNodeHashes(tx, scope, nodes.map((node) => node.hash)), rootEdges);
126
139
  const changed = [...replayed].flatMap(([hash, reference]) => {
127
140
  const before = loaded.get(hash);
128
141
  return reference && !(before && isSameNodeReference(before, reference))
@@ -169,6 +182,45 @@ export async function writePrincipalHistoryNodes(tx, scope, nodes) {
169
182
  })
170
183
  .run();
171
184
  }
185
+ /**
186
+ * Deletes one cascade iteration's obsolete nodes and their liveness rows, then
187
+ * releases their children: one decrement per distinct multiplicity, since
188
+ * `max(count - k, 0)` equals k successive floored decrements. A child that is
189
+ * itself obsolete in this iteration is deleted either way, as the per-node
190
+ * loop deleted it before or after decrementing it.
191
+ */
192
+ async function releaseObsoleteNodes(tx, scope, obsolete) {
193
+ const hashes = obsolete.map((node) => node.hash);
194
+ for (const batch of batches(hashes, SCOPED_HASH_BATCH_SIZE)) {
195
+ await tx
196
+ .delete(principalHistoryNodes)
197
+ .where(and(eq(principalHistoryNodes.scopeId, scope.scopeId), eq(principalHistoryNodes.organizationId, scope.organizationId), inArray(principalHistoryNodes.hash, batch)))
198
+ .run();
199
+ await tx
200
+ .delete(principalHistoryNodeReferences)
201
+ .where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), inArray(principalHistoryNodeReferences.hash, batch)))
202
+ .run();
203
+ }
204
+ const released = new Set(hashes);
205
+ const multiplicity = new Map();
206
+ for (const node of obsolete)
207
+ for (const hash of [node.leftHash, node.rightHash])
208
+ if (hash && !released.has(hash))
209
+ multiplicity.set(hash, (multiplicity.get(hash) ?? 0) + 1);
210
+ const byCount = new Map();
211
+ for (const [hash, count] of multiplicity)
212
+ byCount.set(count, [...(byCount.get(count) ?? []), hash]);
213
+ for (const [count, children] of [...byCount].sort(([a], [b]) => a - b))
214
+ for (const batch of batches(children.sort(), SCOPED_HASH_BATCH_SIZE))
215
+ await tx
216
+ .update(principalHistoryNodeReferences)
217
+ .set({
218
+ // `count` is a positive integer computed here, inlined like the literal 1.
219
+ referenceCount: sql `max(${principalHistoryNodeReferences.referenceCount} - ${sql.raw(String(count))}, 0)`,
220
+ })
221
+ .where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), eq(principalHistoryNodeReferences.organizationId, scope.organizationId), inArray(principalHistoryNodeReferences.hash, batch)))
222
+ .run();
223
+ }
172
224
  /** A bounded cascade releases only managed proof nodes with no live incoming edge. */
173
225
  export async function reclaimPrincipalHistoryNodes(tx, scope) {
174
226
  let reclaimed = 0;
@@ -181,20 +233,8 @@ export async function reclaimPrincipalHistoryNodes(tx, scope) {
181
233
  .limit(64 - reclaimed);
182
234
  if (obsolete.length === 0)
183
235
  break;
184
- for (const node of obsolete) {
185
- await tx
186
- .delete(principalHistoryNodes)
187
- .where(and(eq(principalHistoryNodes.scopeId, scope.scopeId), eq(principalHistoryNodes.organizationId, scope.organizationId), eq(principalHistoryNodes.hash, node.hash)))
188
- .run();
189
- await tx
190
- .delete(principalHistoryNodeReferences)
191
- .where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), eq(principalHistoryNodeReferences.hash, node.hash)))
192
- .run();
193
- for (const hash of [node.leftHash, node.rightHash])
194
- if (hash)
195
- await decrementPrincipalHistoryNodeReference(tx, scope, hash);
196
- reclaimed += 1;
197
- }
236
+ await releaseObsoleteNodes(tx, scope, obsolete);
237
+ reclaimed += obsolete.length;
198
238
  }
199
239
  return reclaimed;
200
240
  }
@@ -1 +1 @@
1
- {"version":3,"file":"principalHistoryNodeRetention.js","sourceRoot":"","sources":["principalHistoryNodeRetention.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalHistoryIndexNode } from \"@tearleads/crypto\";\nimport { and, asc, eq, inArray } from \"drizzle-orm\";\nimport { principalHistoryNodes } from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeReferences } from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport {\n decrementPrincipalHistoryNodeReference,\n type PrincipalHistoryNodeScope,\n} from \"./principalHistoryRootOwnership\";\n\ntype PrincipalHistoryNodeReference =\n typeof principalHistoryNodeReferences.$inferSelect;\n\nconst REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n principalHistoryNodeReferences,\n);\nconst NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);\n// The scope id binds once per lookup.\nconst HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n\nfunction batches<T>(items: readonly T[], size: number): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < items.length; index += size)\n result.push(items.slice(index, index + size));\n return result;\n}\n\nasync function loadNodeReferences(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n into: Map<string, PrincipalHistoryNodeReference | null>,\n): Promise<void> {\n const missing = [...new Set(hashes)].filter((hash) => !into.has(hash));\n for (const batch of batches(missing, HASH_LOOKUP_BATCH_SIZE)) {\n for (const hash of batch) into.set(hash, null);\n const rows = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n );\n for (const row of rows) into.set(row.hash, row);\n }\n}\n\nasync function loadStoredNodeHashes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n): Promise<Set<string>> {\n const stored = new Set<string>();\n for (const batch of batches([...new Set(hashes)], HASH_LOOKUP_BATCH_SIZE)) {\n const rows = await tx\n .select({ hash: principalHistoryNodes.hash })\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n );\n for (const row of rows) stored.add(row.hash);\n }\n return stored;\n}\n\n/**\n * Replays the per-node edge bookkeeping in memory: increment inserts or adds\n * one, decrement floors at zero within the scope's organization, and an edge\n * change keeps the count while marking nodes first stored here as managed.\n */\nfunction replayNodeEdges(\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n loaded: ReadonlyMap<string, PrincipalHistoryNodeReference | null>,\n storedNodeHashes: Set<string>,\n): Map<string, PrincipalHistoryNodeReference | null> {\n const references = new Map(loaded);\n const read = (hash: string) => {\n const reference = references.get(hash);\n if (reference === undefined)\n throw new Error(\"Principal history node reference was not loaded\");\n return reference;\n };\n const increment = (hash: string) => {\n const reference = read(hash);\n references.set(\n hash,\n reference\n ? { ...reference, referenceCount: reference.referenceCount + 1 }\n : {\n ...scope,\n hash,\n referenceCount: 1,\n managed: false,\n leftHash: null,\n rightHash: null,\n },\n );\n };\n const decrement = (hash: string) => {\n const reference = read(hash);\n if (reference?.organizationId === scope.organizationId)\n references.set(hash, {\n ...reference,\n referenceCount: Math.max(reference.referenceCount - 1, 0),\n });\n };\n for (const node of nodes) {\n const previous = read(node.hash);\n if (\n previous?.leftHash !== node.leftHash ||\n previous.rightHash !== node.rightHash\n ) {\n const managed = previous?.managed || !storedNodeHashes.has(node.hash);\n increment(node.leftHash);\n increment(node.rightHash);\n const current = read(node.hash);\n references.set(node.hash, {\n ...(current ?? {\n ...scope,\n hash: node.hash,\n referenceCount: previous?.referenceCount ?? 0,\n }),\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n managed,\n });\n for (const hash of [previous?.leftHash, previous?.rightHash])\n if (hash) decrement(hash);\n }\n storedNodeHashes.add(node.hash);\n }\n return references;\n}\n\nfunction isSameNodeReference(\n left: PrincipalHistoryNodeReference,\n right: PrincipalHistoryNodeReference,\n): boolean {\n return (\n left.organizationId === right.organizationId &&\n left.referenceCount === right.referenceCount &&\n left.managed === right.managed &&\n left.leftHash === right.leftHash &&\n left.rightHash === right.rightHash\n );\n}\n\n/**\n * Register edges and store proof nodes in the given order, with the same\n * result as recording each node's edges and then upserting it. Reads the\n * touched liveness rows once, replays the bookkeeping, and writes the changed\n * rows and the nodes as multi-row upserts.\n */\nexport async function writePrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n): Promise<void> {\n if (nodes.length === 0) return;\n const loaded = new Map<string, PrincipalHistoryNodeReference | null>();\n await loadNodeReferences(\n tx,\n scope,\n nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]),\n loaded,\n );\n // An edge change releases the stored row's previous children.\n await loadNodeReferences(\n tx,\n scope,\n nodes.flatMap((node) => {\n const stored = loaded.get(node.hash);\n return [stored?.leftHash, stored?.rightHash].filter(\n (hash): hash is string => Boolean(hash),\n );\n }),\n loaded,\n );\n const replayed = replayNodeEdges(\n scope,\n nodes,\n loaded,\n await loadStoredNodeHashes(\n tx,\n scope,\n nodes.map((node) => node.hash),\n ),\n );\n const changed = [...replayed].flatMap(([hash, reference]) => {\n const before = loaded.get(hash);\n return reference && !(before && isSameNodeReference(before, reference))\n ? [reference]\n : [];\n });\n for (const batch of batches(changed, REFERENCE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodeReferences)\n .values(batch)\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n referenceCount: excludedValue(\n principalHistoryNodeReferences.referenceCount,\n ),\n managed: excludedValue(principalHistoryNodeReferences.managed),\n leftHash: excludedValue(principalHistoryNodeReferences.leftHash),\n rightHash: excludedValue(principalHistoryNodeReferences.rightHash),\n },\n })\n .run();\n const nodeRows = new Map(\n nodes.map((node) => [\n node.hash,\n {\n ...scope,\n hash: node.hash,\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n },\n ]),\n );\n for (const batch of batches([...nodeRows.values()], NODE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodes)\n .values(batch)\n .onConflictDoUpdate({\n target: [principalHistoryNodes.scopeId, principalHistoryNodes.hash],\n set: {\n organizationId: excludedValue(principalHistoryNodes.organizationId),\n leftHash: excludedValue(principalHistoryNodes.leftHash),\n rightHash: excludedValue(principalHistoryNodes.rightHash),\n },\n })\n .run();\n}\n\n/** A bounded cascade releases only managed proof nodes with no live incoming edge. */\nexport async function reclaimPrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n): Promise<number> {\n let reclaimed = 0;\n while (reclaimed < 64) {\n const obsolete = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(\n principalHistoryNodeReferences.organizationId,\n scope.organizationId,\n ),\n eq(principalHistoryNodeReferences.managed, true),\n eq(principalHistoryNodeReferences.referenceCount, 0),\n ),\n )\n .orderBy(asc(principalHistoryNodeReferences.hash))\n .limit(64 - reclaimed);\n if (obsolete.length === 0) break;\n for (const node of obsolete) {\n await tx\n .delete(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n eq(principalHistoryNodes.organizationId, scope.organizationId),\n eq(principalHistoryNodes.hash, node.hash),\n ),\n )\n .run();\n await tx\n .delete(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(principalHistoryNodeReferences.hash, node.hash),\n ),\n )\n .run();\n for (const hash of [node.leftHash, node.rightHash])\n if (hash) await decrementPrincipalHistoryNodeReference(tx, scope, hash);\n reclaimed += 1;\n }\n }\n return reclaimed;\n}\n"]}
1
+ {"version":3,"file":"principalHistoryNodeRetention.js","sourceRoot":"","sources":["principalHistoryNodeRetention.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalHistoryIndexNode } from \"@tearleads/crypto\";\nimport { and, asc, eq, inArray, sql } from \"drizzle-orm\";\nimport { principalHistoryNodes } from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeReferences } from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { excludedValue } from \"../../sqlite/sqliteUpsert\";\nimport type {\n PrincipalHistoryNodeScope,\n PrincipalHistoryRootEdges,\n} from \"./principalHistoryRootOwnership\";\n\ntype PrincipalHistoryNodeReference =\n typeof principalHistoryNodeReferences.$inferSelect;\n\nconst REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(\n principalHistoryNodeReferences,\n);\nconst NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);\n// The scope id binds once per lookup.\nconst HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);\n// The scope and organization ids bind once per statement.\nconst SCOPED_HASH_BATCH_SIZE = sqliteRowsPerStatement(1, 3);\n\nfunction batches<T>(items: readonly T[], size: number): T[][] {\n const result: T[][] = [];\n for (let index = 0; index < items.length; index += size)\n result.push(items.slice(index, index + size));\n return result;\n}\n\nasync function loadNodeReferences(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n into: Map<string, PrincipalHistoryNodeReference | null>,\n): Promise<void> {\n const missing = [...new Set(hashes)].filter((hash) => !into.has(hash));\n for (const batch of batches(missing, HASH_LOOKUP_BATCH_SIZE)) {\n for (const hash of batch) into.set(hash, null);\n const rows = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n );\n for (const row of rows) into.set(row.hash, row);\n }\n}\n\nasync function loadStoredNodeHashes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hashes: readonly string[],\n): Promise<Set<string>> {\n const stored = new Set<string>();\n for (const batch of batches([...new Set(hashes)], HASH_LOOKUP_BATCH_SIZE)) {\n const rows = await tx\n .select({ hash: principalHistoryNodes.hash })\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n );\n for (const row of rows) stored.add(row.hash);\n }\n return stored;\n}\n\n/**\n * Replays the per-node edge bookkeeping in memory: increment inserts or adds\n * one, decrement floors at zero within the scope's organization, and an edge\n * change keeps the count while marking nodes first stored here as managed.\n * Folded root edges replay last, in the order root ownership applies them.\n */\nfunction replayNodeEdges(\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n loaded: ReadonlyMap<string, PrincipalHistoryNodeReference | null>,\n storedNodeHashes: Set<string>,\n rootEdges: PrincipalHistoryRootEdges | undefined,\n): Map<string, PrincipalHistoryNodeReference | null> {\n const references = new Map(loaded);\n const read = (hash: string) => {\n const reference = references.get(hash);\n if (reference === undefined)\n throw new Error(\"Principal history node reference was not loaded\");\n return reference;\n };\n const increment = (hash: string) => {\n const reference = read(hash);\n references.set(\n hash,\n reference\n ? { ...reference, referenceCount: reference.referenceCount + 1 }\n : {\n ...scope,\n hash,\n referenceCount: 1,\n managed: false,\n leftHash: null,\n rightHash: null,\n },\n );\n };\n const decrement = (hash: string) => {\n const reference = read(hash);\n if (reference?.organizationId === scope.organizationId)\n references.set(hash, {\n ...reference,\n referenceCount: Math.max(reference.referenceCount - 1, 0),\n });\n };\n for (const node of nodes) {\n const previous = read(node.hash);\n if (\n previous?.leftHash !== node.leftHash ||\n previous.rightHash !== node.rightHash\n ) {\n const managed = previous?.managed || !storedNodeHashes.has(node.hash);\n increment(node.leftHash);\n increment(node.rightHash);\n const current = read(node.hash);\n references.set(node.hash, {\n ...(current ?? {\n ...scope,\n hash: node.hash,\n referenceCount: previous?.referenceCount ?? 0,\n }),\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n managed,\n });\n for (const hash of [previous?.leftHash, previous?.rightHash])\n if (hash) decrement(hash);\n }\n storedNodeHashes.add(node.hash);\n }\n // Root ownership's edges follow the page's nodes, as its statements did.\n if (rootEdges) {\n increment(rootEdges.increment);\n if (rootEdges.decrement) decrement(rootEdges.decrement);\n }\n return references;\n}\n\nfunction isSameNodeReference(\n left: PrincipalHistoryNodeReference,\n right: PrincipalHistoryNodeReference,\n): boolean {\n return (\n left.organizationId === right.organizationId &&\n left.referenceCount === right.referenceCount &&\n left.managed === right.managed &&\n left.leftHash === right.leftHash &&\n left.rightHash === right.rightHash\n );\n}\n\n/**\n * Register edges and store proof nodes in the given order, with the same\n * result as recording each node's edges and then upserting it, followed by\n * any folded root ownership edges. Reads the touched liveness rows once,\n * replays the bookkeeping, and writes the changed rows and the nodes as\n * multi-row upserts.\n */\nexport async function writePrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n nodes: readonly PrincipalHistoryIndexNode[],\n rootEdges?: PrincipalHistoryRootEdges | undefined,\n): Promise<void> {\n if (nodes.length === 0 && !rootEdges) return;\n const loaded = new Map<string, PrincipalHistoryNodeReference | null>();\n await loadNodeReferences(\n tx,\n scope,\n [\n ...nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]),\n ...(rootEdges ? [rootEdges.increment] : []),\n ...(rootEdges?.decrement ? [rootEdges.decrement] : []),\n ],\n loaded,\n );\n // An edge change releases the stored row's previous children.\n await loadNodeReferences(\n tx,\n scope,\n nodes.flatMap((node) => {\n const stored = loaded.get(node.hash);\n return [stored?.leftHash, stored?.rightHash].filter(\n (hash): hash is string => Boolean(hash),\n );\n }),\n loaded,\n );\n const replayed = replayNodeEdges(\n scope,\n nodes,\n loaded,\n await loadStoredNodeHashes(\n tx,\n scope,\n nodes.map((node) => node.hash),\n ),\n rootEdges,\n );\n const changed = [...replayed].flatMap(([hash, reference]) => {\n const before = loaded.get(hash);\n return reference && !(before && isSameNodeReference(before, reference))\n ? [reference]\n : [];\n });\n for (const batch of batches(changed, REFERENCE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodeReferences)\n .values(batch)\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n referenceCount: excludedValue(\n principalHistoryNodeReferences.referenceCount,\n ),\n managed: excludedValue(principalHistoryNodeReferences.managed),\n leftHash: excludedValue(principalHistoryNodeReferences.leftHash),\n rightHash: excludedValue(principalHistoryNodeReferences.rightHash),\n },\n })\n .run();\n const nodeRows = new Map(\n nodes.map((node) => [\n node.hash,\n {\n ...scope,\n hash: node.hash,\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n },\n ]),\n );\n for (const batch of batches([...nodeRows.values()], NODE_WRITE_BATCH_SIZE))\n await tx\n .insert(principalHistoryNodes)\n .values(batch)\n .onConflictDoUpdate({\n target: [principalHistoryNodes.scopeId, principalHistoryNodes.hash],\n set: {\n organizationId: excludedValue(principalHistoryNodes.organizationId),\n leftHash: excludedValue(principalHistoryNodes.leftHash),\n rightHash: excludedValue(principalHistoryNodes.rightHash),\n },\n })\n .run();\n}\n\n/**\n * Deletes one cascade iteration's obsolete nodes and their liveness rows, then\n * releases their children: one decrement per distinct multiplicity, since\n * `max(count - k, 0)` equals k successive floored decrements. A child that is\n * itself obsolete in this iteration is deleted either way, as the per-node\n * loop deleted it before or after decrementing it.\n */\nasync function releaseObsoleteNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n obsolete: readonly PrincipalHistoryNodeReference[],\n): Promise<void> {\n const hashes = obsolete.map((node) => node.hash);\n for (const batch of batches(hashes, SCOPED_HASH_BATCH_SIZE)) {\n await tx\n .delete(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n eq(principalHistoryNodes.organizationId, scope.organizationId),\n inArray(principalHistoryNodes.hash, batch),\n ),\n )\n .run();\n await tx\n .delete(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n )\n .run();\n }\n const released = new Set(hashes);\n const multiplicity = new Map<string, number>();\n for (const node of obsolete)\n for (const hash of [node.leftHash, node.rightHash])\n if (hash && !released.has(hash))\n multiplicity.set(hash, (multiplicity.get(hash) ?? 0) + 1);\n const byCount = new Map<number, string[]>();\n for (const [hash, count] of multiplicity)\n byCount.set(count, [...(byCount.get(count) ?? []), hash]);\n for (const [count, children] of [...byCount].sort(([a], [b]) => a - b))\n for (const batch of batches(children.sort(), SCOPED_HASH_BATCH_SIZE))\n await tx\n .update(principalHistoryNodeReferences)\n .set({\n // `count` is a positive integer computed here, inlined like the literal 1.\n referenceCount: sql`max(${principalHistoryNodeReferences.referenceCount} - ${sql.raw(String(count))}, 0)`,\n })\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(\n principalHistoryNodeReferences.organizationId,\n scope.organizationId,\n ),\n inArray(principalHistoryNodeReferences.hash, batch),\n ),\n )\n .run();\n}\n\n/** A bounded cascade releases only managed proof nodes with no live incoming edge. */\nexport async function reclaimPrincipalHistoryNodes(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n): Promise<number> {\n let reclaimed = 0;\n while (reclaimed < 64) {\n const obsolete = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(\n principalHistoryNodeReferences.organizationId,\n scope.organizationId,\n ),\n eq(principalHistoryNodeReferences.managed, true),\n eq(principalHistoryNodeReferences.referenceCount, 0),\n ),\n )\n .orderBy(asc(principalHistoryNodeReferences.hash))\n .limit(64 - reclaimed);\n if (obsolete.length === 0) break;\n await releaseObsoleteNodes(tx, scope, obsolete);\n reclaimed += obsolete.length;\n }\n return reclaimed;\n}\n"]}
@@ -56,7 +56,12 @@ export async function savePrincipalHistoryPrefix(input) {
56
56
  // or explicitly replaced before repair can rely on the new prefix.
57
57
  prefix.progress = previous.progress;
58
58
  }
59
- if (previous)
59
+ // Archiving reads only these three fields, so an identical previous
60
+ // prefix would repeat the same idempotent write.
61
+ if (previous &&
62
+ (previous.organizationId !== prefix.organizationId ||
63
+ previous.currentJson !== prefix.currentJson ||
64
+ previous.version !== prefix.version))
60
65
  await archivePrincipalHistoryKeyEnvelopes(tx, previous);
61
66
  await archivePrincipalHistoryKeyEnvelopes(tx, prefix);
62
67
  await tx
@@ -1 +1 @@
1
- {"version":3,"file":"principalHistoryPrefixPersistence.js","sourceRoot":"","sources":["principalHistoryPrefixPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { assertProjectionVerificationCurrent } from \"../../keyingProjectionVerification/types\";\nimport {\n principalHistoryEvidenceTables,\n principalHistoryPrefixes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryStageTables } from \"../../sqlite/schema/principalHistoryStageSchema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { archivePrincipalHistoryKeyEnvelopes } from \"../principalKeys/principalKeyEnvelopeArchivePersistence\";\nimport { reclaimPrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport {\n releasePrincipalHistoryRoot,\n retainPrincipalHistoryRoot,\n} from \"./principalHistoryRootOwnership\";\nimport { reclaimCompletedPrincipalHistoryStages } from \"./principalHistoryStageRetention\";\n\nexport type PrincipalHistoryPrefix =\n typeof principalHistoryPrefixes.$inferSelect;\n\nexport async function loadPrincipalHistoryPrefix(\n execSql: ExecSql,\n scopeId: string,\n) {\n await ensureSqlTables(execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [prefix] = await db\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, scopeId))\n .limit(1);\n return prefix ?? null;\n}\n\n/**\n * Preserve identical completed progress; this sink does not authenticate hints.\n * Callers must discard an unreadable prefix or identify it with rejectedPrefix\n * before publishing a repair of the same head and current artifacts.\n */\nexport async function savePrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly indexRootHash: string;\n readonly stillCurrent: () => boolean;\n readonly rejectedPrefix?: PrincipalHistoryPrefix | null;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n const indexRootHash = input.indexRootHash;\n const rejected = input.rejectedPrefix ? { ...input.rejectedPrefix } : null;\n await ensureSqlTables(input.execSql, [\n ...principalHistoryEvidenceTables,\n ...principalHistoryStageTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [previous] = await tx\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n const replacesRejected =\n previous &&\n rejected?.scopeId === previous.scopeId &&\n rejected.progress === previous.progress;\n if (previous && previous.version > prefix.version && !replacesRejected)\n return;\n if (\n previous &&\n previous.version === prefix.version &&\n previous.organizationId === prefix.organizationId &&\n previous.headJson === prefix.headJson &&\n previous.currentJson === prefix.currentJson &&\n !replacesRejected\n ) {\n // Resealing an unchanged prefix would invalidate an acknowledgement's\n // progress CAS even though its authenticated predecessor is unchanged.\n // Still refresh authenticated root ownership and retention below.\n // Reuse authenticates this opaque progress; rejected hints are removed\n // or explicitly replaced before repair can rely on the new prefix.\n prefix.progress = previous.progress;\n }\n if (previous) await archivePrincipalHistoryKeyEnvelopes(tx, previous);\n await archivePrincipalHistoryKeyEnvelopes(tx, prefix);\n await tx\n .insert(principalHistoryPrefixes)\n .values(prefix)\n .onConflictDoUpdate({\n target: principalHistoryPrefixes.scopeId,\n set: prefix,\n })\n .run();\n await reclaimCompletedPrincipalHistoryStages(tx, prefix);\n await retainPrincipalHistoryRoot(tx, {\n id: `prefix:${prefix.scopeId}`,\n scopeId: prefix.scopeId,\n organizationId: prefix.organizationId,\n rootHash: indexRootHash,\n });\n await reclaimPrincipalHistoryNodes(tx, prefix);\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n\n/** Invalid hints are disposable, but a losing reader must preserve a newer hint. */\nexport async function discardPrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [current] = await tx\n .select({ progress: principalHistoryPrefixes.progress })\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n if (current?.progress !== prefix.progress) return;\n await releasePrincipalHistoryRoot(tx, `prefix:${prefix.scopeId}`);\n await tx\n .delete(principalHistoryPrefixes)\n .where(\n and(\n eq(principalHistoryPrefixes.scopeId, prefix.scopeId),\n eq(principalHistoryPrefixes.progress, prefix.progress),\n ),\n )\n .run();\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n"]}
1
+ {"version":3,"file":"principalHistoryPrefixPersistence.js","sourceRoot":"","sources":["principalHistoryPrefixPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { assertProjectionVerificationCurrent } from \"../../keyingProjectionVerification/types\";\nimport {\n principalHistoryEvidenceTables,\n principalHistoryPrefixes,\n} from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryStageTables } from \"../../sqlite/schema/principalHistoryStageSchema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { archivePrincipalHistoryKeyEnvelopes } from \"../principalKeys/principalKeyEnvelopeArchivePersistence\";\nimport { reclaimPrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport {\n releasePrincipalHistoryRoot,\n retainPrincipalHistoryRoot,\n} from \"./principalHistoryRootOwnership\";\nimport { reclaimCompletedPrincipalHistoryStages } from \"./principalHistoryStageRetention\";\n\nexport type PrincipalHistoryPrefix =\n typeof principalHistoryPrefixes.$inferSelect;\n\nexport async function loadPrincipalHistoryPrefix(\n execSql: ExecSql,\n scopeId: string,\n) {\n await ensureSqlTables(execSql, principalHistoryEvidenceTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [prefix] = await db\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, scopeId))\n .limit(1);\n return prefix ?? null;\n}\n\n/**\n * Preserve identical completed progress; this sink does not authenticate hints.\n * Callers must discard an unreadable prefix or identify it with rejectedPrefix\n * before publishing a repair of the same head and current artifacts.\n */\nexport async function savePrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly indexRootHash: string;\n readonly stillCurrent: () => boolean;\n readonly rejectedPrefix?: PrincipalHistoryPrefix | null;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n const indexRootHash = input.indexRootHash;\n const rejected = input.rejectedPrefix ? { ...input.rejectedPrefix } : null;\n await ensureSqlTables(input.execSql, [\n ...principalHistoryEvidenceTables,\n ...principalHistoryStageTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [previous] = await tx\n .select()\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n const replacesRejected =\n previous &&\n rejected?.scopeId === previous.scopeId &&\n rejected.progress === previous.progress;\n if (previous && previous.version > prefix.version && !replacesRejected)\n return;\n if (\n previous &&\n previous.version === prefix.version &&\n previous.organizationId === prefix.organizationId &&\n previous.headJson === prefix.headJson &&\n previous.currentJson === prefix.currentJson &&\n !replacesRejected\n ) {\n // Resealing an unchanged prefix would invalidate an acknowledgement's\n // progress CAS even though its authenticated predecessor is unchanged.\n // Still refresh authenticated root ownership and retention below.\n // Reuse authenticates this opaque progress; rejected hints are removed\n // or explicitly replaced before repair can rely on the new prefix.\n prefix.progress = previous.progress;\n }\n // Archiving reads only these three fields, so an identical previous\n // prefix would repeat the same idempotent write.\n if (\n previous &&\n (previous.organizationId !== prefix.organizationId ||\n previous.currentJson !== prefix.currentJson ||\n previous.version !== prefix.version)\n )\n await archivePrincipalHistoryKeyEnvelopes(tx, previous);\n await archivePrincipalHistoryKeyEnvelopes(tx, prefix);\n await tx\n .insert(principalHistoryPrefixes)\n .values(prefix)\n .onConflictDoUpdate({\n target: principalHistoryPrefixes.scopeId,\n set: prefix,\n })\n .run();\n await reclaimCompletedPrincipalHistoryStages(tx, prefix);\n await retainPrincipalHistoryRoot(tx, {\n id: `prefix:${prefix.scopeId}`,\n scopeId: prefix.scopeId,\n organizationId: prefix.organizationId,\n rootHash: indexRootHash,\n });\n await reclaimPrincipalHistoryNodes(tx, prefix);\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n\n/** Invalid hints are disposable, but a losing reader must preserve a newer hint. */\nexport async function discardPrincipalHistoryPrefix(input: {\n readonly execSql: ExecSql;\n readonly prefix: PrincipalHistoryPrefix;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const prefix = { ...input.prefix };\n await ensureSqlTables(input.execSql, principalHistoryEvidenceTables);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const result = await runtime.guardedTransaction(\n async (tx) => {\n const [current] = await tx\n .select({ progress: principalHistoryPrefixes.progress })\n .from(principalHistoryPrefixes)\n .where(eq(principalHistoryPrefixes.scopeId, prefix.scopeId))\n .limit(1);\n if (current?.progress !== prefix.progress) return;\n await releasePrincipalHistoryRoot(tx, `prefix:${prefix.scopeId}`);\n await tx\n .delete(principalHistoryPrefixes)\n .where(\n and(\n eq(principalHistoryPrefixes.scopeId, prefix.scopeId),\n eq(principalHistoryPrefixes.progress, prefix.progress),\n ),\n )\n .run();\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => result.committed);\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { principalHistoryRootOwners } from "../../sqlite/schema/principalHistoryNodeRetentionSchema.js";
2
+ import type { ClientSQLiteTransactionScope } from "../../sqlite/sqlitePersistenceRuntime.js";
3
+ export interface PrincipalHistoryNodeScope {
4
+ /** Evidence scope hashes include organizationId; one scope belongs to one organization. */
5
+ readonly scopeId: string;
6
+ readonly organizationId: string;
7
+ }
8
+ export declare function incrementPrincipalHistoryNodeReference(tx: ClientSQLiteTransactionScope, scope: PrincipalHistoryNodeScope, hash: string): Promise<void>;
9
+ export declare function decrementPrincipalHistoryNodeReference(tx: ClientSQLiteTransactionScope, scope: PrincipalHistoryNodeScope, hash: string): Promise<void>;
10
+ type PrincipalHistoryRootOwner = typeof principalHistoryRootOwners.$inferSelect;
11
+ type PrincipalHistoryRootInput = PrincipalHistoryNodeScope & {
12
+ readonly id: string;
13
+ readonly rootHash: string;
14
+ };
15
+ /** An owner whose root changes: the new root gains an edge, the old one loses it. */
16
+ export interface PrincipalHistoryRootRetention {
17
+ readonly input: PrincipalHistoryRootInput;
18
+ readonly previous: PrincipalHistoryRootOwner | undefined;
19
+ }
20
+ /** The root edges a node write replays itself, after its nodes and in this order. */
21
+ export interface PrincipalHistoryRootEdges {
22
+ readonly increment: string;
23
+ readonly decrement: string | null;
24
+ }
25
+ /** Null when the owner already holds this exact root, which changes nothing. */
26
+ export declare function loadPrincipalHistoryRootRetention(tx: ClientSQLiteTransactionScope, input: PrincipalHistoryRootInput): Promise<PrincipalHistoryRootRetention | null>;
27
+ /**
28
+ * The edges a write of `page`'s nodes can apply for this retention without
29
+ * reordering them: the increment only when the owner is in the page's scope,
30
+ * and the previous root's decrement only after it and in the same scope. A
31
+ * page without nodes writes no liveness rows, so nothing folds into it.
32
+ */
33
+ export declare function foldablePrincipalHistoryRootEdges(retention: PrincipalHistoryRootRetention | null, page: PrincipalHistoryNodeScope & {
34
+ readonly nodes: readonly unknown[];
35
+ }): PrincipalHistoryRootEdges | undefined;
36
+ /**
37
+ * Records the owner and applies the root edges `folded` did not already
38
+ * write. Caller commits ownership together with the exact saved progress it
39
+ * describes.
40
+ */
41
+ export declare function writePrincipalHistoryRootRetention(tx: ClientSQLiteTransactionScope, retention: PrincipalHistoryRootRetention | null, folded?: PrincipalHistoryRootEdges | undefined): Promise<void>;
42
+ /** Caller commits ownership together with the exact saved progress it describes. */
43
+ export declare function retainPrincipalHistoryRoot(tx: ClientSQLiteTransactionScope, input: PrincipalHistoryRootInput): Promise<void>;
44
+ export declare function releasePrincipalHistoryRoot(tx: ClientSQLiteTransactionScope, id: string): Promise<void>;
45
+ export {};
@@ -31,8 +31,8 @@ export async function decrementPrincipalHistoryNodeReference(tx, scope, hash) {
31
31
  .where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), eq(principalHistoryNodeReferences.organizationId, scope.organizationId), eq(principalHistoryNodeReferences.hash, hash)))
32
32
  .run();
33
33
  }
34
- /** Caller commits ownership together with the exact saved progress it describes. */
35
- export async function retainPrincipalHistoryRoot(tx, input) {
34
+ /** Null when the owner already holds this exact root, which changes nothing. */
35
+ export async function loadPrincipalHistoryRootRetention(tx, input) {
36
36
  const [previous] = await tx
37
37
  .select()
38
38
  .from(principalHistoryRootOwners)
@@ -41,8 +41,41 @@ export async function retainPrincipalHistoryRoot(tx, input) {
41
41
  if (previous?.scopeId === input.scopeId &&
42
42
  previous.organizationId === input.organizationId &&
43
43
  previous.rootHash === input.rootHash)
44
+ return null;
45
+ return { input: { ...input }, previous };
46
+ }
47
+ function sameScope(left, right) {
48
+ return (left.scopeId === right.scopeId &&
49
+ left.organizationId === right.organizationId);
50
+ }
51
+ /**
52
+ * The edges a write of `page`'s nodes can apply for this retention without
53
+ * reordering them: the increment only when the owner is in the page's scope,
54
+ * and the previous root's decrement only after it and in the same scope. A
55
+ * page without nodes writes no liveness rows, so nothing folds into it.
56
+ */
57
+ export function foldablePrincipalHistoryRootEdges(retention, page) {
58
+ if (!retention ||
59
+ page.nodes.length === 0 ||
60
+ !sameScope(retention.input, page))
61
+ return undefined;
62
+ const { previous } = retention;
63
+ return {
64
+ increment: retention.input.rootHash,
65
+ decrement: previous && sameScope(previous, page) ? previous.rootHash : null,
66
+ };
67
+ }
68
+ /**
69
+ * Records the owner and applies the root edges `folded` did not already
70
+ * write. Caller commits ownership together with the exact saved progress it
71
+ * describes.
72
+ */
73
+ export async function writePrincipalHistoryRootRetention(tx, retention, folded) {
74
+ if (!retention)
44
75
  return;
45
- await incrementPrincipalHistoryNodeReference(tx, input, input.rootHash);
76
+ const { input, previous } = retention;
77
+ if (!folded)
78
+ await incrementPrincipalHistoryNodeReference(tx, input, input.rootHash);
46
79
  await tx
47
80
  .insert(principalHistoryRootOwners)
48
81
  .values(input)
@@ -51,9 +84,13 @@ export async function retainPrincipalHistoryRoot(tx, input) {
51
84
  set: input,
52
85
  })
53
86
  .run();
54
- if (previous)
87
+ if (previous && !folded?.decrement)
55
88
  await decrementPrincipalHistoryNodeReference(tx, previous, previous.rootHash);
56
89
  }
90
+ /** Caller commits ownership together with the exact saved progress it describes. */
91
+ export async function retainPrincipalHistoryRoot(tx, input) {
92
+ await writePrincipalHistoryRootRetention(tx, await loadPrincipalHistoryRootRetention(tx, input));
93
+ }
57
94
  export async function releasePrincipalHistoryRoot(tx, id) {
58
95
  const [previous] = await tx
59
96
  .select()
@@ -1 +1 @@
1
- {"version":3,"file":"principalHistoryRootOwnership.js","sourceRoot":"","sources":["principalHistoryRootOwnership.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, sql } from \"drizzle-orm\";\nimport {\n principalHistoryNodeReferences,\n principalHistoryRootOwners,\n} from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\nexport interface PrincipalHistoryNodeScope {\n /** Evidence scope hashes include organizationId; one scope belongs to one organization. */\n readonly scopeId: string;\n readonly organizationId: string;\n}\n\nexport async function incrementPrincipalHistoryNodeReference(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hash: string,\n) {\n await tx\n .insert(principalHistoryNodeReferences)\n .values({\n ...scope,\n hash,\n referenceCount: 1,\n managed: false,\n leftHash: null,\n rightHash: null,\n })\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n referenceCount: sql`${principalHistoryNodeReferences.referenceCount} + 1`,\n },\n })\n .run();\n}\n\nexport async function decrementPrincipalHistoryNodeReference(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hash: string,\n) {\n await tx\n .update(principalHistoryNodeReferences)\n .set({\n referenceCount: sql`max(${principalHistoryNodeReferences.referenceCount} - 1, 0)`,\n })\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(principalHistoryNodeReferences.organizationId, scope.organizationId),\n eq(principalHistoryNodeReferences.hash, hash),\n ),\n )\n .run();\n}\n\n/** Caller commits ownership together with the exact saved progress it describes. */\nexport async function retainPrincipalHistoryRoot(\n tx: ClientSQLiteTransactionScope,\n input: PrincipalHistoryNodeScope & {\n readonly id: string;\n readonly rootHash: string;\n },\n) {\n const [previous] = await tx\n .select()\n .from(principalHistoryRootOwners)\n .where(eq(principalHistoryRootOwners.id, input.id))\n .limit(1);\n if (\n previous?.scopeId === input.scopeId &&\n previous.organizationId === input.organizationId &&\n previous.rootHash === input.rootHash\n )\n return;\n await incrementPrincipalHistoryNodeReference(tx, input, input.rootHash);\n await tx\n .insert(principalHistoryRootOwners)\n .values(input)\n .onConflictDoUpdate({\n target: principalHistoryRootOwners.id,\n set: input,\n })\n .run();\n if (previous)\n await decrementPrincipalHistoryNodeReference(\n tx,\n previous,\n previous.rootHash,\n );\n}\n\nexport async function releasePrincipalHistoryRoot(\n tx: ClientSQLiteTransactionScope,\n id: string,\n) {\n const [previous] = await tx\n .select()\n .from(principalHistoryRootOwners)\n .where(eq(principalHistoryRootOwners.id, id))\n .limit(1);\n if (!previous) return;\n await tx\n .delete(principalHistoryRootOwners)\n .where(eq(principalHistoryRootOwners.id, id))\n .run();\n await decrementPrincipalHistoryNodeReference(tx, previous, previous.rootHash);\n}\n"]}
1
+ {"version":3,"file":"principalHistoryRootOwnership.js","sourceRoot":"","sources":["principalHistoryRootOwnership.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, sql } from \"drizzle-orm\";\nimport {\n principalHistoryNodeReferences,\n principalHistoryRootOwners,\n} from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\n\nexport interface PrincipalHistoryNodeScope {\n /** Evidence scope hashes include organizationId; one scope belongs to one organization. */\n readonly scopeId: string;\n readonly organizationId: string;\n}\n\nexport async function incrementPrincipalHistoryNodeReference(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hash: string,\n) {\n await tx\n .insert(principalHistoryNodeReferences)\n .values({\n ...scope,\n hash,\n referenceCount: 1,\n managed: false,\n leftHash: null,\n rightHash: null,\n })\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n referenceCount: sql`${principalHistoryNodeReferences.referenceCount} + 1`,\n },\n })\n .run();\n}\n\nexport async function decrementPrincipalHistoryNodeReference(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n hash: string,\n) {\n await tx\n .update(principalHistoryNodeReferences)\n .set({\n referenceCount: sql`max(${principalHistoryNodeReferences.referenceCount} - 1, 0)`,\n })\n .where(\n and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(principalHistoryNodeReferences.organizationId, scope.organizationId),\n eq(principalHistoryNodeReferences.hash, hash),\n ),\n )\n .run();\n}\n\ntype PrincipalHistoryRootOwner = typeof principalHistoryRootOwners.$inferSelect;\n\ntype PrincipalHistoryRootInput = PrincipalHistoryNodeScope & {\n readonly id: string;\n readonly rootHash: string;\n};\n\n/** An owner whose root changes: the new root gains an edge, the old one loses it. */\nexport interface PrincipalHistoryRootRetention {\n readonly input: PrincipalHistoryRootInput;\n readonly previous: PrincipalHistoryRootOwner | undefined;\n}\n\n/** The root edges a node write replays itself, after its nodes and in this order. */\nexport interface PrincipalHistoryRootEdges {\n readonly increment: string;\n readonly decrement: string | null;\n}\n\n/** Null when the owner already holds this exact root, which changes nothing. */\nexport async function loadPrincipalHistoryRootRetention(\n tx: ClientSQLiteTransactionScope,\n input: PrincipalHistoryRootInput,\n): Promise<PrincipalHistoryRootRetention | null> {\n const [previous] = await tx\n .select()\n .from(principalHistoryRootOwners)\n .where(eq(principalHistoryRootOwners.id, input.id))\n .limit(1);\n if (\n previous?.scopeId === input.scopeId &&\n previous.organizationId === input.organizationId &&\n previous.rootHash === input.rootHash\n )\n return null;\n return { input: { ...input }, previous };\n}\n\nfunction sameScope(\n left: PrincipalHistoryNodeScope,\n right: PrincipalHistoryNodeScope,\n): boolean {\n return (\n left.scopeId === right.scopeId &&\n left.organizationId === right.organizationId\n );\n}\n\n/**\n * The edges a write of `page`'s nodes can apply for this retention without\n * reordering them: the increment only when the owner is in the page's scope,\n * and the previous root's decrement only after it and in the same scope. A\n * page without nodes writes no liveness rows, so nothing folds into it.\n */\nexport function foldablePrincipalHistoryRootEdges(\n retention: PrincipalHistoryRootRetention | null,\n page: PrincipalHistoryNodeScope & { readonly nodes: readonly unknown[] },\n): PrincipalHistoryRootEdges | undefined {\n if (\n !retention ||\n page.nodes.length === 0 ||\n !sameScope(retention.input, page)\n )\n return undefined;\n const { previous } = retention;\n return {\n increment: retention.input.rootHash,\n decrement: previous && sameScope(previous, page) ? previous.rootHash : null,\n };\n}\n\n/**\n * Records the owner and applies the root edges `folded` did not already\n * write. Caller commits ownership together with the exact saved progress it\n * describes.\n */\nexport async function writePrincipalHistoryRootRetention(\n tx: ClientSQLiteTransactionScope,\n retention: PrincipalHistoryRootRetention | null,\n folded?: PrincipalHistoryRootEdges | undefined,\n) {\n if (!retention) return;\n const { input, previous } = retention;\n if (!folded)\n await incrementPrincipalHistoryNodeReference(tx, input, input.rootHash);\n await tx\n .insert(principalHistoryRootOwners)\n .values(input)\n .onConflictDoUpdate({\n target: principalHistoryRootOwners.id,\n set: input,\n })\n .run();\n if (previous && !folded?.decrement)\n await decrementPrincipalHistoryNodeReference(\n tx,\n previous,\n previous.rootHash,\n );\n}\n\n/** Caller commits ownership together with the exact saved progress it describes. */\nexport async function retainPrincipalHistoryRoot(\n tx: ClientSQLiteTransactionScope,\n input: PrincipalHistoryRootInput,\n) {\n await writePrincipalHistoryRootRetention(\n tx,\n await loadPrincipalHistoryRootRetention(tx, input),\n );\n}\n\nexport async function releasePrincipalHistoryRoot(\n tx: ClientSQLiteTransactionScope,\n id: string,\n) {\n const [previous] = await tx\n .select()\n .from(principalHistoryRootOwners)\n .where(eq(principalHistoryRootOwners.id, id))\n .limit(1);\n if (!previous) return;\n await tx\n .delete(principalHistoryRootOwners)\n .where(eq(principalHistoryRootOwners.id, id))\n .run();\n await decrementPrincipalHistoryNodeReference(tx, previous, previous.rootHash);\n}\n"]}
@@ -8,7 +8,7 @@ import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
8
8
  import { writePrincipalHistoryEvidencePage, } from "./principalHistoryEvidencePersistence.js";
9
9
  import { reclaimIncompletePrincipalHistoryStages } from "./principalHistoryIncompleteRetention.js";
10
10
  import { reclaimPrincipalHistoryNodes } from "./principalHistoryNodeRetention.js";
11
- import { releasePrincipalHistoryRoot, retainPrincipalHistoryRoot, } from "./principalHistoryRootOwnership.js";
11
+ import { foldablePrincipalHistoryRootEdges, loadPrincipalHistoryRootRetention, releasePrincipalHistoryRoot, writePrincipalHistoryRootRetention, } from "./principalHistoryRootOwnership.js";
12
12
  import { reclaimUnpublishedPrincipalHistoryStages } from "./principalHistoryUnpublishedRetention.js";
13
13
  const principalHistoryStageColumns = {
14
14
  id: principalHistoryStages.id,
@@ -56,19 +56,23 @@ export async function savePrincipalHistoryStage(input) {
56
56
  .limit(1);
57
57
  if ((previous?.progress ?? null) !== input.previousProgress)
58
58
  throw new PrincipalHistoryStageChangedError();
59
- await writePrincipalHistoryEvidencePage(tx, evidence);
59
+ // The node write replays the root's edges after the page's own, as
60
+ // separate statements after it would.
61
+ const retention = await loadPrincipalHistoryRootRetention(tx, {
62
+ id: `stage:${stage.id}`,
63
+ scopeId: evidence.scopeId,
64
+ organizationId: stage.organizationId,
65
+ rootHash: evidence.indexRootHash,
66
+ });
67
+ const rootEdges = foldablePrincipalHistoryRootEdges(retention, evidence);
68
+ await writePrincipalHistoryEvidencePage(tx, evidence, rootEdges);
60
69
  const row = { ...stage, touchedAt: Date.now() };
61
70
  await tx
62
71
  .insert(principalHistoryStages)
63
72
  .values(row)
64
73
  .onConflictDoUpdate({ target: principalHistoryStages.id, set: row })
65
74
  .run();
66
- await retainPrincipalHistoryRoot(tx, {
67
- id: `stage:${stage.id}`,
68
- scopeId: evidence.scopeId,
69
- organizationId: stage.organizationId,
70
- rootHash: evidence.indexRootHash,
71
- });
75
+ await writePrincipalHistoryRootRetention(tx, retention, rootEdges);
72
76
  await reclaimIncompletePrincipalHistoryStages(tx, stage);
73
77
  await reclaimUnpublishedPrincipalHistoryStages(tx, stage);
74
78
  await reclaimPrincipalHistoryNodes(tx, evidence);
@@ -1 +1 @@
1
- {"version":3,"file":"principalHistoryStagePersistence.js","sourceRoot":"","sources":["principalHistoryStagePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { assertProjectionVerificationCurrent } from \"../../keyingProjectionVerification/types\";\nimport { principalHistoryEvidenceTables } from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeRetentionTables } from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport {\n principalHistoryStages,\n principalHistoryStageTables,\n} from \"../../sqlite/schema/principalHistoryStageSchema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n type PrincipalHistoryEvidencePage,\n writePrincipalHistoryEvidencePage,\n} from \"./principalHistoryEvidencePersistence\";\nimport { reclaimIncompletePrincipalHistoryStages } from \"./principalHistoryIncompleteRetention\";\nimport { reclaimPrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport {\n releasePrincipalHistoryRoot,\n retainPrincipalHistoryRoot,\n} from \"./principalHistoryRootOwnership\";\nimport { reclaimUnpublishedPrincipalHistoryStages } from \"./principalHistoryUnpublishedRetention\";\n\n/** A stage's verification work; its retention hints are written by persistence. */\nexport type PrincipalHistoryStage = Omit<\n typeof principalHistoryStages.$inferSelect,\n \"scopeId\" | \"touchedAt\"\n>;\n\nconst principalHistoryStageColumns = {\n id: principalHistoryStages.id,\n organizationId: principalHistoryStages.organizationId,\n currentJson: principalHistoryStages.currentJson,\n afterVersion: principalHistoryStages.afterVersion,\n complete: principalHistoryStages.complete,\n progress: principalHistoryStages.progress,\n};\n\nclass PrincipalHistoryStageChangedError extends Error {\n readonly code = \"principal_history_stage_changed\";\n constructor() {\n super(\n \"Another operation changed the saved principal history; resume from its current state\",\n );\n this.name = \"PrincipalHistoryStageChangedError\";\n }\n}\n\n/** Another writer replaced this stage's progress between load and save. */\nexport function isPrincipalHistoryStageChangedError(error: unknown): boolean {\n return error instanceof PrincipalHistoryStageChangedError;\n}\n\nexport async function loadPrincipalHistoryStage(\n execSql: ExecSql,\n id: string,\n): Promise<PrincipalHistoryStage | null> {\n await ensureSqlTables(execSql, principalHistoryStageTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [row] = await db\n .select(principalHistoryStageColumns)\n .from(principalHistoryStages)\n .where(eq(principalHistoryStages.id, id))\n .limit(1);\n return row ?? null;\n}\n\n/** Publish accepted entries and index nodes atomically with authenticated progress. */\nexport async function savePrincipalHistoryStage(input: {\n readonly execSql: ExecSql;\n readonly stage: PrincipalHistoryStage;\n readonly evidence: PrincipalHistoryEvidencePage;\n readonly previousProgress: string | null;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const evidence = structuredClone(input.evidence);\n const stage = { ...input.stage, scopeId: evidence.scopeId };\n await ensureSqlTables(input.execSql, [\n ...principalHistoryStageTables,\n ...principalHistoryEvidenceTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const saved = await runtime.guardedTransaction(\n async (tx) => {\n const [previous] = await tx\n .select({ progress: principalHistoryStages.progress })\n .from(principalHistoryStages)\n .where(eq(principalHistoryStages.id, stage.id))\n .limit(1);\n if ((previous?.progress ?? null) !== input.previousProgress)\n throw new PrincipalHistoryStageChangedError();\n await writePrincipalHistoryEvidencePage(tx, evidence);\n const row = { ...stage, touchedAt: Date.now() };\n await tx\n .insert(principalHistoryStages)\n .values(row)\n .onConflictDoUpdate({ target: principalHistoryStages.id, set: row })\n .run();\n await retainPrincipalHistoryRoot(tx, {\n id: `stage:${stage.id}`,\n scopeId: evidence.scopeId,\n organizationId: stage.organizationId,\n rootHash: evidence.indexRootHash,\n });\n await reclaimIncompletePrincipalHistoryStages(tx, stage);\n await reclaimUnpublishedPrincipalHistoryStages(tx, stage);\n await reclaimPrincipalHistoryNodes(tx, evidence);\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => saved.committed);\n}\n\nexport async function discardPrincipalHistoryStage(\n execSql: ExecSql,\n stage: PrincipalHistoryStage,\n stillCurrent: () => boolean,\n) {\n await ensureSqlTables(execSql, [\n ...principalHistoryStageTables,\n ...principalHistoryNodeRetentionTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(execSql);\n const discarded = await runtime.guardedTransaction(\n async (db) => {\n const [current] = await db\n .select({ progress: principalHistoryStages.progress })\n .from(principalHistoryStages)\n .where(eq(principalHistoryStages.id, stage.id))\n .limit(1);\n if (current?.progress !== stage.progress) return;\n await releasePrincipalHistoryRoot(db, `stage:${stage.id}`);\n await db\n .delete(principalHistoryStages)\n .where(\n and(\n eq(principalHistoryStages.id, stage.id),\n eq(principalHistoryStages.progress, stage.progress),\n ),\n )\n .run();\n },\n stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => discarded.committed);\n}\n"]}
1
+ {"version":3,"file":"principalHistoryStagePersistence.js","sourceRoot":"","sources":["principalHistoryStagePersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { assertProjectionVerificationCurrent } from \"../../keyingProjectionVerification/types\";\nimport { principalHistoryEvidenceTables } from \"../../sqlite/schema/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeRetentionTables } from \"../../sqlite/schema/principalHistoryNodeRetentionSchema\";\nimport {\n principalHistoryStages,\n principalHistoryStageTables,\n} from \"../../sqlite/schema/principalHistoryStageSchema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport {\n type PrincipalHistoryEvidencePage,\n writePrincipalHistoryEvidencePage,\n} from \"./principalHistoryEvidencePersistence\";\nimport { reclaimIncompletePrincipalHistoryStages } from \"./principalHistoryIncompleteRetention\";\nimport { reclaimPrincipalHistoryNodes } from \"./principalHistoryNodeRetention\";\nimport {\n foldablePrincipalHistoryRootEdges,\n loadPrincipalHistoryRootRetention,\n releasePrincipalHistoryRoot,\n writePrincipalHistoryRootRetention,\n} from \"./principalHistoryRootOwnership\";\nimport { reclaimUnpublishedPrincipalHistoryStages } from \"./principalHistoryUnpublishedRetention\";\n\n/** A stage's verification work; its retention hints are written by persistence. */\nexport type PrincipalHistoryStage = Omit<\n typeof principalHistoryStages.$inferSelect,\n \"scopeId\" | \"touchedAt\"\n>;\n\nconst principalHistoryStageColumns = {\n id: principalHistoryStages.id,\n organizationId: principalHistoryStages.organizationId,\n currentJson: principalHistoryStages.currentJson,\n afterVersion: principalHistoryStages.afterVersion,\n complete: principalHistoryStages.complete,\n progress: principalHistoryStages.progress,\n};\n\nclass PrincipalHistoryStageChangedError extends Error {\n readonly code = \"principal_history_stage_changed\";\n constructor() {\n super(\n \"Another operation changed the saved principal history; resume from its current state\",\n );\n this.name = \"PrincipalHistoryStageChangedError\";\n }\n}\n\n/** Another writer replaced this stage's progress between load and save. */\nexport function isPrincipalHistoryStageChangedError(error: unknown): boolean {\n return error instanceof PrincipalHistoryStageChangedError;\n}\n\nexport async function loadPrincipalHistoryStage(\n execSql: ExecSql,\n id: string,\n): Promise<PrincipalHistoryStage | null> {\n await ensureSqlTables(execSql, principalHistoryStageTables);\n const { db } = getClientSQLitePersistenceRuntime(execSql);\n const [row] = await db\n .select(principalHistoryStageColumns)\n .from(principalHistoryStages)\n .where(eq(principalHistoryStages.id, id))\n .limit(1);\n return row ?? null;\n}\n\n/** Publish accepted entries and index nodes atomically with authenticated progress. */\nexport async function savePrincipalHistoryStage(input: {\n readonly execSql: ExecSql;\n readonly stage: PrincipalHistoryStage;\n readonly evidence: PrincipalHistoryEvidencePage;\n readonly previousProgress: string | null;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const evidence = structuredClone(input.evidence);\n const stage = { ...input.stage, scopeId: evidence.scopeId };\n await ensureSqlTables(input.execSql, [\n ...principalHistoryStageTables,\n ...principalHistoryEvidenceTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(input.execSql);\n const saved = await runtime.guardedTransaction(\n async (tx) => {\n const [previous] = await tx\n .select({ progress: principalHistoryStages.progress })\n .from(principalHistoryStages)\n .where(eq(principalHistoryStages.id, stage.id))\n .limit(1);\n if ((previous?.progress ?? null) !== input.previousProgress)\n throw new PrincipalHistoryStageChangedError();\n // The node write replays the root's edges after the page's own, as\n // separate statements after it would.\n const retention = await loadPrincipalHistoryRootRetention(tx, {\n id: `stage:${stage.id}`,\n scopeId: evidence.scopeId,\n organizationId: stage.organizationId,\n rootHash: evidence.indexRootHash,\n });\n const rootEdges = foldablePrincipalHistoryRootEdges(retention, evidence);\n await writePrincipalHistoryEvidencePage(tx, evidence, rootEdges);\n const row = { ...stage, touchedAt: Date.now() };\n await tx\n .insert(principalHistoryStages)\n .values(row)\n .onConflictDoUpdate({ target: principalHistoryStages.id, set: row })\n .run();\n await writePrincipalHistoryRootRetention(tx, retention, rootEdges);\n await reclaimIncompletePrincipalHistoryStages(tx, stage);\n await reclaimUnpublishedPrincipalHistoryStages(tx, stage);\n await reclaimPrincipalHistoryNodes(tx, evidence);\n },\n input.stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => saved.committed);\n}\n\nexport async function discardPrincipalHistoryStage(\n execSql: ExecSql,\n stage: PrincipalHistoryStage,\n stillCurrent: () => boolean,\n) {\n await ensureSqlTables(execSql, [\n ...principalHistoryStageTables,\n ...principalHistoryNodeRetentionTables,\n ]);\n const runtime = getClientSQLitePersistenceRuntime(execSql);\n const discarded = await runtime.guardedTransaction(\n async (db) => {\n const [current] = await db\n .select({ progress: principalHistoryStages.progress })\n .from(principalHistoryStages)\n .where(eq(principalHistoryStages.id, stage.id))\n .limit(1);\n if (current?.progress !== stage.progress) return;\n await releasePrincipalHistoryRoot(db, `stage:${stage.id}`);\n await db\n .delete(principalHistoryStages)\n .where(\n and(\n eq(principalHistoryStages.id, stage.id),\n eq(principalHistoryStages.progress, stage.progress),\n ),\n )\n .run();\n },\n stillCurrent,\n { behavior: \"immediate\" },\n );\n assertProjectionVerificationCurrent(() => discarded.committed);\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import { KeyingVerificationError, } from "../../../internal/crypto/index.js";
2
- import { and, eq } from "drizzle-orm";
2
+ import { and, eq, inArray } from "drizzle-orm";
3
3
  import { principalGrantRetirements } from "../../sqlite/schema/principalGrantRetirementSchema.js";
4
+ import { sqliteRowsPerStatement } from "../../sqlite/sqliteBindBudget.js";
4
5
  export async function storePrincipalGrantRetirements(tx, retirement, policies, organizationId) {
5
6
  if (!policies.some((policy) => policy.principalId === retirement.policy.principalId &&
6
7
  policy.principalType === retirement.policy.principalType &&
@@ -40,4 +41,37 @@ export async function assertContainerNotRetired(tx, checkpoint) {
40
41
  if (retired)
41
42
  throw new KeyingVerificationError("equivocation", `Container ${checkpoint.objectId} was retired during principal rotation`);
42
43
  }
44
+ // A lookup binds its organization once.
45
+ const RETIREMENT_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
46
+ function retiredContainerKey(organizationId, containerId) {
47
+ return JSON.stringify([organizationId, containerId]);
48
+ }
49
+ /**
50
+ * The set form of `assertContainerNotRetired` for a batch of checkpoints: one
51
+ * read per organization. The returned check throws the same error for each
52
+ * checkpoint, so a caller replaying its batch in order fails at the same one.
53
+ */
54
+ export async function loadContainerRetirementCheck(tx, checkpoints) {
55
+ const idsByOrganization = new Map();
56
+ for (const checkpoint of checkpoints)
57
+ if (checkpoint.objectKind === "container")
58
+ idsByOrganization.set(checkpoint.organizationId, (idsByOrganization.get(checkpoint.organizationId) ?? new Set()).add(checkpoint.objectId));
59
+ const retired = new Set();
60
+ for (const [organizationId, ids] of idsByOrganization) {
61
+ const all = [...ids];
62
+ for (let index = 0; index < all.length; index += RETIREMENT_LOOKUP_BATCH_SIZE) {
63
+ const rows = await tx
64
+ .select({ containerId: principalGrantRetirements.containerId })
65
+ .from(principalGrantRetirements)
66
+ .where(and(eq(principalGrantRetirements.organizationId, organizationId), inArray(principalGrantRetirements.containerId, all.slice(index, index + RETIREMENT_LOOKUP_BATCH_SIZE))));
67
+ for (const row of rows)
68
+ retired.add(retiredContainerKey(organizationId, row.containerId));
69
+ }
70
+ }
71
+ return (checkpoint) => {
72
+ if (checkpoint.objectKind === "container" &&
73
+ retired.has(retiredContainerKey(checkpoint.organizationId, checkpoint.objectId)))
74
+ throw new KeyingVerificationError("equivocation", `Container ${checkpoint.objectId} was retired during principal rotation`);
75
+ };
76
+ }
43
77
  //# sourceMappingURL=principalGrantRetirementPersistence.js.map