@tearleads/client-sdk 0.1.47 → 0.1.49

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 (56) hide show
  1. package/client/organizations/currentPrincipalMutations.js +36 -17
  2. package/client/organizations/currentPrincipalMutations.js.map +1 -1
  3. package/client/organizations/principalContainerMutations.js +1 -0
  4. package/client/organizations/principalContainerMutations.js.map +1 -1
  5. package/client/organizations/principalMutations.js +2 -2
  6. package/client/organizations/principalMutations.js.map +1 -1
  7. package/data/persistence/documents/internal/documentRows.js +37 -41
  8. package/data/persistence/documents/internal/documentRows.js.map +1 -1
  9. package/data/persistence/principalHistoryEvidencePersistence.js +17 -18
  10. package/data/persistence/principalHistoryEvidencePersistence.js.map +1 -1
  11. package/data/persistence/principalHistoryNodeRetention.js +164 -39
  12. package/data/persistence/principalHistoryNodeRetention.js.map +1 -1
  13. package/data/persistence/principalPolicyReferenceIndexPersistence.js +101 -38
  14. package/data/persistence/principalPolicyReferenceIndexPersistence.js.map +1 -1
  15. package/data/principals/principalMutationJournalShape.js +6 -0
  16. package/data/principals/principalMutationJournalShape.js.map +1 -1
  17. package/data/sqlite/sqliteUpsert.js +9 -0
  18. package/data/sqlite/sqliteUpsert.js.map +1 -0
  19. package/internal/api-client/ApiClient.d.ts +122 -0
  20. package/internal/api-client/principalPolicyRequests.d.ts +122 -0
  21. package/internal/validators/operation/principals.d.ts +1026 -206
  22. package/internal/validators/request/principal.d.ts +332 -0
  23. package/internal/validators/request/principal.js +1 -0
  24. package/internal/validators/request/principal.js.map +1 -1
  25. package/internal/validators/response/principal.d.ts +488 -0
  26. package/internal/validators/response/principal.js +1 -0
  27. package/internal/validators/response/principal.js.map +1 -1
  28. package/package.json +1 -1
  29. package/workflows/containers/child/rekeyProjection.d.ts +6 -1
  30. package/workflows/containers/child/rekeyProjection.js +41 -0
  31. package/workflows/containers/child/rekeyProjection.js.map +1 -1
  32. package/workflows/organizations/authoredPrincipalPolicyWarmer.js +25 -0
  33. package/workflows/organizations/authoredPrincipalPolicyWarmer.js.map +1 -0
  34. package/workflows/organizations/commitCurrentGroupPolicyMutation.js +101 -34
  35. package/workflows/organizations/commitCurrentGroupPolicyMutation.js.map +1 -1
  36. package/workflows/organizations/groupPolicyMutationContext.d.ts +5 -52
  37. package/workflows/organizations/groupPolicyMutationContext.js +2 -65
  38. package/workflows/organizations/groupPolicyMutationContext.js.map +1 -1
  39. package/workflows/organizations/precedingPrincipalRematerialization.js +31 -0
  40. package/workflows/organizations/precedingPrincipalRematerialization.js.map +1 -0
  41. package/workflows/organizations/principalContainerRematerialization.js +39 -7
  42. package/workflows/organizations/principalContainerRematerialization.js.map +1 -1
  43. package/workflows/organizations/principalContainerRematerializationPlanning.js +6 -1
  44. package/workflows/organizations/principalContainerRematerializationPlanning.js.map +1 -1
  45. package/workflows/organizations/principalContainerRematerializationTargets.js +0 -11
  46. package/workflows/organizations/principalContainerRematerializationTargets.js.map +1 -1
  47. package/workflows/organizations/principalMutationReceipt.js +6 -0
  48. package/workflows/organizations/principalMutationReceipt.js.map +1 -1
  49. package/workflows/organizations/principalPolicyApi.d.ts +33 -0
  50. package/workflows/organizations/principalPolicyApi.js +2 -0
  51. package/workflows/organizations/principalPolicyApi.js.map +1 -0
  52. package/workflows/organizations/runtimeCurrentGroupMutation.js +36 -19
  53. package/workflows/organizations/runtimeCurrentGroupMutation.js.map +1 -1
  54. package/workflows/organizations/submitGroupPolicyCommit.d.ts +22 -0
  55. package/workflows/organizations/submitGroupPolicyCommit.js +71 -0
  56. package/workflows/organizations/submitGroupPolicyCommit.js.map +1 -0
@@ -1,48 +1,173 @@
1
- import { and, asc, eq } from "drizzle-orm";
1
+ import { and, asc, eq, inArray } from "drizzle-orm";
2
2
  import { principalHistoryNodes } from "../sqlite/principalHistoryEvidenceSchema.js";
3
3
  import { principalHistoryNodeReferences } from "../sqlite/principalHistoryNodeRetentionSchema.js";
