@tearleads/client-sdk 0.1.39 → 0.1.41

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 (47) hide show
  1. package/client/organizations/organizationGroupPresentation.d.ts +1 -12
  2. package/data/documents/shared/syncResponses.js +5 -7
  3. package/data/documents/shared/syncResponses.js.map +1 -1
  4. package/data/persistence/organizations/organizationReadModelGrantPersistence.js +2 -1
  5. package/data/persistence/organizations/organizationReadModelGrantPersistence.js.map +1 -1
  6. package/data/persistence/organizations/organizationReadModelMemberLoad.d.ts +9 -0
  7. package/data/persistence/organizations/organizationReadModelMemberLoad.js +29 -21
  8. package/data/persistence/organizations/organizationReadModelMemberLoad.js.map +1 -1
  9. package/data/persistence/organizations/organizationReadModelMembershipPersistence.js +91 -46
  10. package/data/persistence/organizations/organizationReadModelMembershipPersistence.js.map +1 -1
  11. package/data/persistence/organizations/organizationReadModelPersistence.js +95 -73
  12. package/data/persistence/organizations/organizationReadModelPersistence.js.map +1 -1
  13. package/data/persistence/organizations/organizationReadModelProjection.js +78 -89
  14. package/data/persistence/organizations/organizationReadModelProjection.js.map +1 -1
  15. package/data/persistence/organizations/organizationReadModelPurge.js +14 -15
  16. package/data/persistence/organizations/organizationReadModelPurge.js.map +1 -1
  17. package/data/sqlite/organizationReadModelSchema.js +23 -28
  18. package/data/sqlite/organizationReadModelSchema.js.map +1 -1
  19. package/data/sqlite/organizationReadModelTableRegistry.d.ts +116 -263
  20. package/data/sqlite/organizationReadModelTableRegistry.js +10 -7
  21. package/data/sqlite/organizationReadModelTableRegistry.js.map +1 -1
  22. package/data/sqlite/schema.d.ts +116 -263
  23. package/data/sqlite/sqliteBindBudget.js +30 -0
  24. package/data/sqlite/sqliteBindBudget.js.map +1 -0
  25. package/internal/validators/documentSyncRefinements.js +5 -0
  26. package/internal/validators/documentSyncRefinements.js.map +1 -1
  27. package/internal/validators/operation/documentSync.d.ts +3 -0
  28. package/internal/validators/operation/organizations.d.ts +0 -16
  29. package/internal/validators/response/documentSyncSchema.js +35 -1
  30. package/internal/validators/response/documentSyncSchema.js.map +1 -1
  31. package/internal/validators/response/index.d.ts +1 -1
  32. package/internal/validators/response/index.js.map +1 -1
  33. package/internal/validators/response/organizationReadModel.d.ts +10 -48
  34. package/internal/validators/response/organizationReadModel.js +9 -1
  35. package/internal/validators/response/organizationReadModel.js.map +1 -1
  36. package/package.json +1 -1
  37. package/workflows/documents/syncContentKeys.js +4 -9
  38. package/workflows/documents/syncContentKeys.js.map +1 -1
  39. package/workflows/organizations/readModel.d.ts +8 -3
  40. package/workflows/organizations/readModel.js.map +1 -1
  41. package/workflows/organizations/readModelProjection.d.ts +2 -12
  42. package/workflows/organizations/readModelProjection.js +1 -1
  43. package/workflows/organizations/readModelProjection.js.map +1 -1
  44. package/workflows/sync/remoteReset.js +1 -3
  45. package/workflows/sync/remoteReset.js.map +1 -1
  46. package/data/persistence/organizations/organizationReadModelPolicyHeadPersistence.js +0 -64
  47. package/data/persistence/organizations/organizationReadModelPolicyHeadPersistence.js.map +0 -1
@@ -1,5 +1,6 @@
1
+ import { compareCanonicalStrings } from "../../../internal/crypto/index.js";
1
2
  import { and, asc, eq } from "drizzle-orm";