4
- import { decrementPrincipalHistoryNodeReference, incrementPrincipalHistoryNodeReference, } from "./principalHistoryRootOwnership.js";
5
- /** Register edges before inserting their proof node, preserving pre-index evidence. */
6
- export async function recordPrincipalHistoryNodeEdges(tx, scope, node) {
7
- const key = and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), eq(principalHistoryNodeReferences.hash, node.hash));
8
- const [previous] = await tx
9
- .select()
10
- .from(principalHistoryNodeReferences)
11
- .where(key)
12
- .limit(1);
13
- if (previous?.leftHash === node.leftHash &&
14
- previous.rightHash === node.rightHash)
4
+ import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../sqlite/sqliteBindBudget.js";
5
+ import { excludedValue } from "../sqlite/sqliteUpsert.js";
6
+ import { decrementPrincipalHistoryNodeReference, } from "./principalHistoryRootOwnership.js";
7
+ const REFERENCE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodeReferences);
8
+ const NODE_WRITE_BATCH_SIZE = sqliteInsertBatchSize(principalHistoryNodes);
9
+ // The scope id binds once per lookup.
10
+ const HASH_LOOKUP_BATCH_SIZE = sqliteRowsPerStatement(1, 1);
11
+ function batches(items, size) {
12
+ const result = [];
13
+ for (let index = 0; index < items.length; index += size)
14
+ result.push(items.slice(index, index + size));
15
+ return result;
16
+ }
17
+ async function loadNodeReferences(tx, scope, hashes, into) {
18
+ const missing = [...new Set(hashes)].filter((hash) => !into.has(hash));
19
+ for (const batch of batches(missing, HASH_LOOKUP_BATCH_SIZE)) {
20
+ for (const hash of batch)
21
+ into.set(hash, null);
22
+ const rows = await tx
23
+ .select()
24
+ .from(principalHistoryNodeReferences)
25
+ .where(and(eq(principalHistoryNodeReferences.scopeId, scope.scopeId), inArray(principalHistoryNodeReferences.hash, batch)));
26
+ for (const row of rows)
27
+ into.set(row.hash, row);
28
+ }
29
+ }
30
+ async function loadStoredNodeHashes(tx, scope, hashes) {
31
+ const stored = new Set();
32
+ for (const batch of batches([...new Set(hashes)], HASH_LOOKUP_BATCH_SIZE)) {
33
+ const rows = await tx
34
+ .select({ hash: principalHistoryNodes.hash })
35
+ .from(principalHistoryNodes)
36
+ .where(and(eq(principalHistoryNodes.scopeId, scope.scopeId), inArray(principalHistoryNodes.hash, batch)));
37
+ for (const row of rows)
38
+ stored.add(row.hash);
39
+ }
40
+ return stored;
41
+ }
42
+ /**
43
+ * Replays the per-node edge bookkeeping in memory: increment inserts or adds
44
+ * one, decrement floors at zero within the scope's organization, and an edge
45
+ * change keeps the count while marking nodes first stored here as managed.
46
+ */
47
+ function replayNodeEdges(scope, nodes, loaded, storedNodeHashes) {
48
+ const references = new Map(loaded);
49
+ const read = (hash) => {
50
+ const reference = references.get(hash);
51
+ if (reference === undefined)
52
+ throw new Error("Principal history node reference was not loaded");
53
+ return reference;
54
+ };
55
+ const increment = (hash) => {
56
+ const reference = read(hash);
57
+ references.set(hash, reference
58
+ ? { ...reference, referenceCount: reference.referenceCount + 1 }
59
+ : {
60
+ ...scope,
61
+ hash,
62
+ referenceCount: 1,
63
+ managed: false,
64
+ leftHash: null,
65
+ rightHash: null,
66
+ });
67
+ };
68
+ const decrement = (hash) => {
69
+ const reference = read(hash);
70
+ if (reference?.organizationId === scope.organizationId)
71
+ references.set(hash, {
72
+ ...reference,
73
+ referenceCount: Math.max(reference.referenceCount - 1, 0),
74
+ });
75
+ };
76
+ for (const node of nodes) {
77
+ const previous = read(node.hash);
78
+ if (previous?.leftHash !== node.leftHash ||
79
+ previous.rightHash !== node.rightHash) {
80
+ const managed = previous?.managed || !storedNodeHashes.has(node.hash);
81
+ increment(node.leftHash);
82
+ increment(node.rightHash);
83
+ const current = read(node.hash);
84
+ references.set(node.hash, {
85
+ ...(current ?? {
86
+ ...scope,
87
+ hash: node.hash,
88
+ referenceCount: previous?.referenceCount ?? 0,
89
+ }),
90
+ leftHash: node.leftHash,
91
+ rightHash: node.rightHash,
92
+ managed,
93
+ });
94
+ for (const hash of [previous?.leftHash, previous?.rightHash])
95
+ if (hash)
96
+ decrement(hash);
97
+ }
98
+ storedNodeHashes.add(node.hash);
99
+ }
100
+ return references;
101
+ }
102
+ function isSameNodeReference(left, right) {
103
+ return (left.organizationId === right.organizationId &&
104
+ left.referenceCount === right.referenceCount &&
105
+ left.managed === right.managed &&
106
+ left.leftHash === right.leftHash &&
107
+ left.rightHash === right.rightHash);
108
+ }
109
+ /**
110
+ * 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.
114
+ */
115
+ export async function writePrincipalHistoryNodes(tx, scope, nodes) {
116
+ if (nodes.length === 0)
15
117
  return;
16
- const [existing] = await tx
17
- .select({ hash: principalHistoryNodes.hash })
18
- .from(principalHistoryNodes)
19
- .where(and(eq(principalHistoryNodes.scopeId, scope.scopeId), eq(principalHistoryNodes.hash, node.hash)))
20
- .limit(1);
21
- for (const hash of [node.leftHash, node.rightHash])
22
- await incrementPrincipalHistoryNodeReference(tx, scope, hash);
23
- await tx
24
- .insert(principalHistoryNodeReferences)
25
- .values({
26
- ...scope,
27
- ...node,
28
- referenceCount: previous?.referenceCount ?? 0,
29
- managed: previous?.managed || !existing,
30
- })
31
- .onConflictDoUpdate({
32
- target: [
33
- principalHistoryNodeReferences.scopeId,
34
- principalHistoryNodeReferences.hash,
35
- ],
36
- set: {
118
+ const loaded = new Map();
119
+ await loadNodeReferences(tx, scope, nodes.flatMap((node) => [node.hash, node.leftHash, node.rightHash]), loaded);
120
+ // An edge change releases the stored row's previous children.
121
+ await loadNodeReferences(tx, scope, nodes.flatMap((node) => {
122
+ const stored = loaded.get(node.hash);
123
+ return [stored?.leftHash, stored?.rightHash].filter((hash) => Boolean(hash));
124
+ }), loaded);
125
+ const replayed = replayNodeEdges(scope, nodes, loaded, await loadStoredNodeHashes(tx, scope, nodes.map((node) => node.hash)));
126
+ const changed = [...replayed].flatMap(([hash, reference]) => {
127
+ const before = loaded.get(hash);
128
+ return reference && !(before && isSameNodeReference(before, reference))
129
+ ? [reference]
130
+ : [];
131
+ });
132
+ for (const batch of batches(changed, REFERENCE_WRITE_BATCH_SIZE))
133
+ await tx
134
+ .insert(principalHistoryNodeReferences)
135
+ .values(batch)
136
+ .onConflictDoUpdate({
137
+ target: [
138
+ principalHistoryNodeReferences.scopeId,
139
+ principalHistoryNodeReferences.hash,
140
+ ],
141
+ set: {
142
+ referenceCount: excludedValue(principalHistoryNodeReferences.referenceCount),
143
+ managed: excludedValue(principalHistoryNodeReferences.managed),
144
+ leftHash: excludedValue(principalHistoryNodeReferences.leftHash),
145
+ rightHash: excludedValue(principalHistoryNodeReferences.rightHash),
146
+ },
147
+ })
148
+ .run();
149
+ const nodeRows = new Map(nodes.map((node) => [
150
+ node.hash,
151
+ {
152
+ ...scope,
153
+ hash: node.hash,
37
154
  leftHash: node.leftHash,
38
155
  rightHash: node.rightHash,
39
- managed: previous?.managed || !existing,
40
156
  },
41
- })
42
- .run();
43
- for (const hash of [previous?.leftHash, previous?.rightHash])
44
- if (hash)
45
- await decrementPrincipalHistoryNodeReference(tx, scope, hash);
157
+ ]));
158
+ for (const batch of batches([...nodeRows.values()], NODE_WRITE_BATCH_SIZE))
159
+ await tx
160
+ .insert(principalHistoryNodes)
161
+ .values(batch)
162
+ .onConflictDoUpdate({
163
+ target: [principalHistoryNodes.scopeId, principalHistoryNodes.hash],
164
+ set: {
165
+ organizationId: excludedValue(principalHistoryNodes.organizationId),
166
+ leftHash: excludedValue(principalHistoryNodes.leftHash),
167
+ rightHash: excludedValue(principalHistoryNodes.rightHash),
168
+ },
169
+ })
170
+ .run();
46
171
  }
47
172
  /** A bounded cascade releases only managed proof nodes with no live incoming edge. */
48
173
  export async function reclaimPrincipalHistoryNodes(tx, scope) {
@@ -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 } from \"drizzle-orm\";\nimport { principalHistoryNodes } from \"../sqlite/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeReferences } from \"../sqlite/principalHistoryNodeRetentionSchema\";\nimport type { ClientSQLiteTransactionScope } from \"../sqlite/sqlitePersistenceRuntime\";\nimport {\n decrementPrincipalHistoryNodeReference,\n incrementPrincipalHistoryNodeReference,\n type PrincipalHistoryNodeScope,\n} from \"./principalHistoryRootOwnership\";\n\n/** Register edges before inserting their proof node, preserving pre-index evidence. */\nexport async function recordPrincipalHistoryNodeEdges(\n tx: ClientSQLiteTransactionScope,\n scope: PrincipalHistoryNodeScope,\n node: PrincipalHistoryIndexNode,\n) {\n const key = and(\n eq(principalHistoryNodeReferences.scopeId, scope.scopeId),\n eq(principalHistoryNodeReferences.hash, node.hash),\n );\n const [previous] = await tx\n .select()\n .from(principalHistoryNodeReferences)\n .where(key)\n .limit(1);\n if (\n previous?.leftHash === node.leftHash &&\n previous.rightHash === node.rightHash\n )\n return;\n const [existing] = await tx\n .select({ hash: principalHistoryNodes.hash })\n .from(principalHistoryNodes)\n .where(\n and(\n eq(principalHistoryNodes.scopeId, scope.scopeId),\n eq(principalHistoryNodes.hash, node.hash),\n ),\n )\n .limit(1);\n for (const hash of [node.leftHash, node.rightHash])\n await incrementPrincipalHistoryNodeReference(tx, scope, hash);\n await tx\n .insert(principalHistoryNodeReferences)\n .values({\n ...scope,\n ...node,\n referenceCount: previous?.referenceCount ?? 0,\n managed: previous?.managed || !existing,\n })\n .onConflictDoUpdate({\n target: [\n principalHistoryNodeReferences.scopeId,\n principalHistoryNodeReferences.hash,\n ],\n set: {\n leftHash: node.leftHash,\n rightHash: node.rightHash,\n managed: previous?.managed || !existing,\n },\n })\n .run();\n for (const hash of [previous?.leftHash, previous?.rightHash])\n if (hash) await decrementPrincipalHistoryNodeReference(tx, scope, hash);\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 } from \"drizzle-orm\";\nimport { principalHistoryNodes } from \"../sqlite/principalHistoryEvidenceSchema\";\nimport { principalHistoryNodeReferences } from \"../sqlite/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,52 +1,115 @@
1
1
  import { KeyingVerificationError } from "../../internal/crypto/index.js";
2
- import { and, eq } from "drizzle-orm";
2
+ import { and, eq, inArray, or } from "drizzle-orm";
3
3
  import { principalPolicyBundleStates } from "../principalPolicyStates.js";
4
4
  import { principalPolicyBundleReferences } from "../sqlite/schema.js";
5
- function exactReferenceWhere(row) {
6
- return and(eq(principalPolicyBundleReferences.principalType, row.principalType), eq(principalPolicyBundleReferences.principalId, row.principalId), eq(principalPolicyBundleReferences.version, row.version), eq(principalPolicyBundleReferences.stateHash, row.stateHash), eq(principalPolicyBundleReferences.keyEpoch, row.keyEpoch), eq(principalPolicyBundleReferences.keyFingerprint, row.keyFingerprint));
5
+ import { sqliteInsertBatchSize, sqliteRowsPerStatement, } from "../sqlite/sqliteBindBudget.js";
6
+ const REFERENCE_INSERT_BATCH_SIZE = sqliteInsertBatchSize(principalPolicyBundleReferences);
7
+ // Principal type and id plus the bundle version and state hash bind once.
8
+ const REFERENCE_LOOKUP_VERSIONS = sqliteRowsPerStatement(1, 4);
9
+ /** The exact six-field match; the lookup already fixes the principal. */
10
+ function isSameReference(stored, row) {
11
+ return (stored.version === row.version &&
12
+ stored.stateHash === row.stateHash &&
13
+ stored.keyEpoch === row.keyEpoch &&
14
+ stored.keyFingerprint === row.keyFingerprint);
7
15
  }
8
- async function assertNoIndexedConflict(tx, row) {
9
- const conflicts = await tx
10
- .select({
11
- bundleStateHash: principalPolicyBundleReferences.bundleStateHash,
12
- })
13
- .from(principalPolicyBundleReferences)
14
- .where(and(exactReferenceWhere(row), eq(principalPolicyBundleReferences.bundleVersion, row.bundleVersion)));
15
- if (conflicts.some(({ bundleStateHash }) => bundleStateHash !== row.bundleStateHash)) {
16
- throw new KeyingVerificationError("equivocation", "principal policy reference has conflicting bundle heads at one version");
16
+ /** The table's primary key within one principal. */
17
+ function referenceKey(row) {
18
+ return JSON.stringify([
19
+ row.version,
20
+ row.stateHash,
21
+ row.keyEpoch,
22
+ row.keyFingerprint,
23
+ row.bundleStateHash,
24
+ ]);
25
+ }
26
+ /** Rows either equivocation check can match, read before this bundle's writes. */
27
+ async function loadStoredReferences(tx, head, versions) {
28
+ const stored = [];
29
+ for (let index = 0; index < versions.length; index += REFERENCE_LOOKUP_VERSIONS) {
30
+ stored.push(...(await tx
31
+ .select({
32
+ version: principalPolicyBundleReferences.version,
33
+ stateHash: principalPolicyBundleReferences.stateHash,
34
+ keyEpoch: principalPolicyBundleReferences.keyEpoch,
35
+ keyFingerprint: principalPolicyBundleReferences.keyFingerprint,
36
+ bundleVersion: principalPolicyBundleReferences.bundleVersion,
37
+ bundleStateHash: principalPolicyBundleReferences.bundleStateHash,
38
+ })
39
+ .from(principalPolicyBundleReferences)
40
+ .where(and(eq(principalPolicyBundleReferences.principalType, head.principalType), eq(principalPolicyBundleReferences.principalId, head.principalId), inArray(principalPolicyBundleReferences.version, versions.slice(index, index + REFERENCE_LOOKUP_VERSIONS)), or(eq(principalPolicyBundleReferences.bundleVersion, head.version), eq(principalPolicyBundleReferences.bundleStateHash, head.stateHash))))));
17
41
  }
42
+ return stored;
18
43
  }
19
- export async function indexPrincipalPolicyBundleInTransaction(tx, bundle) {
20
- const head = bundle.currentState;
21
- for (const state of principalPolicyBundleStates(bundle)) {
22
- if (state.principalType !== head.principalType ||
23
- state.principalId !== head.principalId) {
24
- throw new KeyingVerificationError("object_mismatch", "principal policy bundle reference belongs to another principal");
25
- }
26
- const row = {
27
- principalType: state.principalType,
28
- principalId: state.principalId,
29
- version: state.version,
30
- stateHash: state.stateHash,
31
- keyEpoch: state.keyEpoch,
32
- keyFingerprint: state.keyFingerprint,
33
- bundleVersion: head.version,
34
- bundleStateHash: head.stateHash,
35
- };
36
- await assertNoIndexedConflict(tx, row);
44
+ /**
45
+ * Rows written earlier in the same bundle all carry its head, so neither
46
+ * check can match them: each state's outcome depends only on stored rows.
47
+ */
48
+ function referenceConflict(row, stored) {
49
+ const sameReference = stored.filter((candidate) => isSameReference(candidate, row));
50
+ if (sameReference.some((candidate) => candidate.bundleVersion === row.bundleVersion &&
51
+ candidate.bundleStateHash !== row.bundleStateHash))
52
+ return new KeyingVerificationError("equivocation", "principal policy reference has conflicting bundle heads at one version");
53
+ const indexed = sameReference.find((candidate) => candidate.bundleStateHash === row.bundleStateHash);
54
+ if (indexed && indexed.bundleVersion !== row.bundleVersion)
55
+ return new KeyingVerificationError("equivocation", "principal policy reference index conflicts with the bundle head");
56
+ return null;
57
+ }
58
+ async function insertMissingReferences(tx, rows, stored) {
59
+ const present = new Set(stored.map(referenceKey));
60
+ const missing = new Map();
61
+ for (const row of rows) {
62
+ const key = referenceKey(row);
63
+ if (!present.has(key) && !missing.has(key))
64
+ missing.set(key, row);
65
+ }
66
+ const values = [...missing.values()];
67
+ for (let index = 0; index < values.length; index += REFERENCE_INSERT_BATCH_SIZE) {
37
68
  await tx
38
69
  .insert(principalPolicyBundleReferences)
39
- .values(row)
70
+ .values(values.slice(index, index + REFERENCE_INSERT_BATCH_SIZE))
40
71
  .onConflictDoNothing()
41
72
  .run();
42
- const [stored] = await tx
43
- .select({ bundleVersion: principalPolicyBundleReferences.bundleVersion })
44
- .from(principalPolicyBundleReferences)
45
- .where(and(exactReferenceWhere(row), eq(principalPolicyBundleReferences.bundleStateHash, row.bundleStateHash)))
46
- .limit(1);
47
- if (stored?.bundleVersion !== row.bundleVersion) {
48
- throw new KeyingVerificationError("equivocation", "principal policy reference index conflicts with the bundle head");
73
+ }
74
+ }
75
+ /**
76
+ * Index every state of a verified bundle under its head. Fails in state order
77
+ * with the per-state checks' errors, keeping the states indexed before the
78
+ * first failure.
79
+ */
80
+ export async function indexPrincipalPolicyBundleInTransaction(tx, bundle) {
81
+ const head = bundle.currentState;
82
+ const states = principalPolicyBundleStates(bundle);
83
+ const foreignIndex = states.findIndex((state) => state.principalType !== head.principalType ||
84
+ state.principalId !== head.principalId);
85
+ const rows = (foreignIndex === -1 ? states : states.slice(0, foreignIndex)).map((state) => ({
86
+ principalType: state.principalType,
87
+ principalId: state.principalId,
88
+ version: state.version,
89
+ stateHash: state.stateHash,
90
+ keyEpoch: state.keyEpoch,
91
+ keyFingerprint: state.keyFingerprint,
92
+ bundleVersion: head.version,
93
+ bundleStateHash: head.stateHash,
94
+ }));
95
+ const stored = rows.length === 0
96
+ ? []
97
+ : await loadStoredReferences(tx, head, [
98
+ ...new Set(rows.map((row) => row.version)),
99
+ ]);
100
+ let failure = null;
101
+ let accepted = rows.length;
102
+ for (const [index, row] of rows.entries()) {
103
+ failure = referenceConflict(row, stored);
104
+ if (failure) {
105
+ accepted = index;
106
+ break;
49
107
  }
50
108
  }
109
+ if (!failure && foreignIndex !== -1)
110
+ failure = new KeyingVerificationError("object_mismatch", "principal policy bundle reference belongs to another principal");
111
+ await insertMissingReferences(tx, rows.slice(0, accepted), stored);
112
+ if (failure)
113
+ throw failure;
51
114
  }
52
115
  //# sourceMappingURL=principalPolicyReferenceIndexPersistence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"principalPolicyReferenceIndexPersistence.js","sourceRoot":"","sources":["principalPolicyReferenceIndexPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport type { PrincipalPolicyBundleResponse } from \"@tearleads/validators/response\";\nimport { and, eq } from \"drizzle-orm\";\nimport { principalPolicyBundleStates } from \"../principalPolicyStates\";\nimport { principalPolicyBundleReferences } from \"../sqlite/schema\";\nimport type { ClientSQLiteTransactionScope } from \"../sqlite/sqlitePersistenceRuntime\";\n\ntype PrincipalPolicyReferenceRow =\n typeof principalPolicyBundleReferences.$inferInsert;\n\nfunction exactReferenceWhere(row: PrincipalPolicyReferenceRow) {\n return and(\n eq(principalPolicyBundleReferences.principalType, row.principalType),\n eq(principalPolicyBundleReferences.principalId, row.principalId),\n eq(principalPolicyBundleReferences.version, row.version),\n eq(principalPolicyBundleReferences.stateHash, row.stateHash),\n eq(principalPolicyBundleReferences.keyEpoch, row.keyEpoch),\n eq(principalPolicyBundleReferences.keyFingerprint, row.keyFingerprint),\n );\n}\n\nasync function assertNoIndexedConflict(\n tx: ClientSQLiteTransactionScope,\n row: PrincipalPolicyReferenceRow,\n): Promise<void> {\n const conflicts = await tx\n .select({\n bundleStateHash: principalPolicyBundleReferences.bundleStateHash,\n })\n .from(principalPolicyBundleReferences)\n .where(\n and(\n exactReferenceWhere(row),\n eq(principalPolicyBundleReferences.bundleVersion, row.bundleVersion),\n ),\n );\n if (\n conflicts.some(\n ({ bundleStateHash }) => bundleStateHash !== row.bundleStateHash,\n )\n ) {\n throw new KeyingVerificationError(\n \"equivocation\",\n \"principal policy reference has conflicting bundle heads at one version\",\n );\n }\n}\n\nexport async function indexPrincipalPolicyBundleInTransaction(\n tx: ClientSQLiteTransactionScope,\n bundle: PrincipalPolicyBundleResponse,\n): Promise<void> {\n const head = bundle.currentState;\n for (const state of principalPolicyBundleStates(bundle)) {\n if (\n state.principalType !== head.principalType ||\n state.principalId !== head.principalId\n ) {\n throw new KeyingVerificationError(\n \"object_mismatch\",\n \"principal policy bundle reference belongs to another principal\",\n );\n }\n const row: PrincipalPolicyReferenceRow = {\n principalType: state.principalType,\n principalId: state.principalId,\n version: state.version,\n stateHash: state.stateHash,\n keyEpoch: state.keyEpoch,\n keyFingerprint: state.keyFingerprint,\n bundleVersion: head.version,\n bundleStateHash: head.stateHash,\n };\n await assertNoIndexedConflict(tx, row);\n await tx\n .insert(principalPolicyBundleReferences)\n .values(row)\n .onConflictDoNothing()\n .run();\n const [stored] = await tx\n .select({ bundleVersion: principalPolicyBundleReferences.bundleVersion })\n .from(principalPolicyBundleReferences)\n .where(\n and(\n exactReferenceWhere(row),\n eq(\n principalPolicyBundleReferences.bundleStateHash,\n row.bundleStateHash,\n ),\n ),\n )\n .limit(1);\n if (stored?.bundleVersion !== row.bundleVersion) {\n throw new KeyingVerificationError(\n \"equivocation\",\n \"principal policy reference index conflicts with the bundle head\",\n );\n }\n }\n}\n"]}
1
+ {"version":3,"file":"principalPolicyReferenceIndexPersistence.js","sourceRoot":"","sources":["principalPolicyReferenceIndexPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport type { PrincipalPolicyBundleResponse } from \"@tearleads/validators/response\";\nimport { and, eq, inArray, or } from \"drizzle-orm\";\nimport { principalPolicyBundleStates } from \"../principalPolicyStates\";\nimport { principalPolicyBundleReferences } from \"../sqlite/schema\";\nimport {\n sqliteInsertBatchSize,\n sqliteRowsPerStatement,\n} from \"../sqlite/sqliteBindBudget\";\nimport type { ClientSQLiteTransactionScope } from \"../sqlite/sqlitePersistenceRuntime\";\n\ntype PrincipalPolicyReferenceRow =\n typeof principalPolicyBundleReferences.$inferInsert;\n\ntype StoredPrincipalPolicyReference = Pick<\n typeof principalPolicyBundleReferences.$inferSelect,\n | \"version\"\n | \"stateHash\"\n | \"keyEpoch\"\n | \"keyFingerprint\"\n | \"bundleVersion\"\n | \"bundleStateHash\"\n>;\n\nconst REFERENCE_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n principalPolicyBundleReferences,\n);\n// Principal type and id plus the bundle version and state hash bind once.\nconst REFERENCE_LOOKUP_VERSIONS = sqliteRowsPerStatement(1, 4);\n\n/** The exact six-field match; the lookup already fixes the principal. */\nfunction isSameReference(\n stored: StoredPrincipalPolicyReference,\n row: PrincipalPolicyReferenceRow,\n): boolean {\n return (\n stored.version === row.version &&\n stored.stateHash === row.stateHash &&\n stored.keyEpoch === row.keyEpoch &&\n stored.keyFingerprint === row.keyFingerprint\n );\n}\n\n/** The table's primary key within one principal. */\nfunction referenceKey(\n row: Pick<\n StoredPrincipalPolicyReference,\n \"version\" | \"stateHash\" | \"keyEpoch\" | \"keyFingerprint\" | \"bundleStateHash\"\n >,\n): string {\n return JSON.stringify([\n row.version,\n row.stateHash,\n row.keyEpoch,\n row.keyFingerprint,\n row.bundleStateHash,\n ]);\n}\n\n/** Rows either equivocation check can match, read before this bundle's writes. */\nasync function loadStoredReferences(\n tx: ClientSQLiteTransactionScope,\n head: PrincipalPolicyBundleResponse[\"currentState\"],\n versions: readonly number[],\n): Promise<StoredPrincipalPolicyReference[]> {\n const stored: StoredPrincipalPolicyReference[] = [];\n for (\n let index = 0;\n index < versions.length;\n index += REFERENCE_LOOKUP_VERSIONS\n ) {\n stored.push(\n ...(await tx\n .select({\n version: principalPolicyBundleReferences.version,\n stateHash: principalPolicyBundleReferences.stateHash,\n keyEpoch: principalPolicyBundleReferences.keyEpoch,\n keyFingerprint: principalPolicyBundleReferences.keyFingerprint,\n bundleVersion: principalPolicyBundleReferences.bundleVersion,\n bundleStateHash: principalPolicyBundleReferences.bundleStateHash,\n })\n .from(principalPolicyBundleReferences)\n .where(\n and(\n eq(\n principalPolicyBundleReferences.principalType,\n head.principalType,\n ),\n eq(principalPolicyBundleReferences.principalId, head.principalId),\n inArray(\n principalPolicyBundleReferences.version,\n versions.slice(index, index + REFERENCE_LOOKUP_VERSIONS),\n ),\n or(\n eq(principalPolicyBundleReferences.bundleVersion, head.version),\n eq(\n principalPolicyBundleReferences.bundleStateHash,\n head.stateHash,\n ),\n ),\n ),\n )),\n );\n }\n return stored;\n}\n\n/**\n * Rows written earlier in the same bundle all carry its head, so neither\n * check can match them: each state's outcome depends only on stored rows.\n */\nfunction referenceConflict(\n row: PrincipalPolicyReferenceRow,\n stored: readonly StoredPrincipalPolicyReference[],\n): KeyingVerificationError | null {\n const sameReference = stored.filter((candidate) =>\n isSameReference(candidate, row),\n );\n if (\n sameReference.some(\n (candidate) =>\n candidate.bundleVersion === row.bundleVersion &&\n candidate.bundleStateHash !== row.bundleStateHash,\n )\n )\n return new KeyingVerificationError(\n \"equivocation\",\n \"principal policy reference has conflicting bundle heads at one version\",\n );\n const indexed = sameReference.find(\n (candidate) => candidate.bundleStateHash === row.bundleStateHash,\n );\n if (indexed && indexed.bundleVersion !== row.bundleVersion)\n return new KeyingVerificationError(\n \"equivocation\",\n \"principal policy reference index conflicts with the bundle head\",\n );\n return null;\n}\n\nasync function insertMissingReferences(\n tx: ClientSQLiteTransactionScope,\n rows: readonly PrincipalPolicyReferenceRow[],\n stored: readonly StoredPrincipalPolicyReference[],\n): Promise<void> {\n const present = new Set(stored.map(referenceKey));\n const missing = new Map<string, PrincipalPolicyReferenceRow>();\n for (const row of rows) {\n const key = referenceKey(row);\n if (!present.has(key) && !missing.has(key)) missing.set(key, row);\n }\n const values = [...missing.values()];\n for (\n let index = 0;\n index < values.length;\n index += REFERENCE_INSERT_BATCH_SIZE\n ) {\n await tx\n .insert(principalPolicyBundleReferences)\n .values(values.slice(index, index + REFERENCE_INSERT_BATCH_SIZE))\n .onConflictDoNothing()\n .run();\n }\n}\n\n/**\n * Index every state of a verified bundle under its head. Fails in state order\n * with the per-state checks' errors, keeping the states indexed before the\n * first failure.\n */\nexport async function indexPrincipalPolicyBundleInTransaction(\n tx: ClientSQLiteTransactionScope,\n bundle: PrincipalPolicyBundleResponse,\n): Promise<void> {\n const head = bundle.currentState;\n const states = principalPolicyBundleStates(bundle);\n const foreignIndex = states.findIndex(\n (state) =>\n state.principalType !== head.principalType ||\n state.principalId !== head.principalId,\n );\n const rows: PrincipalPolicyReferenceRow[] = (\n foreignIndex === -1 ? states : states.slice(0, foreignIndex)\n ).map((state) => ({\n principalType: state.principalType,\n principalId: state.principalId,\n version: state.version,\n stateHash: state.stateHash,\n keyEpoch: state.keyEpoch,\n keyFingerprint: state.keyFingerprint,\n bundleVersion: head.version,\n bundleStateHash: head.stateHash,\n }));\n const stored =\n rows.length === 0\n ? []\n : await loadStoredReferences(tx, head, [\n ...new Set(rows.map((row) => row.version)),\n ]);\n let failure: KeyingVerificationError | null = null;\n let accepted = rows.length;\n for (const [index, row] of rows.entries()) {\n failure = referenceConflict(row, stored);\n if (failure) {\n accepted = index;\n break;\n }\n }\n if (!failure && foreignIndex !== -1)\n failure = new KeyingVerificationError(\n \"object_mismatch\",\n \"principal policy bundle reference belongs to another principal\",\n );\n await insertMissingReferences(tx, rows.slice(0, accepted), stored);\n if (failure) throw failure;\n}\n"]}
@@ -33,6 +33,12 @@ export function readPrincipalMutation(value, scope) {
33
33
  throw new Error("Principal mutation journal operation is unsupported");
34
34
  const request = CommitOrganizationGroupPolicyRequestSchema.parse(body);
35
35
  assertStateScope(request.groupPolicy.state, "group", groupId, scope);
36
+ if (request.memberPolicy) {
37
+ // The server binds this signed group ID to the organization's Members group.
38
+ assertStateScope(request.memberPolicy.state, "group", request.memberPolicy.state.principalId, scope);
39
+ if (request.memberPolicy.state.principalId === groupId)
40
+ throw new Error("Principal mutation journal Members target is duplicated");
41
+ }
36
42
  assertStateScope(request.organizationPolicy.state, "organization", scope.organizationId, scope);
37
43
  return { kind, groupId, request };
38
44
  }
@@ -1 +1 @@
1
- {"version":3,"file":"principalMutationJournalShape.js","sourceRoot":"","sources":["principalMutationJournalShape.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type CommitOrganizationGroupPolicyRequest,\n CommitOrganizationGroupPolicyRequestSchema,\n type CreateOrganizationGroupWithPolicyRequest,\n CreateOrganizationGroupWithPolicyRequestSchema,\n type DeleteOrganizationGroupRequest,\n DeleteOrganizationGroupRequestSchema,\n type OrganizationPrincipalPolicyRequest,\n OrganizationPrincipalPolicyRequestSchema,\n} from \"@tearleads/validators/request\";\nimport { isUuidV4String } from \"@tearleads/validators/util\";\n\nexport type AuthoredPrincipalMutation =\n | {\n readonly kind: \"compound\";\n readonly groupId: string;\n readonly request: CommitOrganizationGroupPolicyRequest;\n }\n | {\n readonly kind: \"group-create\";\n readonly groupId: string;\n readonly request: CreateOrganizationGroupWithPolicyRequest;\n }\n | {\n readonly kind: \"group-delete\";\n readonly groupId: string;\n readonly request: DeleteOrganizationGroupRequest;\n }\n | {\n readonly kind: \"organization\";\n readonly groupId: null;\n readonly request: OrganizationPrincipalPolicyRequest;\n };\n\ninterface SignerScope {\n readonly organizationId: string;\n readonly userId: string;\n readonly signingFingerprint: string;\n}\n\n/** Validate authored inputs; opening an existing row authenticates it first. */\nexport function readPrincipalMutation(\n value: unknown,\n scope: SignerScope,\n): AuthoredPrincipalMutation {\n if (typeof value !== \"object\" || value === null)\n throw new Error(\"Principal mutation journal payload is invalid\");\n const groupId: unknown = Reflect.get(value, \"groupId\");\n const kind: unknown = Reflect.get(value, \"kind\");\n const body: unknown = Reflect.get(value, \"request\");\n if (kind === \"organization\") {\n if (groupId !== null)\n throw new Error(\"Organization journal target differs\");\n const request = OrganizationPrincipalPolicyRequestSchema.parse(body);\n assertStateScope(\n request.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n }\n if (typeof groupId !== \"string\" || !isUuidV4String(groupId))\n throw new Error(\"Principal mutation journal group target is invalid\");\n if (kind === \"group-create\") {\n const request = CreateOrganizationGroupWithPolicyRequestSchema.parse(body);\n if (request.groupId !== groupId)\n throw new Error(\"Group creation journal target differs\");\n assertStateScope(request.initialGroupPolicy.state, \"group\", groupId, scope);\n assertStateScope(\n request.organizationPolicy.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n }\n if (kind === \"group-delete\") {\n const request = DeleteOrganizationGroupRequestSchema.parse(body);\n assertStateScope(\n request.organizationPolicy.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n }\n if (kind !== \"compound\")\n throw new Error(\"Principal mutation journal operation is unsupported\");\n const request = CommitOrganizationGroupPolicyRequestSchema.parse(body);\n assertStateScope(request.groupPolicy.state, \"group\", groupId, scope);\n assertStateScope(\n request.organizationPolicy.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n}\n\nfunction assertStateScope(\n state: OrganizationPrincipalPolicyRequest[\"state\"],\n principalType: \"organization\" | \"group\",\n principalId: string,\n scope: SignerScope,\n) {\n if (\n state.principalType !== principalType ||\n state.principalId !== principalId ||\n state.signerUserId !== scope.userId ||\n state.signerUserKeyFingerprint !== scope.signingFingerprint\n )\n throw new Error(\"Principal mutation journal target or signer differs\");\n}\n"]}
1
+ {"version":3,"file":"principalMutationJournalShape.js","sourceRoot":"","sources":["principalMutationJournalShape.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type CommitOrganizationGroupPolicyRequest,\n CommitOrganizationGroupPolicyRequestSchema,\n type CreateOrganizationGroupWithPolicyRequest,\n CreateOrganizationGroupWithPolicyRequestSchema,\n type DeleteOrganizationGroupRequest,\n DeleteOrganizationGroupRequestSchema,\n type OrganizationPrincipalPolicyRequest,\n OrganizationPrincipalPolicyRequestSchema,\n} from \"@tearleads/validators/request\";\nimport { isUuidV4String } from \"@tearleads/validators/util\";\n\nexport type AuthoredPrincipalMutation =\n | {\n readonly kind: \"compound\";\n readonly groupId: string;\n readonly request: CommitOrganizationGroupPolicyRequest;\n }\n | {\n readonly kind: \"group-create\";\n readonly groupId: string;\n readonly request: CreateOrganizationGroupWithPolicyRequest;\n }\n | {\n readonly kind: \"group-delete\";\n readonly groupId: string;\n readonly request: DeleteOrganizationGroupRequest;\n }\n | {\n readonly kind: \"organization\";\n readonly groupId: null;\n readonly request: OrganizationPrincipalPolicyRequest;\n };\n\ninterface SignerScope {\n readonly organizationId: string;\n readonly userId: string;\n readonly signingFingerprint: string;\n}\n\n/** Validate authored inputs; opening an existing row authenticates it first. */\nexport function readPrincipalMutation(\n value: unknown,\n scope: SignerScope,\n): AuthoredPrincipalMutation {\n if (typeof value !== \"object\" || value === null)\n throw new Error(\"Principal mutation journal payload is invalid\");\n const groupId: unknown = Reflect.get(value, \"groupId\");\n const kind: unknown = Reflect.get(value, \"kind\");\n const body: unknown = Reflect.get(value, \"request\");\n if (kind === \"organization\") {\n if (groupId !== null)\n throw new Error(\"Organization journal target differs\");\n const request = OrganizationPrincipalPolicyRequestSchema.parse(body);\n assertStateScope(\n request.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n }\n if (typeof groupId !== \"string\" || !isUuidV4String(groupId))\n throw new Error(\"Principal mutation journal group target is invalid\");\n if (kind === \"group-create\") {\n const request = CreateOrganizationGroupWithPolicyRequestSchema.parse(body);\n if (request.groupId !== groupId)\n throw new Error(\"Group creation journal target differs\");\n assertStateScope(request.initialGroupPolicy.state, \"group\", groupId, scope);\n assertStateScope(\n request.organizationPolicy.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n }\n if (kind === \"group-delete\") {\n const request = DeleteOrganizationGroupRequestSchema.parse(body);\n assertStateScope(\n request.organizationPolicy.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n }\n if (kind !== \"compound\")\n throw new Error(\"Principal mutation journal operation is unsupported\");\n const request = CommitOrganizationGroupPolicyRequestSchema.parse(body);\n assertStateScope(request.groupPolicy.state, \"group\", groupId, scope);\n if (request.memberPolicy) {\n // The server binds this signed group ID to the organization's Members group.\n assertStateScope(\n request.memberPolicy.state,\n \"group\",\n request.memberPolicy.state.principalId,\n scope,\n );\n if (request.memberPolicy.state.principalId === groupId)\n throw new Error(\n \"Principal mutation journal Members target is duplicated\",\n );\n }\n assertStateScope(\n request.organizationPolicy.state,\n \"organization\",\n scope.organizationId,\n scope,\n );\n return { kind, groupId, request };\n}\n\nfunction assertStateScope(\n state: OrganizationPrincipalPolicyRequest[\"state\"],\n principalType: \"organization\" | \"group\",\n principalId: string,\n scope: SignerScope,\n) {\n if (\n state.principalType !== principalType ||\n state.principalId !== principalId ||\n state.signerUserId !== scope.userId ||\n state.signerUserKeyFingerprint !== scope.signingFingerprint\n )\n throw new Error(\"Principal mutation journal target or signer differs\");\n}\n"]}
@@ -0,0 +1,9 @@
1
+ import { sql } from "drizzle-orm";
2
+ /**
3
+ * The value a multi-row upsert proposed for `column`. A `set` clause needs it
4
+ * to apply each conflicting row's own value rather than one shared value.
5
+ */
6
+ export function excludedValue(column) {
7
+ return sql `excluded.${sql.identifier(column.name)}`;
8
+ }
9
+ //# sourceMappingURL=sqliteUpsert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqliteUpsert.js","sourceRoot":"","sources":["sqliteUpsert.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,GAAG,EAAE,MAAM,aAAa,CAAC;AAG5C;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAoB;IAChD,OAAO,GAAG,CAAA,YAAY,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;AACtD,CAAC","sourcesContent":["import { type SQL, sql } from \"drizzle-orm\";\nimport type { SQLiteColumn } from \"drizzle-orm/sqlite-core\";\n\n/**\n * The value a multi-row upsert proposed for `column`. A `set` clause needs it\n * to apply each conflicting row's own value rather than one shared value.\n */\nexport function excludedValue(column: SQLiteColumn): SQL {\n return sql`excluded.${sql.identifier(column.name)}`;\n}\n"]}