2
- import { organizationReadModelDirectoryUsers, organizationReadModelGroupMembers, organizationReadModelGroups, organizationReadModelPolicyHeads, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
3
+ import { organizationReadModelDirectoryUsers, organizationReadModelGroupMembers, organizationReadModelGroups, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
3
4
  import { loadOrganizationReadModelGrantsInTransaction } from "./organizationReadModelGrantPersistence.js";
4
5
  import { ORGANIZATION_READ_MODEL_PROTOCOL_VERSION, OrganizationReadModelIntegrityError, } from "./organizationReadModelProtocol.js";
5
6
  function isOrganizationRosterStatus(value) {
@@ -26,69 +27,77 @@ function toDirectoryUser(row, currentUserId) {
26
27
  disabledByUserId: row.disabledByUserId,
27
28
  };
28
29
  }
29
- function toGroupCurrentState(row, policyHead) {
30
+ function isValidPolicyHead(head) {
31
+ return (head.stateHash.length > 0 &&
32
+ Number.isInteger(head.version) &&
33
+ head.version > 0 &&
34
+ Number.isSafeInteger(head.keyEpoch) &&
35
+ head.keyEpoch > 0 &&
36
+ head.keyFingerprint.length > 0 &&
37
+ Number.isInteger(head.memberCount) &&
38
+ head.memberCount >= 0);
39
+ }
40
+ /** A group's state columns are all null (stateless) or one valid head. */
41
+ function toGroupCurrentState(row) {
30
42
  const values = [
31
43
  row.stateHash,
32
44
  row.stateVersion,
33
45
  row.keyEpoch,
46
+ row.keyFingerprint,
34
47
  row.memberCount,
35
48
  ];
36
49
  if (values.every((value) => value === null)) {
37
- if (policyHead) {
38
- throw new OrganizationReadModelIntegrityError("Stored organization group policy head is orphaned");
39
- }
40
50
  return null;
41
51
  }
42
- if (row.stateHash === null ||
52
+ const head = row.stateHash === null ||
43
53
  row.stateVersion === null ||
44
54
  row.keyEpoch === null ||
45
- row.memberCount === null ||
46
- row.stateHash.length === 0 ||
47
- !Number.isInteger(row.stateVersion) ||
48
- row.stateVersion <= 0 ||
49
- !Number.isSafeInteger(row.keyEpoch) ||
50
- row.keyEpoch <= 0 ||
51
- !Number.isInteger(row.memberCount) ||
52
- row.memberCount < 0 ||
53
- !policyHead ||
54
- policyHead.stateHash !== row.stateHash ||
55
- policyHead.version !== row.stateVersion ||
56
- policyHead.keyEpoch !== row.keyEpoch ||
57
- policyHead.memberCount !== row.memberCount) {
55
+ row.keyFingerprint === null ||
56
+ row.memberCount === null
57
+ ? null
58
+ : {
59
+ stateHash: row.stateHash,
60
+ version: row.stateVersion,
61
+ keyEpoch: row.keyEpoch,
62
+ keyFingerprint: row.keyFingerprint,
63
+ memberCount: row.memberCount,
64
+ };
65
+ if (!head || !isValidPolicyHead(head)) {
58
66
  throw new OrganizationReadModelIntegrityError("Stored organization group state is invalid");
59
67
  }
60
- return {
61
- stateHash: row.stateHash,
62
- version: row.stateVersion,
63
- keyEpoch: row.keyEpoch,
64
- keyFingerprint: policyHead.keyFingerprint,
65
- memberCount: row.memberCount,
66
- };
68
+ return head;
67
69
  }
68
- function toPolicyHead(row) {
69
- if ((row.principalType !== "group" && row.principalType !== "organization") ||
70
- row.principalId.length === 0 ||
71
- row.stateHash.length === 0 ||
72
- !Number.isInteger(row.stateVersion) ||
73
- row.stateVersion <= 0 ||
74
- !Number.isSafeInteger(row.keyEpoch) ||
75
- row.keyEpoch <= 0 ||
76
- row.keyFingerprint.length === 0 ||
77
- !Number.isInteger(row.memberCount) ||
78
- row.memberCount < 0) {
70
+ function toOrganizationPolicyHead(organizationId, head) {
71
+ if (!isValidPolicyHead(head)) {
79
72
  throw new OrganizationReadModelIntegrityError("Stored organization policy head is invalid");
80
73
  }
81
74
  return {
82
- organizationId: row.organizationId,
83
- principalType: row.principalType,
84
- principalId: row.principalId,
85
- stateHash: row.stateHash,
86
- version: row.stateVersion,
87
- keyEpoch: row.keyEpoch,
88
- keyFingerprint: row.keyFingerprint,
89
- memberCount: row.memberCount,
75
+ ...head,
76
+ organizationId,
77
+ principalId: organizationId,
78
+ principalType: "organization",
90
79
  };
91
80
  }
81
+ /** The organization head from the state row plus every stateful group head. */
82
+ function toProjectionPolicyHeads(input) {
83
+ const groupHeads = input.groups
84
+ .flatMap((group) => group.currentState
85
+ ? [
86
+ {
87
+ ...group.currentState,
88
+ organizationId: input.organizationId,
89
+ principalId: group.groupId,
90
+ principalType: "group",
91
+ },
92
+ ]
93
+ : [])
94
+ .sort((left, right) => compareCanonicalStrings(left.principalId, right.principalId));
95
+ // Group heads precede the organization head, ordered by principal ID.
96
+ return [
97
+ ...groupHeads,
98
+ toOrganizationPolicyHead(input.organizationId, input.organizationHead),
99
+ ];
100
+ }
92
101
  async function loadDirectoryRows(tx, organizationId) {
93
102
  return tx
94
103
  .select({
@@ -117,6 +126,7 @@ async function loadGroupRows(tx, organizationId) {
117
126
  groupId: organizationReadModelGroups.groupId,
118
127
  isBuiltin: organizationReadModelGroups.isBuiltin,
119
128
  keyEpoch: organizationReadModelGroups.keyEpoch,
129
+ keyFingerprint: organizationReadModelGroups.keyFingerprint,
120
130
  memberCount: organizationReadModelGroups.memberCount,
121
131
  name: organizationReadModelGroups.name,
122
132
  stateHash: organizationReadModelGroups.stateHash,
@@ -126,22 +136,6 @@ async function loadGroupRows(tx, organizationId) {
126
136
  .where(eq(organizationReadModelGroups.organizationId, organizationId))
127
137
  .orderBy(asc(organizationReadModelGroups.sortOrder), asc(organizationReadModelGroups.groupId));
128
138
  }
129
- async function loadPolicyHeadRows(tx, organizationId) {
130
- return tx
131
- .select({
132
- keyEpoch: organizationReadModelPolicyHeads.keyEpoch,
133
- keyFingerprint: organizationReadModelPolicyHeads.keyFingerprint,
134
- memberCount: organizationReadModelPolicyHeads.memberCount,
135
- organizationId: organizationReadModelPolicyHeads.organizationId,
136
- principalId: organizationReadModelPolicyHeads.principalId,
137
- principalType: organizationReadModelPolicyHeads.principalType,
138
- stateHash: organizationReadModelPolicyHeads.stateHash,
139
- stateVersion: organizationReadModelPolicyHeads.stateVersion,
140
- })
141
- .from(organizationReadModelPolicyHeads)
142
- .where(eq(organizationReadModelPolicyHeads.organizationId, organizationId))
143
- .orderBy(asc(organizationReadModelPolicyHeads.principalType), asc(organizationReadModelPolicyHeads.principalId));
144
- }
145
139
  async function loadMembershipEdges(tx, organizationId) {
146
140
  return tx
147
141
  .select({
@@ -152,27 +146,16 @@ async function loadMembershipEdges(tx, organizationId) {
152
146
  .where(eq(organizationReadModelGroupMembers.organizationId, organizationId))
153
147
  .orderBy(asc(organizationReadModelGroupMembers.groupId), asc(organizationReadModelGroupMembers.sortOrder), asc(organizationReadModelGroupMembers.userId));
154
148
  }
155
- function validatePolicyHeads(input) {
156
- const policyHeads = input.policyHeadRows.map(toPolicyHead);
157
- const organizationPolicyHeads = policyHeads.filter((head) => head.principalType === "organization");
158
- if (organizationPolicyHeads.length !== 1 ||
159
- organizationPolicyHeads[0]?.principalId !== input.organizationId) {
160
- throw new OrganizationReadModelIntegrityError("Stored organization policy head is missing or ambiguous");
161
- }
162
- const groupPolicyHeads = new Map(policyHeads
163
- .filter((head) => head.principalType === "group")
164
- .map((head) => [head.principalId, head]));
165
- const visibleGroupIds = new Set(input.groupRows.map((row) => row.groupId));
166
- if ([...groupPolicyHeads.keys()].some((principalId) => !visibleGroupIds.has(principalId))) {
167
- throw new OrganizationReadModelIntegrityError("Stored organization group policy head is not visible");
168
- }
169
- return { groupPolicyHeads, policyHeads };
170
- }
171
149
  export async function loadOrganizationReadModelProjectionInTransaction(input) {
172
150
  const [state] = await input.tx
173
151
  .select({
174
152
  cursor: organizationReadModelState.cursor,
175
153
  memberGroupId: organizationReadModelState.memberGroupId,
154
+ organizationKeyEpoch: organizationReadModelState.organizationKeyEpoch,
155
+ organizationKeyFingerprint: organizationReadModelState.organizationKeyFingerprint,
156
+ organizationMemberCount: organizationReadModelState.organizationMemberCount,
157
+ organizationStateHash: organizationReadModelState.organizationStateHash,
158
+ organizationStateVersion: organizationReadModelState.organizationStateVersion,
176
159
  profileDocumentId: organizationReadModelState.profileDocumentId,
177
160
  updatedAt: organizationReadModelState.updatedAt,
178
161
  })
@@ -191,11 +174,24 @@ export async function loadOrganizationReadModelProjectionInTransaction(input) {
191
174
  const groupRows = await loadGroupRows(input.tx, input.organizationId);
192
175
  const grants = await loadOrganizationReadModelGrantsInTransaction(input);
193
176
  const membershipEdges = await loadMembershipEdges(input.tx, input.organizationId);
194
- const policyHeadRows = await loadPolicyHeadRows(input.tx, input.organizationId);
195
- const { groupPolicyHeads, policyHeads } = validatePolicyHeads({
196
- groupRows,
177
+ const groups = groupRows.map((row) => ({
178
+ groupId: row.groupId,
179
+ organizationId: input.organizationId,
180
+ name: row.name,
181
+ createdAt: row.createdAt,
182
+ isBuiltin: row.isBuiltin,
183
+ currentState: toGroupCurrentState(row),
184
+ }));
185
+ const policyHeads = toProjectionPolicyHeads({
186
+ groups,
187
+ organizationHead: {
188
+ keyEpoch: state.organizationKeyEpoch,
189
+ keyFingerprint: state.organizationKeyFingerprint,
190
+ memberCount: state.organizationMemberCount,
191
+ stateHash: state.organizationStateHash,
192
+ version: state.organizationStateVersion,
193
+ },
197
194
  organizationId: input.organizationId,
198
- policyHeadRows,
199
195
  });
200
196
  return {
201
197
  cursor: state.cursor,
@@ -208,14 +204,7 @@ export async function loadOrganizationReadModelProjectionInTransaction(input) {
208
204
  groups: {
209
205
  organizationId: input.organizationId,
210
206
  memberGroupId: state.memberGroupId,
211
- groups: groupRows.map((row) => ({
212
- groupId: row.groupId,
213
- organizationId: input.organizationId,
214
- name: row.name,
215
- createdAt: row.createdAt,
216
- isBuiltin: row.isBuiltin,
217
- currentState: toGroupCurrentState(row, groupPolicyHeads.get(row.groupId)),
218
- })),
207
+ groups,
219
208
  },
220
209
  membershipEdges,
221
210
  organizationId: input.organizationId,
@@ -1 +1 @@
1
- {"version":3,"file":"organizationReadModelProjection.js","sourceRoot":"","sources":["organizationReadModelProjection.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ListOrganizationGroupsResponse,\n OrganizationContainerGrantsResponse,\n OrganizationDirectoryResponse,\n OrganizationDirectoryUserResponse,\n OrganizationGroupCurrentStateResponse,\n OrganizationRosterStatus,\n} from \"@tearleads/validators/response\";\nimport { and, asc, eq } from \"drizzle-orm\";\nimport {\n organizationReadModelDirectoryUsers,\n organizationReadModelGroupMembers,\n organizationReadModelGroups,\n organizationReadModelPolicyHeads,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { loadOrganizationReadModelGrantsInTransaction } from \"./organizationReadModelGrantPersistence\";\nimport {\n ORGANIZATION_READ_MODEL_PROTOCOL_VERSION,\n OrganizationReadModelIntegrityError,\n} from \"./organizationReadModelProtocol\";\n\nexport type OrganizationDirectoryProjection = Omit<\n OrganizationDirectoryResponse,\n \"currentUser\"\n>;\n\nexport interface OrganizationReadModelProjection {\n readonly cursor: string;\n readonly directory: OrganizationDirectoryProjection;\n readonly grants: OrganizationContainerGrantsResponse;\n readonly groups: Omit<ListOrganizationGroupsResponse, \"groups\"> & {\n groups: Array<\n ListOrganizationGroupsResponse[\"groups\"][number] & { name: string }\n >;\n };\n readonly membershipEdges: OrganizationReadModelMembershipEdge[];\n readonly organizationId: string;\n readonly policyHeads: readonly OrganizationReadModelPolicyHead[];\n readonly protocolVersion: typeof ORGANIZATION_READ_MODEL_PROTOCOL_VERSION;\n readonly requester: OrganizationDirectoryResponse[\"currentUser\"] | null;\n readonly updatedAt: string;\n}\n\nexport interface OrganizationReadModelPolicyHead {\n readonly keyEpoch: number;\n readonly keyFingerprint: string;\n readonly memberCount: number;\n readonly organizationId: string;\n readonly principalId: string;\n readonly principalType: \"group\" | \"organization\";\n readonly stateHash: string;\n readonly version: number;\n}\n\nexport interface OrganizationReadModelMembershipEdge {\n readonly groupId: string;\n readonly userId: string;\n}\n\ninterface SelectedDirectoryUser {\n readonly createdAt: string;\n readonly disabledAt: string | null;\n readonly disabledByUserId: string | null;\n readonly encapsulationKeyFingerprint: string;\n readonly encapsulationPublicKey: string;\n readonly isPersonalOrganizationOwner: boolean;\n readonly joinedAt: string;\n readonly profileDocumentId: string | null;\n readonly signingKeyFingerprint: string;\n readonly signingPublicKey: string;\n readonly status: string;\n readonly updatedAt: string;\n readonly userId: string;\n}\n\ninterface SelectedGroup {\n readonly createdAt: string;\n readonly groupId: string;\n readonly isBuiltin: boolean;\n readonly keyEpoch: number | null;\n readonly memberCount: number | null;\n readonly name: string;\n readonly stateHash: string | null;\n readonly stateVersion: number | null;\n}\n\ninterface SelectedPolicyHead {\n readonly keyEpoch: number;\n readonly keyFingerprint: string;\n readonly memberCount: number;\n readonly organizationId: string;\n readonly principalId: string;\n readonly principalType: string;\n readonly stateHash: string;\n readonly stateVersion: number;\n}\n\nfunction isOrganizationRosterStatus(\n value: string,\n): value is OrganizationRosterStatus {\n return value === \"active\" || value === \"disabled\";\n}\n\nfunction toDirectoryUser(\n row: SelectedDirectoryUser,\n currentUserId: string,\n): OrganizationDirectoryUserResponse {\n if (!isOrganizationRosterStatus(row.status)) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization directory status is invalid\",\n );\n }\n\n return {\n userId: row.userId,\n signingKeyFingerprint: row.signingKeyFingerprint,\n signingPublicKey: row.signingPublicKey,\n encapsulationPublicKey: row.encapsulationPublicKey,\n encapsulationKeyFingerprint: row.encapsulationKeyFingerprint,\n createdAt: row.createdAt,\n isPersonalOrganizationOwner: row.isPersonalOrganizationOwner,\n isSelf: row.userId === currentUserId,\n status: row.status,\n profileDocumentId: row.profileDocumentId,\n joinedAt: row.joinedAt,\n updatedAt: row.updatedAt,\n disabledAt: row.disabledAt,\n disabledByUserId: row.disabledByUserId,\n };\n}\n\nfunction toGroupCurrentState(\n row: SelectedGroup,\n policyHead: OrganizationReadModelPolicyHead | undefined,\n): OrganizationGroupCurrentStateResponse | null {\n const values = [\n row.stateHash,\n row.stateVersion,\n row.keyEpoch,\n row.memberCount,\n ];\n if (values.every((value) => value === null)) {\n if (policyHead) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization group policy head is orphaned\",\n );\n }\n return null;\n }\n if (\n row.stateHash === null ||\n row.stateVersion === null ||\n row.keyEpoch === null ||\n row.memberCount === null ||\n row.stateHash.length === 0 ||\n !Number.isInteger(row.stateVersion) ||\n row.stateVersion <= 0 ||\n !Number.isSafeInteger(row.keyEpoch) ||\n row.keyEpoch <= 0 ||\n !Number.isInteger(row.memberCount) ||\n row.memberCount < 0 ||\n !policyHead ||\n policyHead.stateHash !== row.stateHash ||\n policyHead.version !== row.stateVersion ||\n policyHead.keyEpoch !== row.keyEpoch ||\n policyHead.memberCount !== row.memberCount\n ) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization group state is invalid\",\n );\n }\n\n return {\n stateHash: row.stateHash,\n version: row.stateVersion,\n keyEpoch: row.keyEpoch,\n keyFingerprint: policyHead.keyFingerprint,\n memberCount: row.memberCount,\n };\n}\n\nfunction toPolicyHead(\n row: SelectedPolicyHead,\n): OrganizationReadModelPolicyHead {\n if (\n (row.principalType !== \"group\" && row.principalType !== \"organization\") ||\n row.principalId.length === 0 ||\n row.stateHash.length === 0 ||\n !Number.isInteger(row.stateVersion) ||\n row.stateVersion <= 0 ||\n !Number.isSafeInteger(row.keyEpoch) ||\n row.keyEpoch <= 0 ||\n row.keyFingerprint.length === 0 ||\n !Number.isInteger(row.memberCount) ||\n row.memberCount < 0\n ) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization policy head is invalid\",\n );\n }\n\n return {\n organizationId: row.organizationId,\n principalType: row.principalType,\n principalId: row.principalId,\n stateHash: row.stateHash,\n version: row.stateVersion,\n keyEpoch: row.keyEpoch,\n keyFingerprint: row.keyFingerprint,\n memberCount: row.memberCount,\n };\n}\n\nasync function loadDirectoryRows(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<SelectedDirectoryUser[]> {\n return tx\n .select({\n createdAt: organizationReadModelDirectoryUsers.createdAt,\n disabledAt: organizationReadModelDirectoryUsers.disabledAt,\n disabledByUserId: organizationReadModelDirectoryUsers.disabledByUserId,\n encapsulationKeyFingerprint:\n organizationReadModelDirectoryUsers.encapsulationKeyFingerprint,\n encapsulationPublicKey:\n organizationReadModelDirectoryUsers.encapsulationPublicKey,\n joinedAt: organizationReadModelDirectoryUsers.joinedAt,\n isPersonalOrganizationOwner:\n organizationReadModelDirectoryUsers.isPersonalOrganizationOwner,\n profileDocumentId: organizationReadModelDirectoryUsers.profileDocumentId,\n signingKeyFingerprint:\n organizationReadModelDirectoryUsers.signingKeyFingerprint,\n signingPublicKey: organizationReadModelDirectoryUsers.signingPublicKey,\n status: organizationReadModelDirectoryUsers.status,\n updatedAt: organizationReadModelDirectoryUsers.updatedAt,\n userId: organizationReadModelDirectoryUsers.userId,\n })\n .from(organizationReadModelDirectoryUsers)\n .where(\n eq(organizationReadModelDirectoryUsers.organizationId, organizationId),\n )\n .orderBy(\n asc(organizationReadModelDirectoryUsers.sortOrder),\n asc(organizationReadModelDirectoryUsers.userId),\n );\n}\n\nasync function loadGroupRows(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<SelectedGroup[]> {\n return tx\n .select({\n createdAt: organizationReadModelGroups.createdAt,\n groupId: organizationReadModelGroups.groupId,\n isBuiltin: organizationReadModelGroups.isBuiltin,\n keyEpoch: organizationReadModelGroups.keyEpoch,\n memberCount: organizationReadModelGroups.memberCount,\n name: organizationReadModelGroups.name,\n stateHash: organizationReadModelGroups.stateHash,\n stateVersion: organizationReadModelGroups.stateVersion,\n })\n .from(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, organizationId))\n .orderBy(\n asc(organizationReadModelGroups.sortOrder),\n asc(organizationReadModelGroups.groupId),\n );\n}\n\nasync function loadPolicyHeadRows(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<SelectedPolicyHead[]> {\n return tx\n .select({\n keyEpoch: organizationReadModelPolicyHeads.keyEpoch,\n keyFingerprint: organizationReadModelPolicyHeads.keyFingerprint,\n memberCount: organizationReadModelPolicyHeads.memberCount,\n organizationId: organizationReadModelPolicyHeads.organizationId,\n principalId: organizationReadModelPolicyHeads.principalId,\n principalType: organizationReadModelPolicyHeads.principalType,\n stateHash: organizationReadModelPolicyHeads.stateHash,\n stateVersion: organizationReadModelPolicyHeads.stateVersion,\n })\n .from(organizationReadModelPolicyHeads)\n .where(eq(organizationReadModelPolicyHeads.organizationId, organizationId))\n .orderBy(\n asc(organizationReadModelPolicyHeads.principalType),\n asc(organizationReadModelPolicyHeads.principalId),\n );\n}\n\nasync function loadMembershipEdges(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<OrganizationReadModelMembershipEdge[]> {\n return tx\n .select({\n groupId: organizationReadModelGroupMembers.groupId,\n userId: organizationReadModelGroupMembers.userId,\n })\n .from(organizationReadModelGroupMembers)\n .where(eq(organizationReadModelGroupMembers.organizationId, organizationId))\n .orderBy(\n asc(organizationReadModelGroupMembers.groupId),\n asc(organizationReadModelGroupMembers.sortOrder),\n asc(organizationReadModelGroupMembers.userId),\n );\n}\n\nfunction validatePolicyHeads(input: {\n readonly groupRows: readonly SelectedGroup[];\n readonly organizationId: string;\n readonly policyHeadRows: readonly SelectedPolicyHead[];\n}): {\n readonly groupPolicyHeads: ReadonlyMap<\n string,\n OrganizationReadModelPolicyHead\n >;\n readonly policyHeads: readonly OrganizationReadModelPolicyHead[];\n} {\n const policyHeads = input.policyHeadRows.map(toPolicyHead);\n const organizationPolicyHeads = policyHeads.filter(\n (head) => head.principalType === \"organization\",\n );\n if (\n organizationPolicyHeads.length !== 1 ||\n organizationPolicyHeads[0]?.principalId !== input.organizationId\n ) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization policy head is missing or ambiguous\",\n );\n }\n const groupPolicyHeads = new Map(\n policyHeads\n .filter((head) => head.principalType === \"group\")\n .map((head) => [head.principalId, head]),\n );\n const visibleGroupIds = new Set(input.groupRows.map((row) => row.groupId));\n if (\n [...groupPolicyHeads.keys()].some(\n (principalId) => !visibleGroupIds.has(principalId),\n )\n ) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization group policy head is not visible\",\n );\n }\n return { groupPolicyHeads, policyHeads };\n}\n\nexport async function loadOrganizationReadModelProjectionInTransaction(input: {\n readonly currentUserId: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<OrganizationReadModelProjection | null> {\n const [state] = await input.tx\n .select({\n cursor: organizationReadModelState.cursor,\n memberGroupId: organizationReadModelState.memberGroupId,\n profileDocumentId: organizationReadModelState.profileDocumentId,\n updatedAt: organizationReadModelState.updatedAt,\n })\n .from(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, input.organizationId))\n .limit(1);\n if (!state) {\n return null;\n }\n const [requester] = await input.tx\n .select({ isOrgAdmin: organizationReadModelRequesters.isOrgAdmin })\n .from(organizationReadModelRequesters)\n .where(\n and(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n eq(organizationReadModelRequesters.userId, input.currentUserId),\n ),\n )\n .limit(1);\n const directoryRows = await loadDirectoryRows(input.tx, input.organizationId);\n const groupRows = await loadGroupRows(input.tx, input.organizationId);\n const grants = await loadOrganizationReadModelGrantsInTransaction(input);\n const membershipEdges = await loadMembershipEdges(\n input.tx,\n input.organizationId,\n );\n const policyHeadRows = await loadPolicyHeadRows(\n input.tx,\n input.organizationId,\n );\n const { groupPolicyHeads, policyHeads } = validatePolicyHeads({\n groupRows,\n organizationId: input.organizationId,\n policyHeadRows,\n });\n\n return {\n cursor: state.cursor,\n directory: {\n organizationId: input.organizationId,\n profileDocumentId: state.profileDocumentId,\n users: directoryRows.map((row) =>\n toDirectoryUser(row, input.currentUserId),\n ),\n },\n grants,\n groups: {\n organizationId: input.organizationId,\n memberGroupId: state.memberGroupId,\n groups: groupRows.map((row) => ({\n groupId: row.groupId,\n organizationId: input.organizationId,\n name: row.name,\n createdAt: row.createdAt,\n isBuiltin: row.isBuiltin,\n currentState: toGroupCurrentState(\n row,\n groupPolicyHeads.get(row.groupId),\n ),\n })),\n },\n membershipEdges,\n organizationId: input.organizationId,\n policyHeads,\n protocolVersion: ORGANIZATION_READ_MODEL_PROTOCOL_VERSION,\n requester: requester ? { isOrgAdmin: requester.isOrgAdmin } : null,\n updatedAt: state.updatedAt,\n };\n}\n"]}
1
+ {"version":3,"file":"organizationReadModelProjection.js","sourceRoot":"","sources":["organizationReadModelProjection.ts"],"names":[],"mappings":"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","sourcesContent":["import { compareCanonicalStrings } from \"@tearleads/crypto\";\nimport type {\n ListOrganizationGroupsResponse,\n OrganizationContainerGrantsResponse,\n OrganizationDirectoryResponse,\n OrganizationDirectoryUserResponse,\n OrganizationGroupCurrentStateResponse,\n OrganizationRosterStatus,\n} from \"@tearleads/validators/response\";\nimport { and, asc, eq } from \"drizzle-orm\";\nimport {\n organizationReadModelDirectoryUsers,\n organizationReadModelGroupMembers,\n organizationReadModelGroups,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport type { ClientSQLiteTransactionScope } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { loadOrganizationReadModelGrantsInTransaction } from \"./organizationReadModelGrantPersistence\";\nimport {\n ORGANIZATION_READ_MODEL_PROTOCOL_VERSION,\n OrganizationReadModelIntegrityError,\n} from \"./organizationReadModelProtocol\";\n\nexport type OrganizationDirectoryProjection = Omit<\n OrganizationDirectoryResponse,\n \"currentUser\"\n>;\n\nexport interface OrganizationReadModelProjection {\n readonly cursor: string;\n readonly directory: OrganizationDirectoryProjection;\n readonly grants: OrganizationContainerGrantsResponse;\n readonly groups: Omit<ListOrganizationGroupsResponse, \"groups\"> & {\n groups: Array<\n ListOrganizationGroupsResponse[\"groups\"][number] & { name: string }\n >;\n };\n readonly membershipEdges: OrganizationReadModelMembershipEdge[];\n readonly organizationId: string;\n readonly policyHeads: readonly OrganizationReadModelPolicyHead[];\n readonly protocolVersion: typeof ORGANIZATION_READ_MODEL_PROTOCOL_VERSION;\n readonly requester: OrganizationDirectoryResponse[\"currentUser\"] | null;\n readonly updatedAt: string;\n}\n\nexport interface OrganizationReadModelPolicyHead {\n readonly keyEpoch: number;\n readonly keyFingerprint: string;\n readonly memberCount: number;\n readonly organizationId: string;\n readonly principalId: string;\n readonly principalType: \"group\" | \"organization\";\n readonly stateHash: string;\n readonly version: number;\n}\n\nexport interface OrganizationReadModelMembershipEdge {\n readonly groupId: string;\n readonly userId: string;\n}\n\ninterface SelectedDirectoryUser {\n readonly createdAt: string;\n readonly disabledAt: string | null;\n readonly disabledByUserId: string | null;\n readonly encapsulationKeyFingerprint: string;\n readonly encapsulationPublicKey: string;\n readonly isPersonalOrganizationOwner: boolean;\n readonly joinedAt: string;\n readonly profileDocumentId: string | null;\n readonly signingKeyFingerprint: string;\n readonly signingPublicKey: string;\n readonly status: string;\n readonly updatedAt: string;\n readonly userId: string;\n}\n\ninterface SelectedGroup {\n readonly createdAt: string;\n readonly groupId: string;\n readonly isBuiltin: boolean;\n readonly keyEpoch: number | null;\n readonly keyFingerprint: string | null;\n readonly memberCount: number | null;\n readonly name: string;\n readonly stateHash: string | null;\n readonly stateVersion: number | null;\n}\n\n/** A stored policy head before validation; `version` is the state version. */\ninterface StoredPolicyHead {\n readonly keyEpoch: number;\n readonly keyFingerprint: string;\n readonly memberCount: number;\n readonly stateHash: string;\n readonly version: number;\n}\n\nfunction isOrganizationRosterStatus(\n value: string,\n): value is OrganizationRosterStatus {\n return value === \"active\" || value === \"disabled\";\n}\n\nfunction toDirectoryUser(\n row: SelectedDirectoryUser,\n currentUserId: string,\n): OrganizationDirectoryUserResponse {\n if (!isOrganizationRosterStatus(row.status)) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization directory status is invalid\",\n );\n }\n\n return {\n userId: row.userId,\n signingKeyFingerprint: row.signingKeyFingerprint,\n signingPublicKey: row.signingPublicKey,\n encapsulationPublicKey: row.encapsulationPublicKey,\n encapsulationKeyFingerprint: row.encapsulationKeyFingerprint,\n createdAt: row.createdAt,\n isPersonalOrganizationOwner: row.isPersonalOrganizationOwner,\n isSelf: row.userId === currentUserId,\n status: row.status,\n profileDocumentId: row.profileDocumentId,\n joinedAt: row.joinedAt,\n updatedAt: row.updatedAt,\n disabledAt: row.disabledAt,\n disabledByUserId: row.disabledByUserId,\n };\n}\n\nfunction isValidPolicyHead(head: StoredPolicyHead): boolean {\n return (\n head.stateHash.length > 0 &&\n Number.isInteger(head.version) &&\n head.version > 0 &&\n Number.isSafeInteger(head.keyEpoch) &&\n head.keyEpoch > 0 &&\n head.keyFingerprint.length > 0 &&\n Number.isInteger(head.memberCount) &&\n head.memberCount >= 0\n );\n}\n\n/** A group's state columns are all null (stateless) or one valid head. */\nfunction toGroupCurrentState(\n row: SelectedGroup,\n): OrganizationGroupCurrentStateResponse | null {\n const values = [\n row.stateHash,\n row.stateVersion,\n row.keyEpoch,\n row.keyFingerprint,\n row.memberCount,\n ];\n if (values.every((value) => value === null)) {\n return null;\n }\n const head =\n row.stateHash === null ||\n row.stateVersion === null ||\n row.keyEpoch === null ||\n row.keyFingerprint === null ||\n row.memberCount === null\n ? null\n : {\n stateHash: row.stateHash,\n version: row.stateVersion,\n keyEpoch: row.keyEpoch,\n keyFingerprint: row.keyFingerprint,\n memberCount: row.memberCount,\n };\n if (!head || !isValidPolicyHead(head)) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization group state is invalid\",\n );\n }\n return head;\n}\n\nfunction toOrganizationPolicyHead(\n organizationId: string,\n head: StoredPolicyHead,\n): OrganizationReadModelPolicyHead {\n if (!isValidPolicyHead(head)) {\n throw new OrganizationReadModelIntegrityError(\n \"Stored organization policy head is invalid\",\n );\n }\n return {\n ...head,\n organizationId,\n principalId: organizationId,\n principalType: \"organization\",\n };\n}\n\n/** The organization head from the state row plus every stateful group head. */\nfunction toProjectionPolicyHeads(input: {\n readonly groups: ReadonlyArray<{\n readonly currentState: OrganizationGroupCurrentStateResponse | null;\n readonly groupId: string;\n }>;\n readonly organizationHead: StoredPolicyHead;\n readonly organizationId: string;\n}): OrganizationReadModelPolicyHead[] {\n const groupHeads = input.groups\n .flatMap((group): OrganizationReadModelPolicyHead[] =>\n group.currentState\n ? [\n {\n ...group.currentState,\n organizationId: input.organizationId,\n principalId: group.groupId,\n principalType: \"group\",\n },\n ]\n : [],\n )\n .sort((left, right) =>\n compareCanonicalStrings(left.principalId, right.principalId),\n );\n // Group heads precede the organization head, ordered by principal ID.\n return [\n ...groupHeads,\n toOrganizationPolicyHead(input.organizationId, input.organizationHead),\n ];\n}\n\nasync function loadDirectoryRows(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<SelectedDirectoryUser[]> {\n return tx\n .select({\n createdAt: organizationReadModelDirectoryUsers.createdAt,\n disabledAt: organizationReadModelDirectoryUsers.disabledAt,\n disabledByUserId: organizationReadModelDirectoryUsers.disabledByUserId,\n encapsulationKeyFingerprint:\n organizationReadModelDirectoryUsers.encapsulationKeyFingerprint,\n encapsulationPublicKey:\n organizationReadModelDirectoryUsers.encapsulationPublicKey,\n joinedAt: organizationReadModelDirectoryUsers.joinedAt,\n isPersonalOrganizationOwner:\n organizationReadModelDirectoryUsers.isPersonalOrganizationOwner,\n profileDocumentId: organizationReadModelDirectoryUsers.profileDocumentId,\n signingKeyFingerprint:\n organizationReadModelDirectoryUsers.signingKeyFingerprint,\n signingPublicKey: organizationReadModelDirectoryUsers.signingPublicKey,\n status: organizationReadModelDirectoryUsers.status,\n updatedAt: organizationReadModelDirectoryUsers.updatedAt,\n userId: organizationReadModelDirectoryUsers.userId,\n })\n .from(organizationReadModelDirectoryUsers)\n .where(\n eq(organizationReadModelDirectoryUsers.organizationId, organizationId),\n )\n .orderBy(\n asc(organizationReadModelDirectoryUsers.sortOrder),\n asc(organizationReadModelDirectoryUsers.userId),\n );\n}\n\nasync function loadGroupRows(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<SelectedGroup[]> {\n return tx\n .select({\n createdAt: organizationReadModelGroups.createdAt,\n groupId: organizationReadModelGroups.groupId,\n isBuiltin: organizationReadModelGroups.isBuiltin,\n keyEpoch: organizationReadModelGroups.keyEpoch,\n keyFingerprint: organizationReadModelGroups.keyFingerprint,\n memberCount: organizationReadModelGroups.memberCount,\n name: organizationReadModelGroups.name,\n stateHash: organizationReadModelGroups.stateHash,\n stateVersion: organizationReadModelGroups.stateVersion,\n })\n .from(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, organizationId))\n .orderBy(\n asc(organizationReadModelGroups.sortOrder),\n asc(organizationReadModelGroups.groupId),\n );\n}\n\nasync function loadMembershipEdges(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<OrganizationReadModelMembershipEdge[]> {\n return tx\n .select({\n groupId: organizationReadModelGroupMembers.groupId,\n userId: organizationReadModelGroupMembers.userId,\n })\n .from(organizationReadModelGroupMembers)\n .where(eq(organizationReadModelGroupMembers.organizationId, organizationId))\n .orderBy(\n asc(organizationReadModelGroupMembers.groupId),\n asc(organizationReadModelGroupMembers.sortOrder),\n asc(organizationReadModelGroupMembers.userId),\n );\n}\n\nexport async function loadOrganizationReadModelProjectionInTransaction(input: {\n readonly currentUserId: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<OrganizationReadModelProjection | null> {\n const [state] = await input.tx\n .select({\n cursor: organizationReadModelState.cursor,\n memberGroupId: organizationReadModelState.memberGroupId,\n organizationKeyEpoch: organizationReadModelState.organizationKeyEpoch,\n organizationKeyFingerprint:\n organizationReadModelState.organizationKeyFingerprint,\n organizationMemberCount:\n organizationReadModelState.organizationMemberCount,\n organizationStateHash: organizationReadModelState.organizationStateHash,\n organizationStateVersion:\n organizationReadModelState.organizationStateVersion,\n profileDocumentId: organizationReadModelState.profileDocumentId,\n updatedAt: organizationReadModelState.updatedAt,\n })\n .from(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, input.organizationId))\n .limit(1);\n if (!state) {\n return null;\n }\n const [requester] = await input.tx\n .select({ isOrgAdmin: organizationReadModelRequesters.isOrgAdmin })\n .from(organizationReadModelRequesters)\n .where(\n and(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n eq(organizationReadModelRequesters.userId, input.currentUserId),\n ),\n )\n .limit(1);\n const directoryRows = await loadDirectoryRows(input.tx, input.organizationId);\n const groupRows = await loadGroupRows(input.tx, input.organizationId);\n const grants = await loadOrganizationReadModelGrantsInTransaction(input);\n const membershipEdges = await loadMembershipEdges(\n input.tx,\n input.organizationId,\n );\n const groups = groupRows.map((row) => ({\n groupId: row.groupId,\n organizationId: input.organizationId,\n name: row.name,\n createdAt: row.createdAt,\n isBuiltin: row.isBuiltin,\n currentState: toGroupCurrentState(row),\n }));\n const policyHeads = toProjectionPolicyHeads({\n groups,\n organizationHead: {\n keyEpoch: state.organizationKeyEpoch,\n keyFingerprint: state.organizationKeyFingerprint,\n memberCount: state.organizationMemberCount,\n stateHash: state.organizationStateHash,\n version: state.organizationStateVersion,\n },\n organizationId: input.organizationId,\n });\n\n return {\n cursor: state.cursor,\n directory: {\n organizationId: input.organizationId,\n profileDocumentId: state.profileDocumentId,\n users: directoryRows.map((row) =>\n toDirectoryUser(row, input.currentUserId),\n ),\n },\n grants,\n groups: {\n organizationId: input.organizationId,\n memberGroupId: state.memberGroupId,\n groups,\n },\n membershipEdges,\n organizationId: input.organizationId,\n policyHeads,\n protocolVersion: ORGANIZATION_READ_MODEL_PROTOCOL_VERSION,\n requester: requester ? { isOrgAdmin: requester.isOrgAdmin } : null,\n updatedAt: state.updatedAt,\n };\n}\n"]}
@@ -1,16 +1,22 @@
1
- import { and, eq, ne, notInArray } from "drizzle-orm";
1
+ import { and, eq, ne, notExists } from "drizzle-orm";
2
2
  import { organizationDataUsageCategories, organizationDataUsageSnapshots, } from "../../sqlite/organizationDataUsageSchema.js";
3
- import { organizationReadModelContainerGrants, organizationReadModelDirectoryUsers, organizationReadModelGroupMembers, organizationReadModelGroupMemberships, organizationReadModelGroups, organizationReadModelPolicyHeads, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
3
+ import { organizationReadModelContainerGrants, organizationReadModelDirectoryUsers, organizationReadModelGroupMembers, organizationReadModelGroups, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
4
4
  import { organizationReadModelTables } from "../../sqlite/schema.js";
5
5
  import { getClientSQLitePersistenceRuntime, } from "../../sqlite/sqlitePersistenceRuntime.js";
6
6
  import { ensureSqlTables } from "../../sqlite/sqlSchema.js";
7
7
  import { notifyOrganizationReadModelInvalidated } from "./organizationReadModelInvalidation.js";
8
8
  import { OrganizationReadModelIntegrityError } from "./organizationReadModelProtocol.js";
9
+ /**
10
+ * Rows for users the persisted directory does not list as active. The
11
+ * exclusion is one anti-join against the directory rows rather than a bound
12
+ * list, so an organization of any size fits SQLite's bound-variable budget.
13
+ */
9
14
  function inactiveUserScope(input, organizationIdColumn, userIdColumn) {
10
- const scope = and(eq(organizationIdColumn, input.organizationId), ne(userIdColumn, input.retainUserId));
11
- return input.activeUserIds.length > 0
12
- ? and(scope, notInArray(userIdColumn, [...input.activeUserIds]))
13
- : scope;
15
+ const directory = organizationReadModelDirectoryUsers;
16
+ return and(eq(organizationIdColumn, input.organizationId), ne(userIdColumn, input.retainUserId), notExists(input.tx
17
+ .select({ userId: directory.userId })
18
+ .from(directory)
19
+ .where(and(eq(directory.organizationId, input.organizationId), eq(directory.userId, userIdColumn), eq(directory.status, "active")))));
14
20
  }
15
21
  /**
16
22
  * Retire requester rows — and the requester-scoped usage projections beside
@@ -18,7 +24,8 @@ function inactiveUserScope(input, organizationIdColumn, userIdColumn) {
18
24
  * active. Without this, a removed member whose identity shares the database
19
25
  * keeps presentation access until it personally sees a 403. The applying
20
26
  * requester's own rows are never pruned here: the server response that
21
- * carried this directory already proved that user's access.
27
+ * carried this directory already proved that user's access. The caller must
28
+ * have replaced the organization's directory rows earlier in `tx`.
22
29
  */
23
30
  export async function pruneInactiveOrganizationRequestersInTransaction(input) {
24
31
  await input.tx
@@ -42,10 +49,6 @@ export async function pruneInactiveOrganizationRequestersInTransaction(input) {
42
49
  * the replacement instead of losing access until their own next reconcile.
43
50
  */
44
51
  export async function purgeOrganizationReadModelProjectionInTransaction(input) {
45
- await input.tx
46
- .delete(organizationReadModelPolicyHeads)
47
- .where(eq(organizationReadModelPolicyHeads.organizationId, input.organizationId))
48
- .run();
49
52
  await input.tx
50
53
  .delete(organizationReadModelContainerGrants)
51
54
  .where(eq(organizationReadModelContainerGrants.organizationId, input.organizationId))
@@ -54,10 +57,6 @@ export async function purgeOrganizationReadModelProjectionInTransaction(input) {
54
57
  .delete(organizationReadModelGroupMembers)
55
58
  .where(eq(organizationReadModelGroupMembers.organizationId, input.organizationId))
56
59
  .run();
57
- await input.tx
58
- .delete(organizationReadModelGroupMemberships)
59
- .where(eq(organizationReadModelGroupMemberships.organizationId, input.organizationId))
60
- .run();
61
60
  await input.tx
62
61
  .delete(organizationReadModelDirectoryUsers)
63
62
  .where(eq(organizationReadModelDirectoryUsers.organizationId, input.organizationId))
@@ -1 +1 @@
1
- {"version":3,"file":"organizationReadModelPurge.js","sourceRoot":"","sources":["organizationReadModelPurge.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, type Column, eq, ne, notInArray, type SQL } from \"drizzle-orm\";\nimport {\n organizationDataUsageCategories,\n organizationDataUsageSnapshots,\n} from \"../../sqlite/organizationDataUsageSchema\";\nimport {\n organizationReadModelContainerGrants,\n organizationReadModelDirectoryUsers,\n organizationReadModelGroupMembers,\n organizationReadModelGroupMemberships,\n organizationReadModelGroups,\n organizationReadModelPolicyHeads,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { notifyOrganizationReadModelInvalidated } from \"./organizationReadModelInvalidation\";\nimport { OrganizationReadModelIntegrityError } from \"./organizationReadModelProtocol\";\n\nfunction inactiveUserScope(\n input: {\n readonly activeUserIds: readonly string[];\n readonly organizationId: string;\n readonly retainUserId: string;\n },\n organizationIdColumn: Column,\n userIdColumn: Column,\n): SQL | undefined {\n const scope = and(\n eq(organizationIdColumn, input.organizationId),\n ne(userIdColumn, input.retainUserId),\n );\n return input.activeUserIds.length > 0\n ? and(scope, notInArray(userIdColumn, [...input.activeUserIds]))\n : scope;\n}\n\n/**\n * Retire requester rows — and the requester-scoped usage projections beside\n * them — for users the just-applied authoritative directory no longer lists as\n * active. Without this, a removed member whose identity shares the database\n * keeps presentation access until it personally sees a 403. The applying\n * requester's own rows are never pruned here: the server response that\n * carried this directory already proved that user's access.\n */\nexport async function pruneInactiveOrganizationRequestersInTransaction(input: {\n readonly activeUserIds: readonly string[];\n readonly organizationId: string;\n readonly retainUserId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelRequesters)\n .where(\n inactiveUserScope(\n input,\n organizationReadModelRequesters.organizationId,\n organizationReadModelRequesters.userId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationDataUsageSnapshots)\n .where(\n inactiveUserScope(\n input,\n organizationDataUsageSnapshots.organizationId,\n organizationDataUsageSnapshots.requesterUserId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationDataUsageCategories)\n .where(\n inactiveUserScope(\n input,\n organizationDataUsageCategories.organizationId,\n organizationDataUsageCategories.requesterUserId,\n ),\n )\n .run();\n}\n\n/**\n * Delete the shared projection rows for one organization. By default every\n * requester row falls with them; `onlyRequesterUserId` scopes the requester\n * delete to that user for rebuilds where the shared rows are about to be\n * replaced authoritatively — other local identities keep their rows and read\n * the replacement instead of losing access until their own next reconcile.\n */\nexport async function purgeOrganizationReadModelProjectionInTransaction(input: {\n readonly onlyRequesterUserId?: string | undefined;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelPolicyHeads)\n .where(\n eq(organizationReadModelPolicyHeads.organizationId, input.organizationId),\n )\n .run();\n await input.tx\n .delete(organizationReadModelContainerGrants)\n .where(\n eq(\n organizationReadModelContainerGrants.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroupMembers)\n .where(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroupMemberships)\n .where(\n eq(\n organizationReadModelGroupMemberships.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelDirectoryUsers)\n .where(\n eq(\n organizationReadModelDirectoryUsers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n await input.tx\n .delete(organizationReadModelRequesters)\n .where(\n input.onlyRequesterUserId === undefined\n ? eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n )\n : and(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n eq(\n organizationReadModelRequesters.userId,\n input.onlyRequesterUserId,\n ),\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, input.organizationId))\n .run();\n}\n\n/**\n * The ONE load shell for the durable read-model projection: run `load` in a\n * transaction and, if a stored row fails integrity validation, purge the\n * organization's projection and report null. The projection is a\n * server-refetchable cache, so the next reconcile requests a cursorless\n * snapshot instead of every read and repair pass failing on the same row.\n */\nexport async function loadWithOrganizationReadModelIntegrityPurge<T>(input: {\n readonly execSql: ExecSql;\n readonly load: (tx: ClientSQLiteTransactionScope) => Promise<T | null>;\n readonly organizationId: string;\n}): Promise<T | null> {\n await ensureSqlTables(input.execSql, organizationReadModelTables);\n let purgedInvalidRows = false;\n const result = await getClientSQLitePersistenceRuntime(\n input.execSql,\n ).transaction(async (tx) => {\n try {\n return await input.load(tx);\n } catch (error) {\n if (!(error instanceof OrganizationReadModelIntegrityError)) {\n throw error;\n }\n await purgeOrganizationReadModelProjectionInTransaction({\n organizationId: input.organizationId,\n tx,\n });\n purgedInvalidRows = true;\n return null;\n }\n });\n if (purgedInvalidRows) {\n // Realtime consumers may hold a caught-up lease over rows that no longer\n // exist; the notification lets them drop it and refetch authoritatively.\n notifyOrganizationReadModelInvalidated(input.execSql, input.organizationId);\n }\n return result;\n}\n"]}
1
+ {"version":3,"file":"organizationReadModelPurge.js","sourceRoot":"","sources":["organizationReadModelPurge.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, type Column, eq, ne, notExists, type SQL } from \"drizzle-orm\";\nimport {\n organizationDataUsageCategories,\n organizationDataUsageSnapshots,\n} from \"../../sqlite/organizationDataUsageSchema\";\nimport {\n organizationReadModelContainerGrants,\n organizationReadModelDirectoryUsers,\n organizationReadModelGroupMembers,\n organizationReadModelGroups,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { notifyOrganizationReadModelInvalidated } from \"./organizationReadModelInvalidation\";\nimport { OrganizationReadModelIntegrityError } from \"./organizationReadModelProtocol\";\n\n/**\n * Rows for users the persisted directory does not list as active. The\n * exclusion is one anti-join against the directory rows rather than a bound\n * list, so an organization of any size fits SQLite's bound-variable budget.\n */\nfunction inactiveUserScope(\n input: {\n readonly organizationId: string;\n readonly retainUserId: string;\n readonly tx: ClientSQLiteTransactionScope;\n },\n organizationIdColumn: Column,\n userIdColumn: Column,\n): SQL | undefined {\n const directory = organizationReadModelDirectoryUsers;\n return and(\n eq(organizationIdColumn, input.organizationId),\n ne(userIdColumn, input.retainUserId),\n notExists(\n input.tx\n .select({ userId: directory.userId })\n .from(directory)\n .where(\n and(\n eq(directory.organizationId, input.organizationId),\n eq(directory.userId, userIdColumn),\n eq(directory.status, \"active\"),\n ),\n ),\n ),\n );\n}\n\n/**\n * Retire requester rows — and the requester-scoped usage projections beside\n * them — for users the just-applied authoritative directory no longer lists as\n * active. Without this, a removed member whose identity shares the database\n * keeps presentation access until it personally sees a 403. The applying\n * requester's own rows are never pruned here: the server response that\n * carried this directory already proved that user's access. The caller must\n * have replaced the organization's directory rows earlier in `tx`.\n */\nexport async function pruneInactiveOrganizationRequestersInTransaction(input: {\n readonly organizationId: string;\n readonly retainUserId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelRequesters)\n .where(\n inactiveUserScope(\n input,\n organizationReadModelRequesters.organizationId,\n organizationReadModelRequesters.userId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationDataUsageSnapshots)\n .where(\n inactiveUserScope(\n input,\n organizationDataUsageSnapshots.organizationId,\n organizationDataUsageSnapshots.requesterUserId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationDataUsageCategories)\n .where(\n inactiveUserScope(\n input,\n organizationDataUsageCategories.organizationId,\n organizationDataUsageCategories.requesterUserId,\n ),\n )\n .run();\n}\n\n/**\n * Delete the shared projection rows for one organization. By default every\n * requester row falls with them; `onlyRequesterUserId` scopes the requester\n * delete to that user for rebuilds where the shared rows are about to be\n * replaced authoritatively — other local identities keep their rows and read\n * the replacement instead of losing access until their own next reconcile.\n */\nexport async function purgeOrganizationReadModelProjectionInTransaction(input: {\n readonly onlyRequesterUserId?: string | undefined;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelContainerGrants)\n .where(\n eq(\n organizationReadModelContainerGrants.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroupMembers)\n .where(\n eq(\n organizationReadModelGroupMembers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelDirectoryUsers)\n .where(\n eq(\n organizationReadModelDirectoryUsers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n await input.tx\n .delete(organizationReadModelRequesters)\n .where(\n input.onlyRequesterUserId === undefined\n ? eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n )\n : and(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n eq(\n organizationReadModelRequesters.userId,\n input.onlyRequesterUserId,\n ),\n ),\n )\n .run();\n await input.tx\n .delete(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, input.organizationId))\n .run();\n}\n\n/**\n * The ONE load shell for the durable read-model projection: run `load` in a\n * transaction and, if a stored row fails integrity validation, purge the\n * organization's projection and report null. The projection is a\n * server-refetchable cache, so the next reconcile requests a cursorless\n * snapshot instead of every read and repair pass failing on the same row.\n */\nexport async function loadWithOrganizationReadModelIntegrityPurge<T>(input: {\n readonly execSql: ExecSql;\n readonly load: (tx: ClientSQLiteTransactionScope) => Promise<T | null>;\n readonly organizationId: string;\n}): Promise<T | null> {\n await ensureSqlTables(input.execSql, organizationReadModelTables);\n let purgedInvalidRows = false;\n const result = await getClientSQLitePersistenceRuntime(\n input.execSql,\n ).transaction(async (tx) => {\n try {\n return await input.load(tx);\n } catch (error) {\n if (!(error instanceof OrganizationReadModelIntegrityError)) {\n throw error;\n }\n await purgeOrganizationReadModelProjectionInTransaction({\n organizationId: input.organizationId,\n tx,\n });\n purgedInvalidRows = true;\n return null;\n }\n });\n if (purgedInvalidRows) {\n // Realtime consumers may hold a caught-up lease over rows that no longer\n // exist; the notification lets them drop it and refetch authoritatively.\n notifyOrganizationReadModelInvalidated(input.execSql, input.organizationId);\n }\n return result;\n}\n"]}
@@ -1,10 +1,20 @@
1
1
  import { integer, primaryKey, sqliteTable, text, } from "drizzle-orm/sqlite-core";
2
- /** Cursor and lane-level fields for one locally projected organization. */
2
+ /**
3
+ * Cursor and lane-level fields for one locally projected organization: the
4
+ * exact organization policy head, and the state hash of the reserved Members
5
+ * group's membership head, which the group catalog does not list.
6
+ */
3
7
  export const organizationReadModelState = sqliteTable("organization_read_model_state", {
4
8
  organizationId: text("organization_id").notNull(),
5
9
  cursor: text("cursor").notNull(),
6
10
  profileDocumentId: text("profile_document_id"),
7
11
  memberGroupId: text("member_group_id").notNull(),
12
+ memberGroupStateHash: text("member_group_state_hash").notNull(),
13
+ organizationStateHash: text("organization_state_hash").notNull(),
14
+ organizationStateVersion: integer("organization_state_version").notNull(),
15
+ organizationKeyEpoch: integer("organization_key_epoch").notNull(),
16
+ organizationKeyFingerprint: text("organization_key_fingerprint").notNull(),
17
+ organizationMemberCount: integer("organization_member_count").notNull(),
8
18
  updatedAt: text("updated_at").notNull(),
9
19
  }, (table) => [primaryKey({ columns: [table.organizationId] })]);
10
20
  /** Requester-scoped fields that must not become organization-global state. */
@@ -34,7 +44,12 @@ export const organizationReadModelDirectoryUsers = sqliteTable("organization_rea
34
44
  disabledAt: text("disabled_at"),
35
45
  disabledByUserId: text("disabled_by_user_id"),
36
46
  }, (table) => [primaryKey({ columns: [table.organizationId, table.userId] })]);
37
- /** Normalized organization group summaries and their current state heads. */
47
+ /**
48
+ * Normalized visible organization group summaries. The state columns are the
49
+ * group's exact policy head, all null for a stateless group.
50
+ * `members_state_hash` is the state hash of the membership head delivered for
51
+ * the group, null until the groupMemberships lane covers it.
52
+ */
38
53
  export const organizationReadModelGroups = sqliteTable("organization_read_model_groups", {
39
54
  organizationId: text("organization_id").notNull(),
40
55
  groupId: text("group_id").notNull(),
@@ -45,30 +60,14 @@ export const organizationReadModelGroups = sqliteTable("organization_read_model_
45
60
  stateHash: text("state_hash"),
46
61
  stateVersion: integer("state_version"),
47
62
  keyEpoch: integer("key_epoch"),
63
+ keyFingerprint: text("key_fingerprint"),
48
64
  memberCount: integer("member_count"),
65
+ membersStateHash: text("members_state_hash"),
49
66
  }, (table) => [primaryKey({ columns: [table.organizationId, table.groupId] })]);
50
- /** Exact policy-state heads used to bind locally projected cryptographic data. */
51
- export const organizationReadModelPolicyHeads = sqliteTable("organization_read_model_policy_heads", {
52
- organizationId: text("organization_id").notNull(),
53
- principalType: text("principal_type").notNull(),
54
- principalId: text("principal_id").notNull(),
55
- stateHash: text("state_hash").notNull(),
56
- stateVersion: integer("state_version").notNull(),
57
- keyEpoch: integer("key_epoch").notNull(),
58
- keyFingerprint: text("key_fingerprint").notNull(),
59
- memberCount: integer("member_count").notNull(),
60
- }, (table) => [
61
- primaryKey({
62
- columns: [table.organizationId, table.principalType, table.principalId],
63
- }),
64
- ]);
65
- /** State-hash-bound completeness rows for each projected group membership. */
66
- export const organizationReadModelGroupMemberships = sqliteTable("organization_read_model_group_memberships", {
67
- organizationId: text("organization_id").notNull(),
68
- groupId: text("group_id").notNull(),
69
- stateHash: text("state_hash").notNull(),
70
- }, (table) => [primaryKey({ columns: [table.organizationId, table.groupId] })]);
71
- /** Direct users bound to the exact state in the membership completeness row. */
67
+ /**
68
+ * Direct users bound to the exact state of their group's membership head.
69
+ * Public keys are not repeated here; the directory lane holds them per user.
70
+ */
72
71
  export const organizationReadModelGroupMembers = sqliteTable("organization_read_model_group_members", {
73
72
  organizationId: text("organization_id").notNull(),
74
73
  groupId: text("group_id").notNull(),
@@ -76,10 +75,6 @@ export const organizationReadModelGroupMembers = sqliteTable("organization_read_
76
75
  sortOrder: integer("sort_order").notNull(),
77
76
  stateHash: text("state_hash").notNull(),
78
77
  role: text("role").notNull(),
79
- signingKeyFingerprint: text("signing_key_fingerprint"),
80
- signingPublicKey: text("signing_public_key"),
81
- encapsulationPublicKey: text("encapsulation_public_key"),
82
- encapsulationKeyFingerprint: text("encapsulation_key_fingerprint"),
83
78
  }, (table) => [
84
79
  primaryKey({
85
80
  columns: [table.organizationId, table.groupId, table.userId],
@@ -1 +1 @@
1
- {"version":3,"file":"organizationReadModelSchema.js","sourceRoot":"","sources":["organizationReadModelSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n integer,\n primaryKey,\n sqliteTable,\n text,\n} from \"drizzle-orm/sqlite-core\";\n\n/** Cursor and lane-level fields for one locally projected organization. */\nexport const organizationReadModelState = sqliteTable(\n \"organization_read_model_state\",\n {\n organizationId: text(\"organization_id\").notNull(),\n cursor: text(\"cursor\").notNull(),\n profileDocumentId: text(\"profile_document_id\"),\n memberGroupId: text(\"member_group_id\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.organizationId] })],\n);\n\n/** Requester-scoped fields that must not become organization-global state. */\nexport const organizationReadModelRequesters = sqliteTable(\n \"organization_read_model_requesters\",\n {\n organizationId: text(\"organization_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n isOrgAdmin: integer(\"is_org_admin\", { mode: \"boolean\" }).notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.userId] })],\n);\n\n/** Normalized organization directory rows. `isSelf` is derived at read time. */\nexport const organizationReadModelDirectoryUsers = sqliteTable(\n \"organization_read_model_directory_users\",\n {\n organizationId: text(\"organization_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n signingKeyFingerprint: text(\"signing_key_fingerprint\").notNull(),\n signingPublicKey: text(\"signing_public_key\").notNull(),\n encapsulationPublicKey: text(\"encapsulation_public_key\").notNull(),\n encapsulationKeyFingerprint: text(\n \"encapsulation_key_fingerprint\",\n ).notNull(),\n isPersonalOrganizationOwner: integer(\"is_personal_organization_owner\", {\n mode: \"boolean\",\n }).notNull(),\n createdAt: text(\"created_at\").notNull(),\n status: text(\"status\").notNull(),\n profileDocumentId: text(\"profile_document_id\"),\n joinedAt: text(\"joined_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n disabledAt: text(\"disabled_at\"),\n disabledByUserId: text(\"disabled_by_user_id\"),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.userId] })],\n);\n\n/** Normalized organization group summaries and their current state heads. */\nexport const organizationReadModelGroups = sqliteTable(\n \"organization_read_model_groups\",\n {\n organizationId: text(\"organization_id\").notNull(),\n groupId: text(\"group_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n name: text(\"name\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n isBuiltin: integer(\"is_builtin\", { mode: \"boolean\" }).notNull(),\n stateHash: text(\"state_hash\"),\n stateVersion: integer(\"state_version\"),\n keyEpoch: integer(\"key_epoch\"),\n memberCount: integer(\"member_count\"),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.groupId] })],\n);\n\n/** Exact policy-state heads used to bind locally projected cryptographic data. */\nexport const organizationReadModelPolicyHeads = sqliteTable(\n \"organization_read_model_policy_heads\",\n {\n organizationId: text(\"organization_id\").notNull(),\n principalType: text(\"principal_type\").notNull(),\n principalId: text(\"principal_id\").notNull(),\n stateHash: text(\"state_hash\").notNull(),\n stateVersion: integer(\"state_version\").notNull(),\n keyEpoch: integer(\"key_epoch\").notNull(),\n keyFingerprint: text(\"key_fingerprint\").notNull(),\n memberCount: integer(\"member_count\").notNull(),\n },\n (table) => [\n primaryKey({\n columns: [table.organizationId, table.principalType, table.principalId],\n }),\n ],\n);\n\n/** State-hash-bound completeness rows for each projected group membership. */\nexport const organizationReadModelGroupMemberships = sqliteTable(\n \"organization_read_model_group_memberships\",\n {\n organizationId: text(\"organization_id\").notNull(),\n groupId: text(\"group_id\").notNull(),\n stateHash: text(\"state_hash\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.groupId] })],\n);\n\n/** Direct users bound to the exact state in the membership completeness row. */\nexport const organizationReadModelGroupMembers = sqliteTable(\n \"organization_read_model_group_members\",\n {\n organizationId: text(\"organization_id\").notNull(),\n groupId: text(\"group_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n stateHash: text(\"state_hash\").notNull(),\n role: text(\"role\").notNull(),\n signingKeyFingerprint: text(\"signing_key_fingerprint\"),\n signingPublicKey: text(\"signing_public_key\"),\n encapsulationPublicKey: text(\"encapsulation_public_key\"),\n encapsulationKeyFingerprint: text(\"encapsulation_key_fingerprint\"),\n },\n (table) => [\n primaryKey({\n columns: [table.organizationId, table.groupId, table.userId],\n }),\n ],\n);\n\n/**\n * Durable record of a server 403/404 for one requester's presentation\n * projection. Written before the revoked rows are purged so a purge failure\n * followed by an offline restart cannot serve the revoked projection; cleared\n * only by a successful authoritative reconcile for the same scope.\n */\nexport const organizationPresentationDenials = sqliteTable(\n \"organization_presentation_denials\",\n {\n organizationId: text(\"organization_id\").notNull(),\n requesterUserId: text(\"requester_user_id\").notNull(),\n projection: text(\"projection\").notNull(),\n deniedAt: text(\"denied_at\").notNull(),\n },\n (table) => [\n primaryKey({\n columns: [table.organizationId, table.requesterUserId, table.projection],\n }),\n ],\n);\n\n/** Whole-lane container grants used only by organization presentation reads. */\nexport const organizationReadModelContainerGrants = sqliteTable(\n \"organization_read_model_container_grants\",\n {\n organizationId: text(\"organization_id\").notNull(),\n containerId: text(\"container_id\").notNull(),\n subjectType: text(\"subject_type\").notNull(),\n subjectId: text(\"subject_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n accessLevel: text(\"access_level\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n depth: integer(\"depth\").notNull(),\n isBuiltin: integer(\"is_builtin\", { mode: \"boolean\" }).notNull(),\n metadataAccessEpoch: integer(\"metadata_access_epoch\").notNull(),\n metadataAccessStateHash: text(\"metadata_access_state_hash\").notNull(),\n metadataDocumentId: text(\"metadata_document_id\"),\n parentId: text(\"parent_id\"),\n updatedAt: text(\"updated_at\").notNull(),\n userId: text(\"user_id\"),\n signingKeyFingerprint: text(\"signing_key_fingerprint\"),\n groupId: text(\"group_id\"),\n },\n (table) => [\n primaryKey({\n columns: [\n table.organizationId,\n table.containerId,\n table.subjectType,\n table.subjectId,\n ],\n }),\n ],\n);\n"]}
1
+ {"version":3,"file":"organizationReadModelSchema.js","sourceRoot":"","sources":["organizationReadModelSchema.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n integer,\n primaryKey,\n sqliteTable,\n text,\n} from \"drizzle-orm/sqlite-core\";\n\n/**\n * Cursor and lane-level fields for one locally projected organization: the\n * exact organization policy head, and the state hash of the reserved Members\n * group's membership head, which the group catalog does not list.\n */\nexport const organizationReadModelState = sqliteTable(\n \"organization_read_model_state\",\n {\n organizationId: text(\"organization_id\").notNull(),\n cursor: text(\"cursor\").notNull(),\n profileDocumentId: text(\"profile_document_id\"),\n memberGroupId: text(\"member_group_id\").notNull(),\n memberGroupStateHash: text(\"member_group_state_hash\").notNull(),\n organizationStateHash: text(\"organization_state_hash\").notNull(),\n organizationStateVersion: integer(\"organization_state_version\").notNull(),\n organizationKeyEpoch: integer(\"organization_key_epoch\").notNull(),\n organizationKeyFingerprint: text(\"organization_key_fingerprint\").notNull(),\n organizationMemberCount: integer(\"organization_member_count\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.organizationId] })],\n);\n\n/** Requester-scoped fields that must not become organization-global state. */\nexport const organizationReadModelRequesters = sqliteTable(\n \"organization_read_model_requesters\",\n {\n organizationId: text(\"organization_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n isOrgAdmin: integer(\"is_org_admin\", { mode: \"boolean\" }).notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.userId] })],\n);\n\n/** Normalized organization directory rows. `isSelf` is derived at read time. */\nexport const organizationReadModelDirectoryUsers = sqliteTable(\n \"organization_read_model_directory_users\",\n {\n organizationId: text(\"organization_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n signingKeyFingerprint: text(\"signing_key_fingerprint\").notNull(),\n signingPublicKey: text(\"signing_public_key\").notNull(),\n encapsulationPublicKey: text(\"encapsulation_public_key\").notNull(),\n encapsulationKeyFingerprint: text(\n \"encapsulation_key_fingerprint\",\n ).notNull(),\n isPersonalOrganizationOwner: integer(\"is_personal_organization_owner\", {\n mode: \"boolean\",\n }).notNull(),\n createdAt: text(\"created_at\").notNull(),\n status: text(\"status\").notNull(),\n profileDocumentId: text(\"profile_document_id\"),\n joinedAt: text(\"joined_at\").notNull(),\n updatedAt: text(\"updated_at\").notNull(),\n disabledAt: text(\"disabled_at\"),\n disabledByUserId: text(\"disabled_by_user_id\"),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.userId] })],\n);\n\n/**\n * Normalized visible organization group summaries. The state columns are the\n * group's exact policy head, all null for a stateless group.\n * `members_state_hash` is the state hash of the membership head delivered for\n * the group, null until the groupMemberships lane covers it.\n */\nexport const organizationReadModelGroups = sqliteTable(\n \"organization_read_model_groups\",\n {\n organizationId: text(\"organization_id\").notNull(),\n groupId: text(\"group_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n name: text(\"name\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n isBuiltin: integer(\"is_builtin\", { mode: \"boolean\" }).notNull(),\n stateHash: text(\"state_hash\"),\n stateVersion: integer(\"state_version\"),\n keyEpoch: integer(\"key_epoch\"),\n keyFingerprint: text(\"key_fingerprint\"),\n memberCount: integer(\"member_count\"),\n membersStateHash: text(\"members_state_hash\"),\n },\n (table) => [primaryKey({ columns: [table.organizationId, table.groupId] })],\n);\n\n/**\n * Direct users bound to the exact state of their group's membership head.\n * Public keys are not repeated here; the directory lane holds them per user.\n */\nexport const organizationReadModelGroupMembers = sqliteTable(\n \"organization_read_model_group_members\",\n {\n organizationId: text(\"organization_id\").notNull(),\n groupId: text(\"group_id\").notNull(),\n userId: text(\"user_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n stateHash: text(\"state_hash\").notNull(),\n role: text(\"role\").notNull(),\n },\n (table) => [\n primaryKey({\n columns: [table.organizationId, table.groupId, table.userId],\n }),\n ],\n);\n\n/**\n * Durable record of a server 403/404 for one requester's presentation\n * projection. Written before the revoked rows are purged so a purge failure\n * followed by an offline restart cannot serve the revoked projection; cleared\n * only by a successful authoritative reconcile for the same scope.\n */\nexport const organizationPresentationDenials = sqliteTable(\n \"organization_presentation_denials\",\n {\n organizationId: text(\"organization_id\").notNull(),\n requesterUserId: text(\"requester_user_id\").notNull(),\n projection: text(\"projection\").notNull(),\n deniedAt: text(\"denied_at\").notNull(),\n },\n (table) => [\n primaryKey({\n columns: [table.organizationId, table.requesterUserId, table.projection],\n }),\n ],\n);\n\n/** Whole-lane container grants used only by organization presentation reads. */\nexport const organizationReadModelContainerGrants = sqliteTable(\n \"organization_read_model_container_grants\",\n {\n organizationId: text(\"organization_id\").notNull(),\n containerId: text(\"container_id\").notNull(),\n subjectType: text(\"subject_type\").notNull(),\n subjectId: text(\"subject_id\").notNull(),\n sortOrder: integer(\"sort_order\").notNull(),\n accessLevel: text(\"access_level\").notNull(),\n createdAt: text(\"created_at\").notNull(),\n depth: integer(\"depth\").notNull(),\n isBuiltin: integer(\"is_builtin\", { mode: \"boolean\" }).notNull(),\n metadataAccessEpoch: integer(\"metadata_access_epoch\").notNull(),\n metadataAccessStateHash: text(\"metadata_access_state_hash\").notNull(),\n metadataDocumentId: text(\"metadata_document_id\"),\n parentId: text(\"parent_id\"),\n updatedAt: text(\"updated_at\").notNull(),\n userId: text(\"user_id\"),\n signingKeyFingerprint: text(\"signing_key_fingerprint\"),\n groupId: text(\"group_id\"),\n },\n (table) => [\n primaryKey({\n columns: [\n table.organizationId,\n table.containerId,\n table.subjectType,\n table.subjectId,\n ],\n }),\n ],\n);\n"]}