@tearleads/client-sdk 0.1.56 → 0.1.57

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 (25) hide show
  1. package/data/persistence/organizations/organizationAccessRevocationPersistence.js +2 -2
  2. package/data/persistence/organizations/organizationAccessRevocationPersistence.js.map +1 -1
  3. package/data/persistence/organizations/organizationReadModelPersistence.js +2 -2
  4. package/data/persistence/organizations/organizationReadModelPersistence.js.map +1 -1
  5. package/data/principals/principalPolicyCoordinator.js +40 -0
  6. package/data/principals/principalPolicyCoordinator.js.map +1 -0
  7. package/package.json +1 -1
  8. package/workflows/organizations/principalMutationJournalManagement.js +3 -3
  9. package/workflows/organizations/principalMutationJournalManagement.js.map +1 -1
  10. package/workflows/organizations/principalMutationJournalSession.js +3 -3
  11. package/workflows/organizations/principalMutationJournalSession.js.map +1 -1
  12. package/workflows/principals/principalRecoveryMemoValidity.js +2 -2
  13. package/workflows/principals/principalRecoveryMemoValidity.js.map +1 -1
  14. package/workflows/principals/runtimePolicyRecovery.js +2 -2
  15. package/workflows/principals/runtimePolicyRecovery.js.map +1 -1
  16. package/workflows/principals/runtimeProjectionPolicyRecovery.js +2 -2
  17. package/workflows/principals/runtimeProjectionPolicyRecovery.js.map +1 -1
  18. package/workflows/registration/provisionedPrincipalHistory.js +2 -2
  19. package/workflows/registration/provisionedPrincipalHistory.js.map +1 -1
  20. package/data/principals/principalRecoveryEpoch.js +0 -18
  21. package/data/principals/principalRecoveryEpoch.js.map +0 -1
  22. package/workflows/organizations/principalMutationJournalLane.js +0 -26
  23. package/workflows/organizations/principalMutationJournalLane.js.map +0 -1
  24. package/workflows/principals/principalRecoveryQueue.js +0 -22
  25. package/workflows/principals/principalRecoveryQueue.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { and, eq } from "drizzle-orm";
2
- import { invalidatePrincipalRecovery } from "../../principals/principalRecoveryEpoch.js";
2
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../principals/principalPolicyCoordinator.js";
3
3
  import { organizationDataUsageTables } from "../../sqlite/organizationDataUsageSchema.js";
4
4
  import { organizationReadModelRequesters } from "../../sqlite/organizationReadModelSchema.js";
5
5
  import { organizationReadModelTables } from "../../sqlite/schema.js";
@@ -44,6 +44,6 @@ export async function purgeOrganizationAccessProjection(input) {
44
44
  });
45
45
  // A denial is a realtime-hinted access change: in-flight operations recover
46
46
  // the organization's directory again instead of reusing their memo.
47
- invalidatePrincipalRecovery(input.execSql, input.organizationId);
47
+ getOrCreatePrincipalPolicyCoordinator(input.execSql).invalidateRecovery(input.organizationId);
48
48
  }
49
49
  //# sourceMappingURL=organizationAccessRevocationPersistence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"organizationAccessRevocationPersistence.js","sourceRoot":"","sources":["organizationAccessRevocationPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,2BAA2B,EAAE,MAAM,yCAAyC,CAAC;AACtF,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,OAAO,EAAE,+BAA+B,EAAE,MAAM,0CAA0C,CAAC;AAC3F,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAE1F,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,iDAAiD,EAAE,MAAM,oCAAoC,CAAC;AACvG,OAAO,EAAE,iDAAiD,EAAE,MAAM,8BAA8B,CAAC;AAEjG,MAAM,kCAAkC,GAAG;IACzC,GAAG,2BAA2B;IAC9B,GAAG,2BAA2B;CAC/B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC,CAAC,KAIvD;IACC,MAAM,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,kCAAkC,CAAC,CAAC;IACzE,MAAM,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAChE,KAAK,EAAE,EAAE,EAAE,EAAE;QACX,MAAM,EAAE;aACL,MAAM,CAAC,+BAA+B,CAAC;aACvC,KAAK,CACJ,GAAG,CACD,EAAE,CACA,+BAA+B,CAAC,cAAc,EAC9C,KAAK,CAAC,cAAc,CACrB,EACD,EAAE,CAAC,+BAA+B,CAAC,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,CAClE,CACF;aACA,GAAG,EAAE,CAAC;QACT,MAAM,CAAC,kBAAkB,CAAC,GAAG,MAAM,EAAE;aAClC,MAAM,CAAC,EAAE,MAAM,EAAE,+BAA+B,CAAC,MAAM,EAAE,CAAC;aAC1D,IAAI,CAAC,+BAA+B,CAAC;aACrC,KAAK,CACJ,EAAE,CACA,+BAA+B,CAAC,cAAc,EAC9C,KAAK,CAAC,cAAc,CACrB,CACF;aACA,KAAK,CAAC,CAAC,CAAC,CAAC;QACZ,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,MAAM,iDAAiD,CAAC;gBACtD,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,EAAE;aACH,CAAC,CAAC;QACL,CAAC;QACD,MAAM,iDAAiD,CAAC;YACtD,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,EAAE;SACH,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IACF,4EAA4E;IAC5E,oEAAoE;IACpE,2BAA2B,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;AACnE,CAAC","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { invalidatePrincipalRecovery } from \"../../principals/principalRecoveryEpoch\";\nimport { organizationDataUsageTables } from \"../../sqlite/organizationDataUsageSchema\";\nimport { organizationReadModelRequesters } from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport { ensureSqlTables } from \"../../sqlite/sqlTableSchema\";\nimport { purgeOrganizationDataUsageProjectionInTransaction } from \"./organizationDataUsagePersistence\";\nimport { purgeOrganizationReadModelProjectionInTransaction } from \"./organizationReadModelPurge\";\n\nconst organizationAccessProjectionTables = [\n ...organizationReadModelTables,\n ...organizationDataUsageTables,\n];\n\n/**\n * Purge one requester's access to an organization's presentation projections\n * after the server denied that requester. The denial is requester-scoped: the\n * shared read-model rows stay while another local identity in the same\n * database still holds a requester row, and organization-wide rows fall with\n * the last requester.\n */\nexport async function purgeOrganizationAccessProjection(input: {\n readonly execSql: ExecSql;\n readonly organizationId: string;\n readonly requesterUserId: string;\n}): Promise<void> {\n await ensureSqlTables(input.execSql, organizationAccessProjectionTables);\n await getClientSQLitePersistenceRuntime(input.execSql).transaction(\n async (tx) => {\n await tx\n .delete(organizationReadModelRequesters)\n .where(\n and(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n eq(organizationReadModelRequesters.userId, input.requesterUserId),\n ),\n )\n .run();\n const [remainingRequester] = await tx\n .select({ userId: organizationReadModelRequesters.userId })\n .from(organizationReadModelRequesters)\n .where(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n )\n .limit(1);\n if (!remainingRequester) {\n await purgeOrganizationReadModelProjectionInTransaction({\n organizationId: input.organizationId,\n tx,\n });\n }\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId: input.organizationId,\n requesterUserId: input.requesterUserId,\n tx,\n });\n },\n );\n // A denial is a realtime-hinted access change: in-flight operations recover\n // the organization's directory again instead of reusing their memo.\n invalidatePrincipalRecovery(input.execSql, input.organizationId);\n}\n"]}
1
+ {"version":3,"file":"organizationAccessRevocationPersistence.js","sourceRoot":"","sources":["organizationAccessRevocationPersistence.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,qCAAqC,EAAE,MAAM,6CAA6C,CAAC;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,OAAO,EAAE,+BAA+B,EAAE,MAAM,0CAA0C,CAAC;AAC3F,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,iCAAiC,EAAE,MAAM,uCAAuC,CAAC;AAE1F,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,iDAAiD,EAAE,MAAM,oCAAoC,CAAC;AACvG,OAAO,EAAE,iDAAiD,EAAE,MAAM,8BAA8B,CAAC;AAEjG,MAAM,kCAAkC,GAAG;IACzC,GAAG,2BAA2B;IAC9B,GAAG,2BAA2B;CAC/B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iCAAiC,CAAC,KAIvD;IACC,MAAM,eAAe,CAAC,KAAK,CAAC,OAAO,EAAE,kCAAkC,CAAC,CAAC;IACzE,MAAM,iCAAiC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,WAAW,CAChE,KAAK,EAAE,EAAE,EAAE,EAAE;QACX,MAAM,EAAE;aACL,MAAM,CAAC,+BAA+B,CAAC;aACvC,KAAK,CACJ,GAAG,CACD,EAAE,CACA,+BAA+B,CAAC,cAAc,EAC9C,KAAK,CAAC,cAAc,CACrB,EACD,EAAE,CAAC,+BAA+B,CAAC,MAAM,EAAE,KAAK,CAAC,eAAe,CAAC,CAClE,CACF;aACA,GAAG,EAAE,CAAC;QACT,MAAM,CAAC,kBAAkB,CAAC,GAAG,MAAM,EAAE;aAClC,MAAM,CAAC,EAAE,MAAM,EAAE,+BAA+B,CAAC,MAAM,EAAE,CAAC;aAC1D,IAAI,CAAC,+BAA+B,CAAC;aACrC,KAAK,CACJ,EAAE,CACA,+BAA+B,CAAC,cAAc,EAC9C,KAAK,CAAC,cAAc,CACrB,CACF;aACA,KAAK,CAAC,CAAC,CAAC,CAAC;QACZ,IAAI,CAAC,kBAAkB,EAAE,CAAC;YACxB,MAAM,iDAAiD,CAAC;gBACtD,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,EAAE;aACH,CAAC,CAAC;QACL,CAAC;QACD,MAAM,iDAAiD,CAAC;YACtD,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,EAAE;SACH,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IACF,4EAA4E;IAC5E,oEAAoE;IACpE,qCAAqC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,kBAAkB,CACrE,KAAK,CAAC,cAAc,CACrB,CAAC;AACJ,CAAC","sourcesContent":["import { and, eq } from \"drizzle-orm\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../principals/principalPolicyCoordinator\";\nimport { organizationDataUsageTables } from \"../../sqlite/organizationDataUsageSchema\";\nimport { organizationReadModelRequesters } from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport { getClientSQLitePersistenceRuntime } from \"../../sqlite/sqlitePersistenceRuntime\";\nimport type { ExecSql } from \"../../sqlite/sqlSchema\";\nimport { ensureSqlTables } from \"../../sqlite/sqlTableSchema\";\nimport { purgeOrganizationDataUsageProjectionInTransaction } from \"./organizationDataUsagePersistence\";\nimport { purgeOrganizationReadModelProjectionInTransaction } from \"./organizationReadModelPurge\";\n\nconst organizationAccessProjectionTables = [\n ...organizationReadModelTables,\n ...organizationDataUsageTables,\n];\n\n/**\n * Purge one requester's access to an organization's presentation projections\n * after the server denied that requester. The denial is requester-scoped: the\n * shared read-model rows stay while another local identity in the same\n * database still holds a requester row, and organization-wide rows fall with\n * the last requester.\n */\nexport async function purgeOrganizationAccessProjection(input: {\n readonly execSql: ExecSql;\n readonly organizationId: string;\n readonly requesterUserId: string;\n}): Promise<void> {\n await ensureSqlTables(input.execSql, organizationAccessProjectionTables);\n await getClientSQLitePersistenceRuntime(input.execSql).transaction(\n async (tx) => {\n await tx\n .delete(organizationReadModelRequesters)\n .where(\n and(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n eq(organizationReadModelRequesters.userId, input.requesterUserId),\n ),\n )\n .run();\n const [remainingRequester] = await tx\n .select({ userId: organizationReadModelRequesters.userId })\n .from(organizationReadModelRequesters)\n .where(\n eq(\n organizationReadModelRequesters.organizationId,\n input.organizationId,\n ),\n )\n .limit(1);\n if (!remainingRequester) {\n await purgeOrganizationReadModelProjectionInTransaction({\n organizationId: input.organizationId,\n tx,\n });\n }\n await purgeOrganizationDataUsageProjectionInTransaction({\n organizationId: input.organizationId,\n requesterUserId: input.requesterUserId,\n tx,\n });\n },\n );\n // A denial is a realtime-hinted access change: in-flight operations recover\n // the organization's directory again instead of reusing their memo.\n getOrCreatePrincipalPolicyCoordinator(input.execSql).invalidateRecovery(\n input.organizationId,\n );\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { eq } from "drizzle-orm";
2
- import { invalidatePrincipalRecovery } from "../../principals/principalRecoveryEpoch.js";
2
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../principals/principalPolicyCoordinator.js";
3
3
  import { organizationDataUsageTables } from "../../sqlite/organizationDataUsageSchema.js";
4
4
  import { organizationReadModelDirectoryUsers, organizationReadModelGroups, organizationReadModelRequesters, organizationReadModelState, } from "../../sqlite/organizationReadModelSchema.js";
5
5
  import { organizationReadModelTables } from "../../sqlite/schema.js";
@@ -313,7 +313,7 @@ export async function applyOrganizationReadModelResponse(input) {
313
313
  // An applied change follows an organization hint or mutation; in-flight
314
314
  // operations recover the directory again instead of reusing their memo.
315
315
  if (disposition === "applied")
316
- invalidatePrincipalRecovery(input.execSql, input.response.organizationId);
316
+ getOrCreatePrincipalPolicyCoordinator(input.execSql).invalidateRecovery(input.response.organizationId);
317
317
  return disposition;
318
318
  }
319
319
  export async function purgeOrganizationReadModelProjection(execSql, organizationId, options = {}) {
@@ -1 +1 @@
1
- {"version":3,"file":"organizationReadModelPersistence.js","sourceRoot":"","sources":["organizationReadModelPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ListOrganizationGroupsResponse,\n OrganizationGroupCurrentStateResponse,\n OrganizationReadModelDirectoryResponse,\n OrganizationReadModelResponse,\n} from \"@tearleads/validators/response\";\nimport { eq } from \"drizzle-orm\";\nimport { invalidatePrincipalRecovery } from \"../../principals/principalRecoveryEpoch\";\nimport { organizationDataUsageTables } from \"../../sqlite/organizationDataUsageSchema\";\nimport {\n organizationReadModelDirectoryUsers,\n organizationReadModelGroups,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { clearOrganizationPresentationDenialInTransaction } from \"./organizationPresentationDenialPersistence\";\nimport { applyOrganizationReadModelGrantsLane } from \"./organizationReadModelGrantPersistence\";\nimport {\n applyGroupMembershipsLane,\n assertStoredGroupMembershipBindings,\n OrganizationReadModelBindingError,\n} from \"./organizationReadModelMembershipPersistence\";\nimport {\n loadOrganizationReadModelProjectionInTransaction,\n type OrganizationReadModelProjection,\n} from \"./organizationReadModelProjection\";\nimport {\n loadWithOrganizationReadModelIntegrityPurge,\n pruneInactiveOrganizationRequestersInTransaction,\n purgeOrganizationReadModelProjectionInTransaction,\n} from \"./organizationReadModelPurge\";\nimport { assertOrganizationReadModelResponse } from \"./organizationReadModelResponseAssertions\";\n\nexport type { OrganizationReadModelProjection } from \"./organizationReadModelProjection\";\n\nconst DIRECTORY_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelDirectoryUsers,\n);\nconst GROUP_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelGroups,\n);\n\ntype ApplyOrganizationReadModelResponseResult =\n | \"already-applied\"\n | \"applied\"\n | \"stale\";\n\ninterface CurrentOrganizationReadModelState {\n readonly cursor: string;\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string;\n readonly organizationHead: OrganizationGroupCurrentStateResponse;\n readonly profileDocumentId: string | null;\n}\n\nasync function replaceDirectoryLane(input: {\n readonly directory: OrganizationReadModelDirectoryResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelDirectoryUsers)\n .where(\n eq(\n organizationReadModelDirectoryUsers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n\n const rows = input.directory.users.map((user, sortOrder) => ({\n organizationId: input.organizationId,\n userId: user.userId,\n sortOrder,\n signingKeyFingerprint: user.signingKeyFingerprint,\n signingPublicKey: user.signingPublicKey,\n encapsulationPublicKey: user.encapsulationPublicKey,\n encapsulationKeyFingerprint: user.encapsulationKeyFingerprint,\n isPersonalOrganizationOwner: user.isPersonalOrganizationOwner,\n createdAt: user.createdAt,\n status: user.status,\n profileDocumentId: user.profileDocumentId,\n joinedAt: user.joinedAt,\n updatedAt: user.updatedAt,\n disabledAt: user.disabledAt,\n disabledByUserId: user.disabledByUserId,\n }));\n for (\n let index = 0;\n index < rows.length;\n index += DIRECTORY_INSERT_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelDirectoryUsers)\n .values(rows.slice(index, index + DIRECTORY_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function upsertRequester(input: {\n readonly currentUserId: string;\n readonly isOrgAdmin: boolean;\n readonly now: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const requesterRow = {\n organizationId: input.organizationId,\n userId: input.currentUserId,\n isOrgAdmin: input.isOrgAdmin,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelRequesters)\n .values(requesterRow)\n .onConflictDoUpdate({\n target: [\n organizationReadModelRequesters.organizationId,\n organizationReadModelRequesters.userId,\n ],\n set: {\n isOrgAdmin: requesterRow.isOrgAdmin,\n updatedAt: requesterRow.updatedAt,\n },\n })\n .run();\n // Storing an authoritative response proves access: retire the durable\n // denial marker for this scope in the same transaction.\n await clearOrganizationPresentationDenialInTransaction({\n organizationId: input.organizationId,\n projection: \"readModel\",\n requesterUserId: input.currentUserId,\n tx: input.tx,\n });\n}\n\nasync function replaceGroupsLane(input: {\n readonly groups: ListOrganizationGroupsResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n // Hydrated names and delivered membership heads outlive a catalog\n // replacement; the groupMemberships lane rebinds any head that changed.\n const previousGroups = await input.tx\n .select({\n groupId: organizationReadModelGroups.groupId,\n membersStateHash: organizationReadModelGroups.membersStateHash,\n name: organizationReadModelGroups.name,\n })\n .from(organizationReadModelGroups)\n .where(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n );\n const previousById = new Map(\n previousGroups.map((group) => [group.groupId, group]),\n );\n await input.tx\n .delete(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n\n const rows = input.groups.groups.map((group, sortOrder) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n sortOrder,\n name: previousById.get(group.groupId)?.name ?? \"\",\n createdAt: group.createdAt,\n isBuiltin: group.isBuiltin,\n stateHash: group.currentState?.stateHash ?? null,\n stateVersion: group.currentState?.version ?? null,\n keyEpoch: group.currentState?.keyEpoch ?? null,\n keyFingerprint: group.currentState?.keyFingerprint ?? null,\n memberCount: group.currentState?.memberCount ?? null,\n membersStateHash: previousById.get(group.groupId)?.membersStateHash ?? null,\n }));\n for (let index = 0; index < rows.length; index += GROUP_INSERT_BATCH_SIZE) {\n await input.tx\n .insert(organizationReadModelGroups)\n .values(rows.slice(index, index + GROUP_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function loadCurrentState(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<CurrentOrganizationReadModelState | null> {\n const [current] = await tx\n .select({\n cursor: organizationReadModelState.cursor,\n keyEpoch: organizationReadModelState.organizationKeyEpoch,\n keyFingerprint: organizationReadModelState.organizationKeyFingerprint,\n memberCount: organizationReadModelState.organizationMemberCount,\n memberGroupId: organizationReadModelState.memberGroupId,\n memberGroupStateHash: organizationReadModelState.memberGroupStateHash,\n profileDocumentId: organizationReadModelState.profileDocumentId,\n stateHash: organizationReadModelState.organizationStateHash,\n version: organizationReadModelState.organizationStateVersion,\n })\n .from(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, organizationId))\n .limit(1);\n if (!current) {\n return null;\n }\n const { keyEpoch, keyFingerprint, memberCount, stateHash, version, ...rest } =\n current;\n return {\n ...rest,\n organizationHead: {\n keyEpoch,\n keyFingerprint,\n memberCount,\n stateHash,\n version,\n },\n };\n}\n\nfunction resolveApplyDisposition(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): ApplyOrganizationReadModelResponseResult | null {\n const currentCursor = input.current?.cursor ?? null;\n if (currentCursor !== input.requestedCursor) {\n return currentCursor === input.response.nextCursor\n ? \"already-applied\"\n : \"stale\";\n }\n if (!input.current && input.response.mode !== \"snapshot\") {\n throw new Error(\"Organization read-model delta requires a local snapshot\");\n }\n return null;\n}\n\ninterface ResponseLanesInput {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly memberGroupId: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}\n\n/** Apply every carried lane and return the reserved Members head. */\nasync function replaceResponseLanes(\n input: ResponseLanesInput,\n): Promise<string | null> {\n const { organizationId } = input.response;\n const directory = input.response.lanes.directory;\n if (directory) {\n await replaceDirectoryLane({ directory, organizationId, tx: input.tx });\n }\n const groups = input.response.lanes.groups;\n if (groups) {\n await replaceGroupsLane({ groups, organizationId, tx: input.tx });\n }\n const groupMemberships = input.response.lanes.groupMemberships;\n const memberGroupStateHash = groupMemberships\n ? await applyGroupMembershipsLane({\n currentMemberGroupStateHash:\n input.current?.memberGroupStateHash ?? null,\n lane: groupMemberships,\n memberGroupId: input.memberGroupId,\n organizationId,\n replaceAll: input.response.mode === \"snapshot\",\n tx: input.tx,\n })\n : (input.current?.memberGroupStateHash ?? null);\n const grants = input.response.lanes.grants;\n if (grants) {\n await applyOrganizationReadModelGrantsLane({\n lane: grants,\n organizationId,\n tx: input.tx,\n });\n }\n return memberGroupStateHash;\n}\n\nasync function applyResponseLanes(\n input: ResponseLanesInput & { readonly currentUserId: string },\n): Promise<string | null> {\n const memberGroupStateHash = await replaceResponseLanes(input);\n // The directory lane is the authoritative roster, already persisted above:\n // requester rows for users it no longer lists as active fall with it.\n if (input.response.lanes.directory) {\n await pruneInactiveOrganizationRequestersInTransaction({\n organizationId: input.response.organizationId,\n retainUserId: input.currentUserId,\n tx: input.tx,\n });\n }\n if (input.response.lanes.groups || input.response.lanes.groupMemberships) {\n await assertStoredGroupMembershipBindings({\n memberGroupId: input.memberGroupId,\n memberGroupStateHash,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n return memberGroupStateHash;\n}\n\n/**\n * Write the cursor with the organization policy head and the reserved Members\n * head in one row, so a failure anywhere in the page rolls them back together.\n */\nasync function upsertOrganizationReadModelState(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string | null;\n readonly now: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const organizationHead =\n input.response.lanes.organizationPolicy?.currentState ??\n input.current?.organizationHead;\n if (!organizationHead || input.memberGroupStateHash === null) {\n throw new OrganizationReadModelBindingError(\n \"Organization read-model policy or Members head is missing\",\n );\n }\n const directoryLane = input.response.lanes.directory;\n const values = {\n cursor: input.response.nextCursor,\n profileDocumentId:\n directoryLane !== undefined\n ? directoryLane.profileDocumentId\n : (input.current?.profileDocumentId ?? null),\n memberGroupId: input.memberGroupId,\n memberGroupStateHash: input.memberGroupStateHash,\n organizationStateHash: organizationHead.stateHash,\n organizationStateVersion: organizationHead.version,\n organizationKeyEpoch: organizationHead.keyEpoch,\n organizationKeyFingerprint: organizationHead.keyFingerprint,\n organizationMemberCount: organizationHead.memberCount,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelState)\n .values({ ...values, organizationId: input.response.organizationId })\n .onConflictDoUpdate({\n target: organizationReadModelState.organizationId,\n set: values,\n })\n .run();\n}\n\nfunction requireMemberGroupId(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly response: OrganizationReadModelResponse;\n}): string {\n const memberGroupId =\n input.response.lanes.groups?.memberGroupId ?? input.current?.memberGroupId;\n if (!memberGroupId) {\n throw new Error(\"Organization read-model member group is missing\");\n }\n return memberGroupId;\n}\n\nexport async function loadOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n currentUserId: string,\n): Promise<OrganizationReadModelProjection | null> {\n return loadWithOrganizationReadModelIntegrityPurge({\n execSql,\n organizationId,\n load: (tx) =>\n loadOrganizationReadModelProjectionInTransaction({\n currentUserId,\n organizationId,\n tx,\n }),\n });\n}\n\nexport async function applyOrganizationReadModelResponse(input: {\n readonly currentUserId: string;\n readonly execSql: ExecSql;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): Promise<ApplyOrganizationReadModelResponseResult> {\n assertOrganizationReadModelResponse({\n currentUserId: input.currentUserId,\n response: input.response,\n });\n // The requester prune below also retires pruned users' usage projections.\n await ensureSqlTables(input.execSql, [\n ...organizationReadModelTables,\n ...organizationDataUsageTables,\n ]);\n\n const disposition = await getClientSQLitePersistenceRuntime(\n input.execSql,\n ).transaction(\n async (tx): Promise<ApplyOrganizationReadModelResponseResult> => {\n const current = await loadCurrentState(tx, input.response.organizationId);\n const disposition = resolveApplyDisposition({\n current,\n requestedCursor: input.requestedCursor,\n response: input.response,\n });\n if (disposition === \"stale\") {\n return disposition;\n }\n const now = new Date().toISOString();\n await upsertRequester({\n currentUserId: input.currentUserId,\n isOrgAdmin: input.response.currentUser.isOrgAdmin,\n now,\n organizationId: input.response.organizationId,\n tx,\n });\n if (disposition === \"already-applied\") {\n return disposition;\n }\n const memberGroupId = requireMemberGroupId({\n current,\n response: input.response,\n });\n const memberGroupStateHash = await applyResponseLanes({\n current,\n currentUserId: input.currentUserId,\n memberGroupId,\n response: input.response,\n tx,\n });\n await upsertOrganizationReadModelState({\n current,\n memberGroupId,\n memberGroupStateHash,\n now,\n response: input.response,\n tx,\n });\n\n return \"applied\";\n },\n );\n // An applied change follows an organization hint or mutation; in-flight\n // operations recover the directory again instead of reusing their memo.\n if (disposition === \"applied\")\n invalidatePrincipalRecovery(input.execSql, input.response.organizationId);\n return disposition;\n}\n\nexport async function purgeOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n options: { readonly onlyRequesterUserId?: string } = {},\n): Promise<void> {\n await ensureSqlTables(execSql, organizationReadModelTables);\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await purgeOrganizationReadModelProjectionInTransaction({\n onlyRequesterUserId: options.onlyRequesterUserId,\n organizationId,\n tx,\n });\n });\n}\n"]}
1
+ {"version":3,"file":"organizationReadModelPersistence.js","sourceRoot":"","sources":["organizationReadModelPersistence.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n ListOrganizationGroupsResponse,\n OrganizationGroupCurrentStateResponse,\n OrganizationReadModelDirectoryResponse,\n OrganizationReadModelResponse,\n} from \"@tearleads/validators/response\";\nimport { eq } from \"drizzle-orm\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../principals/principalPolicyCoordinator\";\nimport { organizationDataUsageTables } from \"../../sqlite/organizationDataUsageSchema\";\nimport {\n organizationReadModelDirectoryUsers,\n organizationReadModelGroups,\n organizationReadModelRequesters,\n organizationReadModelState,\n} from \"../../sqlite/organizationReadModelSchema\";\nimport { organizationReadModelTables } from \"../../sqlite/schema\";\nimport { sqliteInsertBatchSize } from \"../../sqlite/sqliteBindBudget\";\nimport {\n type ClientSQLiteTransactionScope,\n getClientSQLitePersistenceRuntime,\n} from \"../../sqlite/sqlitePersistenceRuntime\";\nimport { type ExecSql, ensureSqlTables } from \"../../sqlite/sqlSchema\";\nimport { clearOrganizationPresentationDenialInTransaction } from \"./organizationPresentationDenialPersistence\";\nimport { applyOrganizationReadModelGrantsLane } from \"./organizationReadModelGrantPersistence\";\nimport {\n applyGroupMembershipsLane,\n assertStoredGroupMembershipBindings,\n OrganizationReadModelBindingError,\n} from \"./organizationReadModelMembershipPersistence\";\nimport {\n loadOrganizationReadModelProjectionInTransaction,\n type OrganizationReadModelProjection,\n} from \"./organizationReadModelProjection\";\nimport {\n loadWithOrganizationReadModelIntegrityPurge,\n pruneInactiveOrganizationRequestersInTransaction,\n purgeOrganizationReadModelProjectionInTransaction,\n} from \"./organizationReadModelPurge\";\nimport { assertOrganizationReadModelResponse } from \"./organizationReadModelResponseAssertions\";\n\nexport type { OrganizationReadModelProjection } from \"./organizationReadModelProjection\";\n\nconst DIRECTORY_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelDirectoryUsers,\n);\nconst GROUP_INSERT_BATCH_SIZE = sqliteInsertBatchSize(\n organizationReadModelGroups,\n);\n\ntype ApplyOrganizationReadModelResponseResult =\n | \"already-applied\"\n | \"applied\"\n | \"stale\";\n\ninterface CurrentOrganizationReadModelState {\n readonly cursor: string;\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string;\n readonly organizationHead: OrganizationGroupCurrentStateResponse;\n readonly profileDocumentId: string | null;\n}\n\nasync function replaceDirectoryLane(input: {\n readonly directory: OrganizationReadModelDirectoryResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n await input.tx\n .delete(organizationReadModelDirectoryUsers)\n .where(\n eq(\n organizationReadModelDirectoryUsers.organizationId,\n input.organizationId,\n ),\n )\n .run();\n\n const rows = input.directory.users.map((user, sortOrder) => ({\n organizationId: input.organizationId,\n userId: user.userId,\n sortOrder,\n signingKeyFingerprint: user.signingKeyFingerprint,\n signingPublicKey: user.signingPublicKey,\n encapsulationPublicKey: user.encapsulationPublicKey,\n encapsulationKeyFingerprint: user.encapsulationKeyFingerprint,\n isPersonalOrganizationOwner: user.isPersonalOrganizationOwner,\n createdAt: user.createdAt,\n status: user.status,\n profileDocumentId: user.profileDocumentId,\n joinedAt: user.joinedAt,\n updatedAt: user.updatedAt,\n disabledAt: user.disabledAt,\n disabledByUserId: user.disabledByUserId,\n }));\n for (\n let index = 0;\n index < rows.length;\n index += DIRECTORY_INSERT_BATCH_SIZE\n ) {\n await input.tx\n .insert(organizationReadModelDirectoryUsers)\n .values(rows.slice(index, index + DIRECTORY_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function upsertRequester(input: {\n readonly currentUserId: string;\n readonly isOrgAdmin: boolean;\n readonly now: string;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const requesterRow = {\n organizationId: input.organizationId,\n userId: input.currentUserId,\n isOrgAdmin: input.isOrgAdmin,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelRequesters)\n .values(requesterRow)\n .onConflictDoUpdate({\n target: [\n organizationReadModelRequesters.organizationId,\n organizationReadModelRequesters.userId,\n ],\n set: {\n isOrgAdmin: requesterRow.isOrgAdmin,\n updatedAt: requesterRow.updatedAt,\n },\n })\n .run();\n // Storing an authoritative response proves access: retire the durable\n // denial marker for this scope in the same transaction.\n await clearOrganizationPresentationDenialInTransaction({\n organizationId: input.organizationId,\n projection: \"readModel\",\n requesterUserId: input.currentUserId,\n tx: input.tx,\n });\n}\n\nasync function replaceGroupsLane(input: {\n readonly groups: ListOrganizationGroupsResponse;\n readonly organizationId: string;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n // Hydrated names and delivered membership heads outlive a catalog\n // replacement; the groupMemberships lane rebinds any head that changed.\n const previousGroups = await input.tx\n .select({\n groupId: organizationReadModelGroups.groupId,\n membersStateHash: organizationReadModelGroups.membersStateHash,\n name: organizationReadModelGroups.name,\n })\n .from(organizationReadModelGroups)\n .where(\n eq(organizationReadModelGroups.organizationId, input.organizationId),\n );\n const previousById = new Map(\n previousGroups.map((group) => [group.groupId, group]),\n );\n await input.tx\n .delete(organizationReadModelGroups)\n .where(eq(organizationReadModelGroups.organizationId, input.organizationId))\n .run();\n\n const rows = input.groups.groups.map((group, sortOrder) => ({\n organizationId: input.organizationId,\n groupId: group.groupId,\n sortOrder,\n name: previousById.get(group.groupId)?.name ?? \"\",\n createdAt: group.createdAt,\n isBuiltin: group.isBuiltin,\n stateHash: group.currentState?.stateHash ?? null,\n stateVersion: group.currentState?.version ?? null,\n keyEpoch: group.currentState?.keyEpoch ?? null,\n keyFingerprint: group.currentState?.keyFingerprint ?? null,\n memberCount: group.currentState?.memberCount ?? null,\n membersStateHash: previousById.get(group.groupId)?.membersStateHash ?? null,\n }));\n for (let index = 0; index < rows.length; index += GROUP_INSERT_BATCH_SIZE) {\n await input.tx\n .insert(organizationReadModelGroups)\n .values(rows.slice(index, index + GROUP_INSERT_BATCH_SIZE))\n .run();\n }\n}\n\nasync function loadCurrentState(\n tx: ClientSQLiteTransactionScope,\n organizationId: string,\n): Promise<CurrentOrganizationReadModelState | null> {\n const [current] = await tx\n .select({\n cursor: organizationReadModelState.cursor,\n keyEpoch: organizationReadModelState.organizationKeyEpoch,\n keyFingerprint: organizationReadModelState.organizationKeyFingerprint,\n memberCount: organizationReadModelState.organizationMemberCount,\n memberGroupId: organizationReadModelState.memberGroupId,\n memberGroupStateHash: organizationReadModelState.memberGroupStateHash,\n profileDocumentId: organizationReadModelState.profileDocumentId,\n stateHash: organizationReadModelState.organizationStateHash,\n version: organizationReadModelState.organizationStateVersion,\n })\n .from(organizationReadModelState)\n .where(eq(organizationReadModelState.organizationId, organizationId))\n .limit(1);\n if (!current) {\n return null;\n }\n const { keyEpoch, keyFingerprint, memberCount, stateHash, version, ...rest } =\n current;\n return {\n ...rest,\n organizationHead: {\n keyEpoch,\n keyFingerprint,\n memberCount,\n stateHash,\n version,\n },\n };\n}\n\nfunction resolveApplyDisposition(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): ApplyOrganizationReadModelResponseResult | null {\n const currentCursor = input.current?.cursor ?? null;\n if (currentCursor !== input.requestedCursor) {\n return currentCursor === input.response.nextCursor\n ? \"already-applied\"\n : \"stale\";\n }\n if (!input.current && input.response.mode !== \"snapshot\") {\n throw new Error(\"Organization read-model delta requires a local snapshot\");\n }\n return null;\n}\n\ninterface ResponseLanesInput {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly memberGroupId: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}\n\n/** Apply every carried lane and return the reserved Members head. */\nasync function replaceResponseLanes(\n input: ResponseLanesInput,\n): Promise<string | null> {\n const { organizationId } = input.response;\n const directory = input.response.lanes.directory;\n if (directory) {\n await replaceDirectoryLane({ directory, organizationId, tx: input.tx });\n }\n const groups = input.response.lanes.groups;\n if (groups) {\n await replaceGroupsLane({ groups, organizationId, tx: input.tx });\n }\n const groupMemberships = input.response.lanes.groupMemberships;\n const memberGroupStateHash = groupMemberships\n ? await applyGroupMembershipsLane({\n currentMemberGroupStateHash:\n input.current?.memberGroupStateHash ?? null,\n lane: groupMemberships,\n memberGroupId: input.memberGroupId,\n organizationId,\n replaceAll: input.response.mode === \"snapshot\",\n tx: input.tx,\n })\n : (input.current?.memberGroupStateHash ?? null);\n const grants = input.response.lanes.grants;\n if (grants) {\n await applyOrganizationReadModelGrantsLane({\n lane: grants,\n organizationId,\n tx: input.tx,\n });\n }\n return memberGroupStateHash;\n}\n\nasync function applyResponseLanes(\n input: ResponseLanesInput & { readonly currentUserId: string },\n): Promise<string | null> {\n const memberGroupStateHash = await replaceResponseLanes(input);\n // The directory lane is the authoritative roster, already persisted above:\n // requester rows for users it no longer lists as active fall with it.\n if (input.response.lanes.directory) {\n await pruneInactiveOrganizationRequestersInTransaction({\n organizationId: input.response.organizationId,\n retainUserId: input.currentUserId,\n tx: input.tx,\n });\n }\n if (input.response.lanes.groups || input.response.lanes.groupMemberships) {\n await assertStoredGroupMembershipBindings({\n memberGroupId: input.memberGroupId,\n memberGroupStateHash,\n organizationId: input.response.organizationId,\n tx: input.tx,\n });\n }\n return memberGroupStateHash;\n}\n\n/**\n * Write the cursor with the organization policy head and the reserved Members\n * head in one row, so a failure anywhere in the page rolls them back together.\n */\nasync function upsertOrganizationReadModelState(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly memberGroupId: string;\n readonly memberGroupStateHash: string | null;\n readonly now: string;\n readonly response: OrganizationReadModelResponse;\n readonly tx: ClientSQLiteTransactionScope;\n}): Promise<void> {\n const organizationHead =\n input.response.lanes.organizationPolicy?.currentState ??\n input.current?.organizationHead;\n if (!organizationHead || input.memberGroupStateHash === null) {\n throw new OrganizationReadModelBindingError(\n \"Organization read-model policy or Members head is missing\",\n );\n }\n const directoryLane = input.response.lanes.directory;\n const values = {\n cursor: input.response.nextCursor,\n profileDocumentId:\n directoryLane !== undefined\n ? directoryLane.profileDocumentId\n : (input.current?.profileDocumentId ?? null),\n memberGroupId: input.memberGroupId,\n memberGroupStateHash: input.memberGroupStateHash,\n organizationStateHash: organizationHead.stateHash,\n organizationStateVersion: organizationHead.version,\n organizationKeyEpoch: organizationHead.keyEpoch,\n organizationKeyFingerprint: organizationHead.keyFingerprint,\n organizationMemberCount: organizationHead.memberCount,\n updatedAt: input.now,\n };\n await input.tx\n .insert(organizationReadModelState)\n .values({ ...values, organizationId: input.response.organizationId })\n .onConflictDoUpdate({\n target: organizationReadModelState.organizationId,\n set: values,\n })\n .run();\n}\n\nfunction requireMemberGroupId(input: {\n readonly current: CurrentOrganizationReadModelState | null;\n readonly response: OrganizationReadModelResponse;\n}): string {\n const memberGroupId =\n input.response.lanes.groups?.memberGroupId ?? input.current?.memberGroupId;\n if (!memberGroupId) {\n throw new Error(\"Organization read-model member group is missing\");\n }\n return memberGroupId;\n}\n\nexport async function loadOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n currentUserId: string,\n): Promise<OrganizationReadModelProjection | null> {\n return loadWithOrganizationReadModelIntegrityPurge({\n execSql,\n organizationId,\n load: (tx) =>\n loadOrganizationReadModelProjectionInTransaction({\n currentUserId,\n organizationId,\n tx,\n }),\n });\n}\n\nexport async function applyOrganizationReadModelResponse(input: {\n readonly currentUserId: string;\n readonly execSql: ExecSql;\n readonly requestedCursor: string | null;\n readonly response: OrganizationReadModelResponse;\n}): Promise<ApplyOrganizationReadModelResponseResult> {\n assertOrganizationReadModelResponse({\n currentUserId: input.currentUserId,\n response: input.response,\n });\n // The requester prune below also retires pruned users' usage projections.\n await ensureSqlTables(input.execSql, [\n ...organizationReadModelTables,\n ...organizationDataUsageTables,\n ]);\n\n const disposition = await getClientSQLitePersistenceRuntime(\n input.execSql,\n ).transaction(\n async (tx): Promise<ApplyOrganizationReadModelResponseResult> => {\n const current = await loadCurrentState(tx, input.response.organizationId);\n const disposition = resolveApplyDisposition({\n current,\n requestedCursor: input.requestedCursor,\n response: input.response,\n });\n if (disposition === \"stale\") {\n return disposition;\n }\n const now = new Date().toISOString();\n await upsertRequester({\n currentUserId: input.currentUserId,\n isOrgAdmin: input.response.currentUser.isOrgAdmin,\n now,\n organizationId: input.response.organizationId,\n tx,\n });\n if (disposition === \"already-applied\") {\n return disposition;\n }\n const memberGroupId = requireMemberGroupId({\n current,\n response: input.response,\n });\n const memberGroupStateHash = await applyResponseLanes({\n current,\n currentUserId: input.currentUserId,\n memberGroupId,\n response: input.response,\n tx,\n });\n await upsertOrganizationReadModelState({\n current,\n memberGroupId,\n memberGroupStateHash,\n now,\n response: input.response,\n tx,\n });\n\n return \"applied\";\n },\n );\n // An applied change follows an organization hint or mutation; in-flight\n // operations recover the directory again instead of reusing their memo.\n if (disposition === \"applied\")\n getOrCreatePrincipalPolicyCoordinator(input.execSql).invalidateRecovery(\n input.response.organizationId,\n );\n return disposition;\n}\n\nexport async function purgeOrganizationReadModelProjection(\n execSql: ExecSql,\n organizationId: string,\n options: { readonly onlyRequesterUserId?: string } = {},\n): Promise<void> {\n await ensureSqlTables(execSql, organizationReadModelTables);\n await getClientSQLitePersistenceRuntime(execSql).transaction(async (tx) => {\n await purgeOrganizationReadModelProjectionInTransaction({\n onlyRequesterUserId: options.onlyRequesterUserId,\n organizationId,\n tx,\n });\n });\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import { resolveCanonicalExecSql } from "../sqlite/sqlSchema.js";
2
+ import { principalMutationJournalScopeId, } from "./principalMutationJournal.js";
3
+ /** Runs work for a key after the work already queued for it; idle keys hold nothing. */
4
+ function createKeyedQueue() {
5
+ const tails = new Map();
6
+ return (key, work) => {
7
+ const operation = (tails.get(key) ?? Promise.resolve()).then(work);
8
+ const settled = operation.then(() => undefined, () => undefined);
9
+ tails.set(key, settled);
10
+ void settled.then(() => {
11
+ if (tails.get(key) === settled)
12
+ tails.delete(key);
13
+ });
14
+ return operation;
15
+ };
16
+ }
17
+ function createPrincipalPolicyCoordinator() {
18
+ const recoveries = createKeyedQueue();
19
+ const journalScopes = createKeyedQueue();
20
+ const epochs = new Map();
21
+ return {
22
+ queueRecovery: recoveries,
23
+ recoveryEpoch: (organizationId) => epochs.get(organizationId) ?? 0,
24
+ invalidateRecovery: (organizationId) => {
25
+ epochs.set(organizationId, (epochs.get(organizationId) ?? 0) + 1);
26
+ },
27
+ runJournalOperation: async (scope, run) => journalScopes(await principalMutationJournalScopeId(scope), run),
28
+ };
29
+ }
30
+ const coordinators = new WeakMap();
31
+ export function getOrCreatePrincipalPolicyCoordinator(execSql) {
32
+ const database = resolveCanonicalExecSql(execSql);
33
+ let coordinator = coordinators.get(database);
34
+ if (!coordinator) {
35
+ coordinator = createPrincipalPolicyCoordinator();
36
+ coordinators.set(database, coordinator);
37
+ }
38
+ return coordinator;
39
+ }
40
+ //# sourceMappingURL=principalPolicyCoordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principalPolicyCoordinator.js","sourceRoot":"","sources":["principalPolicyCoordinator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAEL,+BAA+B,GAChC,MAAM,4BAA4B,CAAC;AA+BpC,wFAAwF;AACxF,SAAS,gBAAgB;IACvB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC/C,OAAO,CAAI,GAAW,EAAE,IAAsB,EAAc,EAAE;QAC5D,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnE,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAC5B,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QACF,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QACxB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;YACrB,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,OAAO;gBAAE,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QACH,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,gCAAgC;IACvC,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAC;IACtC,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,OAAO;QACL,aAAa,EAAE,UAAU;QACzB,aAAa,EAAE,CAAC,cAAc,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC;QAClE,kBAAkB,EAAE,CAAC,cAAc,EAAE,EAAE;YACrC,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACpE,CAAC;QACD,mBAAmB,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CACxC,aAAa,CAAC,MAAM,+BAA+B,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC;KACnE,CAAC;AACJ,CAAC;AAED,MAAM,YAAY,GAAG,IAAI,OAAO,EAAuC,CAAC;AAExE,MAAM,UAAU,qCAAqC,CACnD,OAAgB;IAEhB,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,WAAW,GAAG,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,WAAW,GAAG,gCAAgC,EAAE,CAAC;QACjD,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC","sourcesContent":["import { type ExecSql, resolveCanonicalExecSql } from \"../sqlite/sqlSchema\";\nimport {\n type PrincipalMutationJournalScope,\n principalMutationJournalScopeId,\n} from \"./principalMutationJournal\";\n\n/**\n * The principal-policy work one database coordinates in this process. Every\n * executor for a connection, including a transaction's locked one, resolves to\n * the same coordinator; different databases never share one.\n */\nexport interface PrincipalPolicyCoordinator {\n /**\n * Runs recovery for an organization after the recovery already queued for\n * it. Directory and Admins stages are shared by every reference in an\n * organization, so warmers and registration take turns. Queued work must not\n * queue again for the same organization.\n */\n queueRecovery<T>(organizationId: string, work: () => Promise<T>): Promise<T>;\n /**\n * The organization hints applied in this process. Operation-scoped recovery\n * records it before producing a result and recovers again once it changes.\n * Hints are lossy, so this only shortens reuse; each operation still\n * discovers the current head.\n */\n recoveryEpoch(organizationId: string): number;\n /** Evicts recovered directory and Admins views for this organization. */\n invalidateRecovery(organizationId: string): void;\n /** An owned dispatch finishes before another caller can recover or abandon it. */\n runJournalOperation<T>(\n scope: PrincipalMutationJournalScope,\n run: () => Promise<T>,\n ): Promise<T>;\n}\n\n/** Runs work for a key after the work already queued for it; idle keys hold nothing. */\nfunction createKeyedQueue() {\n const tails = new Map<string, Promise<void>>();\n return <T>(key: string, work: () => Promise<T>): Promise<T> => {\n const operation = (tails.get(key) ?? Promise.resolve()).then(work);\n const settled = operation.then(\n () => undefined,\n () => undefined,\n );\n tails.set(key, settled);\n void settled.then(() => {\n if (tails.get(key) === settled) tails.delete(key);\n });\n return operation;\n };\n}\n\nfunction createPrincipalPolicyCoordinator(): PrincipalPolicyCoordinator {\n const recoveries = createKeyedQueue();\n const journalScopes = createKeyedQueue();\n const epochs = new Map<string, number>();\n return {\n queueRecovery: recoveries,\n recoveryEpoch: (organizationId) => epochs.get(organizationId) ?? 0,\n invalidateRecovery: (organizationId) => {\n epochs.set(organizationId, (epochs.get(organizationId) ?? 0) + 1);\n },\n runJournalOperation: async (scope, run) =>\n journalScopes(await principalMutationJournalScopeId(scope), run),\n };\n}\n\nconst coordinators = new WeakMap<ExecSql, PrincipalPolicyCoordinator>();\n\nexport function getOrCreatePrincipalPolicyCoordinator(\n execSql: ExecSql,\n): PrincipalPolicyCoordinator {\n const database = resolveCanonicalExecSql(execSql);\n let coordinator = coordinators.get(database);\n if (!coordinator) {\n coordinator = createPrincipalPolicyCoordinator();\n coordinators.set(database, coordinator);\n }\n return coordinator;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tearleads/client-sdk",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "React-free Tearleads client runtime: local SQLite persistence, keys, sync, encrypted blobs, and domain workflows.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -4,7 +4,7 @@ import { assertProjectionVerificationCurrent } from "../../data/keyingProjection
4
4
  import { clearPrincipalMutationJournal, loadPrincipalMutationJournal, } from "../../data/persistence/principalMutationJournalPersistence.js";
5
5
  import { openPrincipalMutation, principalMutationJournalScopeId, } from "../../data/principals/principalMutationJournal.js";
6
6
  import { principalMutationJournalRecordId, UnreadablePrincipalMutationError, } from "../../data/principals/principalMutationJournalRecord.js";
7
- import { runPrincipalMutationJournalOperation } from "./principalMutationJournalLane.js";
7
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../data/principals/principalPolicyCoordinator.js";
8
8
  async function loadAuthenticatedMutation(input) {
9
9
  assertProjectionVerificationCurrent(input.stillCurrent);
10
10
  const row = await loadPrincipalMutationJournal(input.execSql, await principalMutationJournalScopeId(input.scope));
@@ -35,7 +35,7 @@ export async function discardUnreadableJournaledPrincipalMutation(input) {
35
35
  if (input.acknowledgeUnknownOutcome !== true)
36
36
  throw new Error("Discarding unreadable work requires acknowledging its unknown outcome");
37
37
  const expected = input.recordId;
38
- return runPrincipalMutationJournalOperation(input.execSql, input.scope, async () => {
38
+ return getOrCreatePrincipalPolicyCoordinator(input.execSql).runJournalOperation(input.scope, async () => {
39
39
  assertProjectionVerificationCurrent(input.stillCurrent);
40
40
  const row = await loadPrincipalMutationJournal(input.execSql, await principalMutationJournalScopeId(input.scope));
41
41
  assertProjectionVerificationCurrent(input.stillCurrent);
@@ -83,7 +83,7 @@ export async function abandonJournaledPrincipalMutation(input) {
83
83
  if (input.acknowledgeUnknownOutcome !== true)
84
84
  throw new Error("Abandoning authored work requires acknowledging its unknown outcome");
85
85
  const expected = canonicalKeyingJsonString(JSON.parse(JSON.stringify(input.mutation)), "inspected authored mutation");
86
- return runPrincipalMutationJournalOperation(input.execSql, input.scope, async () => {
86
+ return getOrCreatePrincipalPolicyCoordinator(input.execSql).runJournalOperation(input.scope, async () => {
87
87
  const saved = await loadAuthenticatedMutation(input);
88
88
  if (!saved)
89
89
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"principalMutationJournalManagement.js","sourceRoot":"","sources":["principalMutationJournalManagement.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { canonicalKeyingJsonString } from \"../../data/keyingCanonicalJson\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport {\n clearPrincipalMutationJournal,\n loadPrincipalMutationJournal,\n} from \"../../data/persistence/principalMutationJournalPersistence\";\nimport {\n type AuthoredPrincipalMutation,\n openPrincipalMutation,\n principalMutationJournalScopeId,\n} from \"../../data/principals/principalMutationJournal\";\nimport {\n principalMutationJournalRecordId,\n UnreadablePrincipalMutationError,\n} from \"../../data/principals/principalMutationJournalRecord\";\nimport { runPrincipalMutationJournalOperation } from \"./principalMutationJournalLane\";\nimport type { PrincipalMutationJournalContext } from \"./principalMutationJournalSession\";\n\nexport interface PrincipalMutationRecoveryApi {\n readonly readPendingPrincipalMutation: (\n organizationId: string,\n ) => Promise<AuthoredPrincipalMutation | null>;\n readonly recoverPendingPrincipalMutation: (\n organizationId: string,\n ) => Promise<void>;\n readonly abandonPendingPrincipalMutation: (\n organizationId: string,\n mutation: AuthoredPrincipalMutation,\n acknowledgeUnknownOutcome: true,\n ) => Promise<boolean>;\n readonly discardUnreadablePrincipalMutation: (\n organizationId: string,\n recordId: string,\n acknowledgeUnknownOutcome: true,\n ) => Promise<boolean>;\n}\n\ntype JournalIdentity = Pick<\n PrincipalMutationJournalContext,\n \"execSql\" | \"scope\" | \"signingKeyPair\" | \"stillCurrent\"\n>;\n\nasync function loadAuthenticatedMutation(input: JournalIdentity) {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await loadPrincipalMutationJournal(\n input.execSql,\n await principalMutationJournalScopeId(input.scope),\n );\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!row) return null;\n let mutation: AuthoredPrincipalMutation;\n try {\n mutation = await openPrincipalMutation({\n scope: input.scope,\n row,\n signingPublicKey: input.signingKeyPair.signingPublicKey,\n });\n } catch (error) {\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!(error instanceof KeyingVerificationError)) throw error;\n const recordId = await principalMutationJournalRecordId(row);\n assertProjectionVerificationCurrent(input.stillCurrent);\n throw new UnreadablePrincipalMutationError(\n recordId,\n error.code === \"invalid_shape\" ? \"format\" : \"authentication\",\n );\n }\n assertProjectionVerificationCurrent(input.stillCurrent);\n return { row, mutation };\n}\n\n/** Discard only the inspected unreadable bytes; never parse them for submission. */\nexport async function discardUnreadableJournaledPrincipalMutation(\n input: JournalIdentity & {\n readonly recordId: string;\n readonly acknowledgeUnknownOutcome: true;\n },\n): Promise<boolean> {\n if (input.acknowledgeUnknownOutcome !== true)\n throw new Error(\n \"Discarding unreadable work requires acknowledging its unknown outcome\",\n );\n const expected = input.recordId;\n return runPrincipalMutationJournalOperation(\n input.execSql,\n input.scope,\n async () => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await loadPrincipalMutationJournal(\n input.execSql,\n await principalMutationJournalScopeId(input.scope),\n );\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!row) return false;\n if ((await principalMutationJournalRecordId(row)) !== expected)\n throw new Error(\n \"The unreadable principal mutation changed after inspection\",\n );\n let unreadable = false;\n try {\n await openPrincipalMutation({\n scope: input.scope,\n row,\n signingPublicKey: input.signingKeyPair.signingPublicKey,\n });\n } catch (error) {\n if (!(error instanceof KeyingVerificationError)) throw error;\n unreadable = true;\n }\n if (!unreadable)\n throw new Error(\n \"Readable principal mutations require ordinary abandonment\",\n );\n const removed = await clearPrincipalMutationJournal({\n execSql: input.execSql,\n row,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!removed)\n throw new Error(\n \"The unreadable principal mutation changed during discard\",\n );\n return true;\n },\n );\n}\n\n/** Inspect authenticated authored work without retrying it or changing checkpoints. */\nexport async function readJournaledPrincipalMutation(\n input: JournalIdentity,\n): Promise<AuthoredPrincipalMutation | null> {\n // Inspection may observe an owned dispatch; all mutations still use the lane.\n return (await loadAuthenticatedMutation(input))?.mutation ?? null;\n}\n\n/**\n * An explicit host/user choice to stop retries, not a claim that the write failed.\n * The exact inspected request must still own the lane; ordinary recovery never\n * calls this. Future authoring must still verify the latest policy state.\n */\nexport async function abandonJournaledPrincipalMutation(\n input: JournalIdentity & {\n readonly mutation: AuthoredPrincipalMutation;\n readonly acknowledgeUnknownOutcome: true;\n },\n): Promise<boolean> {\n if (input.acknowledgeUnknownOutcome !== true)\n throw new Error(\n \"Abandoning authored work requires acknowledging its unknown outcome\",\n );\n const expected = canonicalKeyingJsonString(\n JSON.parse(JSON.stringify(input.mutation)),\n \"inspected authored mutation\",\n );\n return runPrincipalMutationJournalOperation(\n input.execSql,\n input.scope,\n async () => {\n const saved = await loadAuthenticatedMutation(input);\n if (!saved) return false;\n if (\n canonicalKeyingJsonString(saved.mutation, \"saved authored mutation\") !==\n expected\n )\n throw new Error(\n \"The pending principal mutation changed after inspection\",\n );\n const removed = await clearPrincipalMutationJournal({\n execSql: input.execSql,\n row: saved.row,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!removed)\n throw new Error(\n \"The pending principal mutation changed during abandonment\",\n );\n return true;\n },\n );\n}\n"]}
1
+ {"version":3,"file":"principalMutationJournalManagement.js","sourceRoot":"","sources":["principalMutationJournalManagement.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { canonicalKeyingJsonString } from \"../../data/keyingCanonicalJson\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport {\n clearPrincipalMutationJournal,\n loadPrincipalMutationJournal,\n} from \"../../data/persistence/principalMutationJournalPersistence\";\nimport {\n type AuthoredPrincipalMutation,\n openPrincipalMutation,\n principalMutationJournalScopeId,\n} from \"../../data/principals/principalMutationJournal\";\nimport {\n principalMutationJournalRecordId,\n UnreadablePrincipalMutationError,\n} from \"../../data/principals/principalMutationJournalRecord\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../data/principals/principalPolicyCoordinator\";\nimport type { PrincipalMutationJournalContext } from \"./principalMutationJournalSession\";\n\nexport interface PrincipalMutationRecoveryApi {\n readonly readPendingPrincipalMutation: (\n organizationId: string,\n ) => Promise<AuthoredPrincipalMutation | null>;\n readonly recoverPendingPrincipalMutation: (\n organizationId: string,\n ) => Promise<void>;\n readonly abandonPendingPrincipalMutation: (\n organizationId: string,\n mutation: AuthoredPrincipalMutation,\n acknowledgeUnknownOutcome: true,\n ) => Promise<boolean>;\n readonly discardUnreadablePrincipalMutation: (\n organizationId: string,\n recordId: string,\n acknowledgeUnknownOutcome: true,\n ) => Promise<boolean>;\n}\n\ntype JournalIdentity = Pick<\n PrincipalMutationJournalContext,\n \"execSql\" | \"scope\" | \"signingKeyPair\" | \"stillCurrent\"\n>;\n\nasync function loadAuthenticatedMutation(input: JournalIdentity) {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await loadPrincipalMutationJournal(\n input.execSql,\n await principalMutationJournalScopeId(input.scope),\n );\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!row) return null;\n let mutation: AuthoredPrincipalMutation;\n try {\n mutation = await openPrincipalMutation({\n scope: input.scope,\n row,\n signingPublicKey: input.signingKeyPair.signingPublicKey,\n });\n } catch (error) {\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!(error instanceof KeyingVerificationError)) throw error;\n const recordId = await principalMutationJournalRecordId(row);\n assertProjectionVerificationCurrent(input.stillCurrent);\n throw new UnreadablePrincipalMutationError(\n recordId,\n error.code === \"invalid_shape\" ? \"format\" : \"authentication\",\n );\n }\n assertProjectionVerificationCurrent(input.stillCurrent);\n return { row, mutation };\n}\n\n/** Discard only the inspected unreadable bytes; never parse them for submission. */\nexport async function discardUnreadableJournaledPrincipalMutation(\n input: JournalIdentity & {\n readonly recordId: string;\n readonly acknowledgeUnknownOutcome: true;\n },\n): Promise<boolean> {\n if (input.acknowledgeUnknownOutcome !== true)\n throw new Error(\n \"Discarding unreadable work requires acknowledging its unknown outcome\",\n );\n const expected = input.recordId;\n return getOrCreatePrincipalPolicyCoordinator(\n input.execSql,\n ).runJournalOperation(input.scope, async () => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await loadPrincipalMutationJournal(\n input.execSql,\n await principalMutationJournalScopeId(input.scope),\n );\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!row) return false;\n if ((await principalMutationJournalRecordId(row)) !== expected)\n throw new Error(\n \"The unreadable principal mutation changed after inspection\",\n );\n let unreadable = false;\n try {\n await openPrincipalMutation({\n scope: input.scope,\n row,\n signingPublicKey: input.signingKeyPair.signingPublicKey,\n });\n } catch (error) {\n if (!(error instanceof KeyingVerificationError)) throw error;\n unreadable = true;\n }\n if (!unreadable)\n throw new Error(\n \"Readable principal mutations require ordinary abandonment\",\n );\n const removed = await clearPrincipalMutationJournal({\n execSql: input.execSql,\n row,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!removed)\n throw new Error(\n \"The unreadable principal mutation changed during discard\",\n );\n return true;\n });\n}\n\n/** Inspect authenticated authored work without retrying it or changing checkpoints. */\nexport async function readJournaledPrincipalMutation(\n input: JournalIdentity,\n): Promise<AuthoredPrincipalMutation | null> {\n // Inspection may observe an owned dispatch; all mutations still use the lane.\n return (await loadAuthenticatedMutation(input))?.mutation ?? null;\n}\n\n/**\n * An explicit host/user choice to stop retries, not a claim that the write failed.\n * The exact inspected request must still own the lane; ordinary recovery never\n * calls this. Future authoring must still verify the latest policy state.\n */\nexport async function abandonJournaledPrincipalMutation(\n input: JournalIdentity & {\n readonly mutation: AuthoredPrincipalMutation;\n readonly acknowledgeUnknownOutcome: true;\n },\n): Promise<boolean> {\n if (input.acknowledgeUnknownOutcome !== true)\n throw new Error(\n \"Abandoning authored work requires acknowledging its unknown outcome\",\n );\n const expected = canonicalKeyingJsonString(\n JSON.parse(JSON.stringify(input.mutation)),\n \"inspected authored mutation\",\n );\n return getOrCreatePrincipalPolicyCoordinator(\n input.execSql,\n ).runJournalOperation(input.scope, async () => {\n const saved = await loadAuthenticatedMutation(input);\n if (!saved) return false;\n if (\n canonicalKeyingJsonString(saved.mutation, \"saved authored mutation\") !==\n expected\n )\n throw new Error(\n \"The pending principal mutation changed after inspection\",\n );\n const removed = await clearPrincipalMutationJournal({\n execSql: input.execSql,\n row: saved.row,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!removed)\n throw new Error(\n \"The pending principal mutation changed during abandonment\",\n );\n return true;\n });\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { assertProjectionVerificationCurrent } from "../../data/keyingProjectionVerification/types.js";
2
2
  import { claimPrincipalMutationJournal, clearPrincipalMutationJournal, loadPrincipalMutationJournal, } from "../../data/persistence/principalMutationJournalPersistence.js";
3
3
  import { openPrincipalMutation, principalMutationJournalScopeId, sealPrincipalMutation, } from "../../data/principals/principalMutationJournal.js";
4
- import { runPrincipalMutationJournalOperation } from "./principalMutationJournalLane.js";
4
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../data/principals/principalPolicyCoordinator.js";
5
5
  import { assertAuthoredPrincipalOperationReceipt, } from "./principalOperationReceipt.js";
6
6
  export class PrincipalMutationOutcomeUnknownError extends Error {
7
7
  constructor(options) {
@@ -57,7 +57,7 @@ async function submitSavedMutation(input, row, recovering) {
57
57
  }
58
58
  /** No HTTP begins until the exact authored request is durably claimed. */
59
59
  export async function submitJournaledPrincipalMutation(input) {
60
- return runPrincipalMutationJournalOperation(input.execSql, input.scope, async () => {
60
+ return getOrCreatePrincipalPolicyCoordinator(input.execSql).runJournalOperation(input.scope, async () => {
61
61
  assertProjectionVerificationCurrent(input.stillCurrent);
62
62
  const row = await sealPrincipalMutation(input);
63
63
  await claimPrincipalMutationJournal({
@@ -70,7 +70,7 @@ export async function submitJournaledPrincipalMutation(input) {
70
70
  }
71
71
  /** Retry only authenticated saved bytes, never an application mutation callback. */
72
72
  export async function recoverJournaledPrincipalMutation(input) {
73
- return runPrincipalMutationJournalOperation(input.execSql, input.scope, async () => {
73
+ return getOrCreatePrincipalPolicyCoordinator(input.execSql).runJournalOperation(input.scope, async () => {
74
74
  assertProjectionVerificationCurrent(input.stillCurrent);
75
75
  const row = await loadPrincipalMutationJournal(input.execSql, await principalMutationJournalScopeId(input.scope));
76
76
  assertProjectionVerificationCurrent(input.stillCurrent);
@@ -1 +1 @@
1
- {"version":3,"file":"principalMutationJournalSession.js","sourceRoot":"","sources":["principalMutationJournalSession.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RequestResult } from \"@tearleads/api-client\";\nimport type { SigningKeyPair } from \"@tearleads/crypto\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport {\n claimPrincipalMutationJournal,\n clearPrincipalMutationJournal,\n loadPrincipalMutationJournal,\n type PrincipalMutationJournalRow,\n} from \"../../data/persistence/principalMutationJournalPersistence\";\nimport {\n type AuthoredPrincipalMutation,\n openPrincipalMutation,\n type PrincipalMutationJournalScope,\n principalMutationJournalScopeId,\n sealPrincipalMutation,\n} from \"../../data/principals/principalMutationJournal\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport { runPrincipalMutationJournalOperation } from \"./principalMutationJournalLane\";\nimport {\n assertAuthoredPrincipalOperationReceipt,\n type PrincipalMutationResponse,\n} from \"./principalOperationReceipt\";\n\ntype MutationResult = RequestResult<PrincipalMutationResponse>;\n\nexport interface PrincipalMutationJournalContext {\n readonly execSql: ExecSql;\n readonly scope: PrincipalMutationJournalScope;\n readonly signingKeyPair: SigningKeyPair;\n readonly stillCurrent: () => boolean;\n readonly submit: (\n mutation: AuthoredPrincipalMutation,\n ) => Promise<MutationResult>;\n}\n\nexport class PrincipalMutationOutcomeUnknownError extends Error {\n constructor(options?: ErrorOptions) {\n super(\n \"A saved policy request may have committed; resolve its outcome or explicitly abandon its retry before another mutation\",\n options,\n );\n this.name = \"PrincipalMutationOutcomeUnknownError\";\n }\n}\n\nfunction knownInitialRefusal(result: MutationResult): boolean {\n if (result.ok) return false;\n // ApiClient separates pre-dispatch cancellation from an uncertain sent write.\n if (result.kind === \"cancelled\") return true;\n if (result.kind !== \"http\") return false;\n return (\n [400, 401, 402, 403, 409].includes(result.status ?? 0) ||\n (result.status === 503 &&\n result.code === \"principal_history_preparation_unavailable\")\n );\n}\n\nasync function submitSavedMutation(\n input: PrincipalMutationJournalContext,\n row: PrincipalMutationJournalRow,\n recovering: boolean,\n): Promise<{ mutation: AuthoredPrincipalMutation; result: MutationResult }> {\n const mutation = await openPrincipalMutation({\n scope: input.scope,\n row,\n signingPublicKey: input.signingKeyPair.signingPublicKey,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n // Keep the authenticated copy for acknowledgement checks even if a transport\n // mutates its argument while preparing or retrying the HTTP request.\n let result: MutationResult;\n try {\n result = await input.submit(structuredClone(mutation));\n } catch (cause) {\n assertProjectionVerificationCurrent(input.stillCurrent);\n throw new PrincipalMutationOutcomeUnknownError({ cause });\n }\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (result.ok) {\n await assertAuthoredPrincipalOperationReceipt(mutation, result.data);\n } else if (recovering || !knownInitialRefusal(result)) {\n // A refusal today (including loss of admin access) cannot establish whether\n // an earlier disconnected attempt committed. Preserve the original intent.\n throw new PrincipalMutationOutcomeUnknownError();\n }\n await clearPrincipalMutationJournal({\n execSql: input.execSql,\n row,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n return { mutation, result };\n}\n\n/** No HTTP begins until the exact authored request is durably claimed. */\nexport async function submitJournaledPrincipalMutation(\n input: PrincipalMutationJournalContext & {\n readonly mutation: AuthoredPrincipalMutation;\n },\n): Promise<MutationResult> {\n return runPrincipalMutationJournalOperation(\n input.execSql,\n input.scope,\n async () => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await sealPrincipalMutation(input);\n await claimPrincipalMutationJournal({\n execSql: input.execSql,\n row,\n stillCurrent: input.stillCurrent,\n });\n return (await submitSavedMutation(input, row, false)).result;\n },\n );\n}\n\n/** Retry only authenticated saved bytes, never an application mutation callback. */\nexport async function recoverJournaledPrincipalMutation(\n input: PrincipalMutationJournalContext,\n): Promise<{\n readonly mutation: AuthoredPrincipalMutation;\n readonly response: PrincipalMutationResponse;\n} | null> {\n return runPrincipalMutationJournalOperation(\n input.execSql,\n input.scope,\n async () => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await loadPrincipalMutationJournal(\n input.execSql,\n await principalMutationJournalScopeId(input.scope),\n );\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!row) return null;\n const { mutation, result } = await submitSavedMutation(input, row, true);\n if (!result.ok) throw new PrincipalMutationOutcomeUnknownError();\n return { mutation, response: result.data };\n },\n );\n}\n"]}
1
+ {"version":3,"file":"principalMutationJournalSession.js","sourceRoot":"","sources":["principalMutationJournalSession.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RequestResult } from \"@tearleads/api-client\";\nimport type { SigningKeyPair } from \"@tearleads/crypto\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport {\n claimPrincipalMutationJournal,\n clearPrincipalMutationJournal,\n loadPrincipalMutationJournal,\n type PrincipalMutationJournalRow,\n} from \"../../data/persistence/principalMutationJournalPersistence\";\nimport {\n type AuthoredPrincipalMutation,\n openPrincipalMutation,\n type PrincipalMutationJournalScope,\n principalMutationJournalScopeId,\n sealPrincipalMutation,\n} from \"../../data/principals/principalMutationJournal\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../data/principals/principalPolicyCoordinator\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport {\n assertAuthoredPrincipalOperationReceipt,\n type PrincipalMutationResponse,\n} from \"./principalOperationReceipt\";\n\ntype MutationResult = RequestResult<PrincipalMutationResponse>;\n\nexport interface PrincipalMutationJournalContext {\n readonly execSql: ExecSql;\n readonly scope: PrincipalMutationJournalScope;\n readonly signingKeyPair: SigningKeyPair;\n readonly stillCurrent: () => boolean;\n readonly submit: (\n mutation: AuthoredPrincipalMutation,\n ) => Promise<MutationResult>;\n}\n\nexport class PrincipalMutationOutcomeUnknownError extends Error {\n constructor(options?: ErrorOptions) {\n super(\n \"A saved policy request may have committed; resolve its outcome or explicitly abandon its retry before another mutation\",\n options,\n );\n this.name = \"PrincipalMutationOutcomeUnknownError\";\n }\n}\n\nfunction knownInitialRefusal(result: MutationResult): boolean {\n if (result.ok) return false;\n // ApiClient separates pre-dispatch cancellation from an uncertain sent write.\n if (result.kind === \"cancelled\") return true;\n if (result.kind !== \"http\") return false;\n return (\n [400, 401, 402, 403, 409].includes(result.status ?? 0) ||\n (result.status === 503 &&\n result.code === \"principal_history_preparation_unavailable\")\n );\n}\n\nasync function submitSavedMutation(\n input: PrincipalMutationJournalContext,\n row: PrincipalMutationJournalRow,\n recovering: boolean,\n): Promise<{ mutation: AuthoredPrincipalMutation; result: MutationResult }> {\n const mutation = await openPrincipalMutation({\n scope: input.scope,\n row,\n signingPublicKey: input.signingKeyPair.signingPublicKey,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n // Keep the authenticated copy for acknowledgement checks even if a transport\n // mutates its argument while preparing or retrying the HTTP request.\n let result: MutationResult;\n try {\n result = await input.submit(structuredClone(mutation));\n } catch (cause) {\n assertProjectionVerificationCurrent(input.stillCurrent);\n throw new PrincipalMutationOutcomeUnknownError({ cause });\n }\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (result.ok) {\n await assertAuthoredPrincipalOperationReceipt(mutation, result.data);\n } else if (recovering || !knownInitialRefusal(result)) {\n // A refusal today (including loss of admin access) cannot establish whether\n // an earlier disconnected attempt committed. Preserve the original intent.\n throw new PrincipalMutationOutcomeUnknownError();\n }\n await clearPrincipalMutationJournal({\n execSql: input.execSql,\n row,\n stillCurrent: input.stillCurrent,\n });\n assertProjectionVerificationCurrent(input.stillCurrent);\n return { mutation, result };\n}\n\n/** No HTTP begins until the exact authored request is durably claimed. */\nexport async function submitJournaledPrincipalMutation(\n input: PrincipalMutationJournalContext & {\n readonly mutation: AuthoredPrincipalMutation;\n },\n): Promise<MutationResult> {\n return getOrCreatePrincipalPolicyCoordinator(\n input.execSql,\n ).runJournalOperation(input.scope, async () => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await sealPrincipalMutation(input);\n await claimPrincipalMutationJournal({\n execSql: input.execSql,\n row,\n stillCurrent: input.stillCurrent,\n });\n return (await submitSavedMutation(input, row, false)).result;\n });\n}\n\n/** Retry only authenticated saved bytes, never an application mutation callback. */\nexport async function recoverJournaledPrincipalMutation(\n input: PrincipalMutationJournalContext,\n): Promise<{\n readonly mutation: AuthoredPrincipalMutation;\n readonly response: PrincipalMutationResponse;\n} | null> {\n return getOrCreatePrincipalPolicyCoordinator(\n input.execSql,\n ).runJournalOperation(input.scope, async () => {\n assertProjectionVerificationCurrent(input.stillCurrent);\n const row = await loadPrincipalMutationJournal(\n input.execSql,\n await principalMutationJournalScopeId(input.scope),\n );\n assertProjectionVerificationCurrent(input.stillCurrent);\n if (!row) return null;\n const { mutation, result } = await submitSavedMutation(input, row, true);\n if (!result.ok) throw new PrincipalMutationOutcomeUnknownError();\n return { mutation, response: result.data };\n });\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { loadPrincipalPolicyCheckpoint } from "../../data/persistence/keyingCheckpointPersistence.js";
2
- import { principalRecoveryEpoch } from "../../data/principals/principalRecoveryEpoch.js";
2
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../data/principals/principalPolicyCoordinator.js";
3
3
  async function pinCovers(input, head) {
4
4
  const pin = await loadPrincipalPolicyCheckpoint(input.execSql, head.principalType, head.principalId);
5
5
  return (!pin ||
@@ -7,7 +7,7 @@ async function pinCovers(input, head) {
7
7
  (pin.version === head.version && pin.stateHash === head.stateHash));
8
8
  }
9
9
  function organizationEpoch(input) {
10
- return () => principalRecoveryEpoch(input.execSql, input.organizationId);
10
+ return () => getOrCreatePrincipalPolicyCoordinator(input.execSql).recoveryEpoch(input.organizationId);
11
11
  }
12
12
  /** A directory names every group head; a newer pin on any of them evicts it. */
13
13
  export function directoryMemoValidity(input) {
@@ -1 +1 @@
1
- {"version":3,"file":"principalRecoveryMemoValidity.js","sourceRoot":"","sources":["principalRecoveryMemoValidity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,sBAAsB,EAAE,MAAM,8CAA8C,CAAC;AAoBtF,KAAK,UAAU,SAAS,CACtB,KAA+B,EAC/B,IAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,6BAA6B,CAC7C,KAAK,CAAC,OAAO,EACb,IAAI,CAAC,aAAa,EAClB,IAAI,CAAC,WAAW,CACjB,CAAC;IACF,OAAO,CACL,CAAC,GAAG;QACJ,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO;QAC1B,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,CACnE,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAA+B;IACxD,OAAO,GAAG,EAAE,CAAC,sBAAsB,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,CAAC;AAC3E,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CACnC,KAA+B;IAE/B,OAAO;QACL,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC;QAC/B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;YAC1B,KAAK,MAAM,IAAI,IAAI;gBACjB,SAAS,CAAC,MAAM,CAAC,KAAK;gBACtB,GAAG,SAAS,CAAC,UAAU,CAAC,UAAU;aACnC;gBACC,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,KAA+B,EAC/B,YAAwB;IAExB,OAAO;QACL,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC;QAC/B,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC;KAC7C,CAAC;AACJ,CAAC","sourcesContent":["import type { ReferencedPrincipalHead } from \"@tearleads/crypto\";\nimport { loadPrincipalPolicyCheckpoint } from \"../../data/persistence/keyingCheckpointPersistence\";\nimport { principalRecoveryEpoch } from \"../../data/principals/principalRecoveryEpoch\";\nimport type { RecoveredPrincipalPolicyHistory } from \"./principalHistoryRecoveryTypes\";\nimport type {\n PrincipalRecoveryContext,\n RecoveredPolicyDirectory,\n} from \"./principalRecoveryDirectory\";\n\n/** What keeps a memo entry valid beyond its producer's lease. */\nexport interface PrincipalRecoveryMemoValidity<T> {\n /** Organization hint epoch; production records it before its reads. */\n readonly epoch: () => number;\n /** No durable pin has moved past or forked from a memoized head. */\n readonly covers: (value: T) => Promise<boolean>;\n}\n\ntype PinnedHead = Pick<\n ReferencedPrincipalHead,\n \"principalType\" | \"principalId\" | \"version\" | \"stateHash\"\n>;\n\nasync function pinCovers(\n input: PrincipalRecoveryContext,\n head: PinnedHead,\n): Promise<boolean> {\n const pin = await loadPrincipalPolicyCheckpoint(\n input.execSql,\n head.principalType,\n head.principalId,\n );\n return (\n !pin ||\n pin.version < head.version ||\n (pin.version === head.version && pin.stateHash === head.stateHash)\n );\n}\n\nfunction organizationEpoch(input: PrincipalRecoveryContext) {\n return () => principalRecoveryEpoch(input.execSql, input.organizationId);\n}\n\n/** A directory names every group head; a newer pin on any of them evicts it. */\nexport function directoryMemoValidity(\n input: PrincipalRecoveryContext,\n): PrincipalRecoveryMemoValidity<RecoveredPolicyDirectory> {\n return {\n epoch: organizationEpoch(input),\n covers: async (directory) => {\n for (const head of [\n directory.policy.state,\n ...directory.descriptor.groupHeads,\n ])\n if (!(await pinCovers(input, head))) return false;\n return true;\n },\n };\n}\n\nexport function adminsMemoValidity(\n input: PrincipalRecoveryContext,\n expectedHead: PinnedHead,\n): PrincipalRecoveryMemoValidity<RecoveredPrincipalPolicyHistory> {\n return {\n epoch: organizationEpoch(input),\n covers: () => pinCovers(input, expectedHead),\n };\n}\n"]}
1
+ {"version":3,"file":"principalRecoveryMemoValidity.js","sourceRoot":"","sources":["principalRecoveryMemoValidity.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AACnG,OAAO,EAAE,qCAAqC,EAAE,MAAM,kDAAkD,CAAC;AAoBzG,KAAK,UAAU,SAAS,CACtB,KAA+B,EAC/B,IAAgB;IAEhB,MAAM,GAAG,GAAG,MAAM,6BAA6B,CAC7C,KAAK,CAAC,OAAO,EACb,IAAI,CAAC,aAAa,EAClB,IAAI,CAAC,WAAW,CACjB,CAAC;IACF,OAAO,CACL,CAAC,GAAG;QACJ,GAAG,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO;QAC1B,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,CAAC,CACnE,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,KAA+B;IACxD,OAAO,GAAG,EAAE,CACV,qCAAqC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,aAAa,CAChE,KAAK,CAAC,cAAc,CACrB,CAAC;AACN,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,qBAAqB,CACnC,KAA+B;IAE/B,OAAO;QACL,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC;QAC/B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;YAC1B,KAAK,MAAM,IAAI,IAAI;gBACjB,SAAS,CAAC,MAAM,CAAC,KAAK;gBACtB,GAAG,SAAS,CAAC,UAAU,CAAC,UAAU;aACnC;gBACC,IAAI,CAAC,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAC;YACpD,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,kBAAkB,CAChC,KAA+B,EAC/B,YAAwB;IAExB,OAAO;QACL,KAAK,EAAE,iBAAiB,CAAC,KAAK,CAAC;QAC/B,MAAM,EAAE,GAAG,EAAE,CAAC,SAAS,CAAC,KAAK,EAAE,YAAY,CAAC;KAC7C,CAAC;AACJ,CAAC","sourcesContent":["import type { ReferencedPrincipalHead } from \"@tearleads/crypto\";\nimport { loadPrincipalPolicyCheckpoint } from \"../../data/persistence/keyingCheckpointPersistence\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../data/principals/principalPolicyCoordinator\";\nimport type { RecoveredPrincipalPolicyHistory } from \"./principalHistoryRecoveryTypes\";\nimport type {\n PrincipalRecoveryContext,\n RecoveredPolicyDirectory,\n} from \"./principalRecoveryDirectory\";\n\n/** What keeps a memo entry valid beyond its producer's lease. */\nexport interface PrincipalRecoveryMemoValidity<T> {\n /** Organization hint epoch; production records it before its reads. */\n readonly epoch: () => number;\n /** No durable pin has moved past or forked from a memoized head. */\n readonly covers: (value: T) => Promise<boolean>;\n}\n\ntype PinnedHead = Pick<\n ReferencedPrincipalHead,\n \"principalType\" | \"principalId\" | \"version\" | \"stateHash\"\n>;\n\nasync function pinCovers(\n input: PrincipalRecoveryContext,\n head: PinnedHead,\n): Promise<boolean> {\n const pin = await loadPrincipalPolicyCheckpoint(\n input.execSql,\n head.principalType,\n head.principalId,\n );\n return (\n !pin ||\n pin.version < head.version ||\n (pin.version === head.version && pin.stateHash === head.stateHash)\n );\n}\n\nfunction organizationEpoch(input: PrincipalRecoveryContext) {\n return () =>\n getOrCreatePrincipalPolicyCoordinator(input.execSql).recoveryEpoch(\n input.organizationId,\n );\n}\n\n/** A directory names every group head; a newer pin on any of them evicts it. */\nexport function directoryMemoValidity(\n input: PrincipalRecoveryContext,\n): PrincipalRecoveryMemoValidity<RecoveredPolicyDirectory> {\n return {\n epoch: organizationEpoch(input),\n covers: async (directory) => {\n for (const head of [\n directory.policy.state,\n ...directory.descriptor.groupHeads,\n ])\n if (!(await pinCovers(input, head))) return false;\n return true;\n },\n };\n}\n\nexport function adminsMemoValidity(\n input: PrincipalRecoveryContext,\n expectedHead: PinnedHead,\n): PrincipalRecoveryMemoValidity<RecoveredPrincipalPolicyHistory> {\n return {\n epoch: organizationEpoch(input),\n covers: () => pinCovers(input, expectedHead),\n };\n}\n"]}
@@ -3,11 +3,11 @@ import { ProjectionDependencyUnavailableError } from "../../data/keyingProjectio
3
3
  import { runWithSecurityIncidentReporting } from "../../data/keyingProjectionVerification/error.js";
4
4
  import { assertProjectionVerificationCurrent, } from "../../data/keyingProjectionVerification/types.js";
5
5
  import { readPrincipalHistoryProtection } from "../../data/principals/principalHistoryRuntime.js";
6
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../data/principals/principalPolicyCoordinator.js";
6
7
  import { createPrincipalCurrentRecoveryBatch } from "./principalCurrentRecoveryBatch.js";
7
8
  import { PrincipalHistoryRecoveryRaceError, PrincipalPolicyHistoryReadError, } from "./principalHistoryRecoveryTypes.js";
8
9
  import { apiPrincipalPolicyPageReader, } from "./principalPolicyHistoryReader.js";
9
10
  import { recoverWithPrincipalLocalPreference } from "./principalRecoveryLocalPreference.js";
10
- import { queuePrincipalRecovery } from "./principalRecoveryQueue.js";
11
11
  /** Keep the private recovery key inside its runtime lease while resolving cited evidence. */
12
12
  export function createRuntimePrincipalPolicyResolver(runtime, options = {}) {
13
13
  const resolve = createRuntimePrincipalPolicyCurrentResolver(runtime);
@@ -32,7 +32,7 @@ export function createRuntimePrincipalPolicyCurrentResolver(runtime) {
32
32
  }
33
33
  return recover;
34
34
  };
35
- return (input) => queuePrincipalRecovery(runtime.infra.execSql, input.organizationId, () => runWithSecurityIncidentReporting(runtime.util.reportSecurityIncident, {
35
+ return (input) => getOrCreatePrincipalPolicyCoordinator(runtime.infra.execSql).queueRecovery(input.organizationId, () => runWithSecurityIncidentReporting(runtime.util.reportSecurityIncident, {
36
36
  objectId: input.reference?.principalId ?? input.organizationId,
37
37
  objectKind: "principal",
38
38
  operation: "principal.policy.recover",
@@ -1 +1 @@
1
- {"version":3,"file":"runtimePolicyRecovery.js","sourceRoot":"","sources":["runtimePolicyRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalPolicyPageCurrent } from \"@tearleads/api-client\";\nimport { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { ProjectionDependencyUnavailableError } from \"../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { runWithSecurityIncidentReporting } from \"../../data/keyingProjectionVerification/error\";\nimport {\n assertProjectionVerificationCurrent,\n type PrincipalPolicyResolveRequest,\n type ReferencedPrincipalPolicyWarmer,\n type ResolvedPrincipalPolicyEvidence,\n} from \"../../data/keyingProjectionVerification/types\";\nimport type { PrincipalHistoryProtectionLease } from \"../../data/principals/principalHistoryProtection\";\nimport { readPrincipalHistoryProtection } from \"../../data/principals/principalHistoryRuntime\";\nimport type { SecurityIncidentReporter } from \"../../data/securityIncidents\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport type { TrustedUserIdentityResolver } from \"../../data/trustedUserIdentity\";\nimport type { RecoveredPrincipalHistoryPage } from \"./loadRecoveredPrincipalHistoryPage\";\nimport { createPrincipalCurrentRecoveryBatch } from \"./principalCurrentRecoveryBatch\";\nimport {\n PrincipalHistoryRecoveryRaceError,\n PrincipalPolicyHistoryReadError,\n} from \"./principalHistoryRecoveryTypes\";\nimport {\n apiPrincipalPolicyPageReader,\n type PrincipalPolicyHistoryReader,\n} from \"./principalPolicyHistoryReader\";\nimport { recoverWithPrincipalLocalPreference } from \"./principalRecoveryLocalPreference\";\nimport { queuePrincipalRecovery } from \"./principalRecoveryQueue\";\nimport type { ScopedPrincipalPolicyRecovery } from \"./recoverScopedPrincipalPolicyHistory\";\n\nexport interface PrincipalPolicyRecoveryRuntime {\n readonly apiClient: Partial<PrincipalPolicyHistoryReader>;\n readonly infra: { readonly execSql: ExecSql };\n readonly state?: { readonly online: boolean } | undefined;\n readonly util: { readonly reportSecurityIncident: SecurityIncidentReporter };\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly withPrincipalHistoryProtection?:\n | PrincipalHistoryProtectionLease\n | undefined;\n}\n\n/** Keep the private recovery key inside its runtime lease while resolving cited evidence. */\nexport function createRuntimePrincipalPolicyResolver(\n runtime: PrincipalPolicyRecoveryRuntime,\n options: { readonly preferLocalCurrent?: boolean } = {},\n): ReferencedPrincipalPolicyWarmer[\"resolveReference\"] {\n const resolve = createRuntimePrincipalPolicyCurrentResolver(runtime);\n return resolve\n ? (input) =>\n resolve({ ...input, preferLocalCurrent: options.preferLocalCurrent })\n : undefined;\n}\n\nexport interface ResolvedPrincipalPolicyCurrent\n extends ResolvedPrincipalPolicyEvidence {\n readonly current: PrincipalPolicyPageCurrent;\n /** Rows end at the selected reference; current/policy may describe a newer recovered head. */\n readonly historyPage?: RecoveredPrincipalHistoryPage | undefined;\n}\n\ninterface PrincipalPolicyCurrentRequest\n extends Omit<PrincipalPolicyResolveRequest, \"reference\"> {\n readonly reference?: PrincipalPolicyResolveRequest[\"reference\"] | undefined;\n /** A read-model caller has selected this exact signed head; not a freshness read. */\n readonly preferLocalCurrent?: boolean | undefined;\n readonly historyPage?: { readonly beforeVersion?: number } | undefined;\n}\n\n/** Keep current artifacts with their verified evidence and private runtime lifetime. */\nexport function createRuntimePrincipalPolicyCurrentResolver(\n runtime: PrincipalPolicyRecoveryRuntime,\n):\n | ((\n input: PrincipalPolicyCurrentRequest,\n ) => Promise<ResolvedPrincipalPolicyCurrent>)\n | undefined {\n const lease = readPrincipalHistoryProtection(runtime);\n const historyReader = apiPrincipalPolicyPageReader(runtime.apiClient);\n if (!lease || !historyReader) return undefined;\n const batches = new WeakMap<\n object,\n ReturnType<typeof createPrincipalCurrentRecoveryBatch>\n >();\n const recoverFor = (batch: object | undefined) => {\n if (!batch) return createPrincipalCurrentRecoveryBatch();\n let recover = batches.get(batch);\n if (!recover) {\n recover = createPrincipalCurrentRecoveryBatch();\n batches.set(batch, recover);\n }\n return recover;\n };\n return (input) =>\n queuePrincipalRecovery(runtime.infra.execSql, input.organizationId, () =>\n runWithSecurityIncidentReporting(\n runtime.util.reportSecurityIncident,\n {\n objectId: input.reference?.principalId ?? input.organizationId,\n objectKind: \"principal\",\n operation: \"principal.policy.recover\",\n organizationId: input.organizationId,\n },\n () =>\n lease(async ({ protection, stillCurrent: leaseCurrent }) => {\n const stillCurrent = () =>\n leaseCurrent() && input.stillCurrent?.() !== false;\n assertProjectionVerificationCurrent(stillCurrent);\n assertHistoryPageRequest(input);\n const offline = runtime.state?.online === false;\n try {\n const options = {\n historyReader,\n execSql: runtime.infra.execSql,\n offline,\n organizationId: input.organizationId,\n protection,\n resolveTrustedUserIdentity: runtime.resolveTrustedUserIdentity,\n stillCurrent,\n };\n const batch = recoverFor(input.recoveryBatch);\n const result: ScopedPrincipalPolicyRecovery = input.reference\n ? await recoverWithPrincipalLocalPreference(\n batch.recover,\n {\n ...options,\n reference: input.reference,\n historyPage: input.historyPage,\n },\n input.preferLocalCurrent === true ||\n input.preferLocalHistory === true,\n input.preferLocalHistory === true,\n )\n : await batch.discover(options);\n // Reused evidence stays bound to every producer lifetime.\n const resultCurrent = () =>\n stillCurrent() && result.stillCurrent();\n assertProjectionVerificationCurrent(resultCurrent);\n return {\n current: result.current,\n organizationId: input.organizationId,\n policy: result.policy,\n dependencies: result.dependencies,\n historyPage: result.historyPage,\n stillCurrent: resultCurrent,\n };\n } catch (error) {\n assertProjectionVerificationCurrent(stillCurrent);\n if (\n error instanceof PrincipalPolicyHistoryReadError ||\n error instanceof PrincipalHistoryRecoveryRaceError ||\n (error instanceof KeyingVerificationError &&\n error.code === \"missing_dependency\")\n )\n throw new ProjectionDependencyUnavailableError(error.message);\n throw error;\n }\n }),\n ),\n );\n}\n\nfunction assertHistoryPageRequest(input: PrincipalPolicyCurrentRequest) {\n if (!input.historyPage) return;\n const before = input.historyPage.beforeVersion;\n if (\n !input.reference ||\n (before !== undefined &&\n (!Number.isSafeInteger(before) ||\n before <= 1 ||\n before > input.reference.version + 1))\n )\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"History display requires a selected head and an in-range cursor\",\n );\n}\n"]}
1
+ {"version":3,"file":"runtimePolicyRecovery.js","sourceRoot":"","sources":["runtimePolicyRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalPolicyPageCurrent } from \"@tearleads/api-client\";\nimport { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { ProjectionDependencyUnavailableError } from \"../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { runWithSecurityIncidentReporting } from \"../../data/keyingProjectionVerification/error\";\nimport {\n assertProjectionVerificationCurrent,\n type PrincipalPolicyResolveRequest,\n type ReferencedPrincipalPolicyWarmer,\n type ResolvedPrincipalPolicyEvidence,\n} from \"../../data/keyingProjectionVerification/types\";\nimport type { PrincipalHistoryProtectionLease } from \"../../data/principals/principalHistoryProtection\";\nimport { readPrincipalHistoryProtection } from \"../../data/principals/principalHistoryRuntime\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../data/principals/principalPolicyCoordinator\";\nimport type { SecurityIncidentReporter } from \"../../data/securityIncidents\";\nimport type { ExecSql } from \"../../data/sqlite/sqlSchema\";\nimport type { TrustedUserIdentityResolver } from \"../../data/trustedUserIdentity\";\nimport type { RecoveredPrincipalHistoryPage } from \"./loadRecoveredPrincipalHistoryPage\";\nimport { createPrincipalCurrentRecoveryBatch } from \"./principalCurrentRecoveryBatch\";\nimport {\n PrincipalHistoryRecoveryRaceError,\n PrincipalPolicyHistoryReadError,\n} from \"./principalHistoryRecoveryTypes\";\nimport {\n apiPrincipalPolicyPageReader,\n type PrincipalPolicyHistoryReader,\n} from \"./principalPolicyHistoryReader\";\nimport { recoverWithPrincipalLocalPreference } from \"./principalRecoveryLocalPreference\";\nimport type { ScopedPrincipalPolicyRecovery } from \"./recoverScopedPrincipalPolicyHistory\";\n\nexport interface PrincipalPolicyRecoveryRuntime {\n readonly apiClient: Partial<PrincipalPolicyHistoryReader>;\n readonly infra: { readonly execSql: ExecSql };\n readonly state?: { readonly online: boolean } | undefined;\n readonly util: { readonly reportSecurityIncident: SecurityIncidentReporter };\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly withPrincipalHistoryProtection?:\n | PrincipalHistoryProtectionLease\n | undefined;\n}\n\n/** Keep the private recovery key inside its runtime lease while resolving cited evidence. */\nexport function createRuntimePrincipalPolicyResolver(\n runtime: PrincipalPolicyRecoveryRuntime,\n options: { readonly preferLocalCurrent?: boolean } = {},\n): ReferencedPrincipalPolicyWarmer[\"resolveReference\"] {\n const resolve = createRuntimePrincipalPolicyCurrentResolver(runtime);\n return resolve\n ? (input) =>\n resolve({ ...input, preferLocalCurrent: options.preferLocalCurrent })\n : undefined;\n}\n\nexport interface ResolvedPrincipalPolicyCurrent\n extends ResolvedPrincipalPolicyEvidence {\n readonly current: PrincipalPolicyPageCurrent;\n /** Rows end at the selected reference; current/policy may describe a newer recovered head. */\n readonly historyPage?: RecoveredPrincipalHistoryPage | undefined;\n}\n\ninterface PrincipalPolicyCurrentRequest\n extends Omit<PrincipalPolicyResolveRequest, \"reference\"> {\n readonly reference?: PrincipalPolicyResolveRequest[\"reference\"] | undefined;\n /** A read-model caller has selected this exact signed head; not a freshness read. */\n readonly preferLocalCurrent?: boolean | undefined;\n readonly historyPage?: { readonly beforeVersion?: number } | undefined;\n}\n\n/** Keep current artifacts with their verified evidence and private runtime lifetime. */\nexport function createRuntimePrincipalPolicyCurrentResolver(\n runtime: PrincipalPolicyRecoveryRuntime,\n):\n | ((\n input: PrincipalPolicyCurrentRequest,\n ) => Promise<ResolvedPrincipalPolicyCurrent>)\n | undefined {\n const lease = readPrincipalHistoryProtection(runtime);\n const historyReader = apiPrincipalPolicyPageReader(runtime.apiClient);\n if (!lease || !historyReader) return undefined;\n const batches = new WeakMap<\n object,\n ReturnType<typeof createPrincipalCurrentRecoveryBatch>\n >();\n const recoverFor = (batch: object | undefined) => {\n if (!batch) return createPrincipalCurrentRecoveryBatch();\n let recover = batches.get(batch);\n if (!recover) {\n recover = createPrincipalCurrentRecoveryBatch();\n batches.set(batch, recover);\n }\n return recover;\n };\n return (input) =>\n getOrCreatePrincipalPolicyCoordinator(runtime.infra.execSql).queueRecovery(\n input.organizationId,\n () =>\n runWithSecurityIncidentReporting(\n runtime.util.reportSecurityIncident,\n {\n objectId: input.reference?.principalId ?? input.organizationId,\n objectKind: \"principal\",\n operation: \"principal.policy.recover\",\n organizationId: input.organizationId,\n },\n () =>\n lease(async ({ protection, stillCurrent: leaseCurrent }) => {\n const stillCurrent = () =>\n leaseCurrent() && input.stillCurrent?.() !== false;\n assertProjectionVerificationCurrent(stillCurrent);\n assertHistoryPageRequest(input);\n const offline = runtime.state?.online === false;\n try {\n const options = {\n historyReader,\n execSql: runtime.infra.execSql,\n offline,\n organizationId: input.organizationId,\n protection,\n resolveTrustedUserIdentity:\n runtime.resolveTrustedUserIdentity,\n stillCurrent,\n };\n const batch = recoverFor(input.recoveryBatch);\n const result: ScopedPrincipalPolicyRecovery = input.reference\n ? await recoverWithPrincipalLocalPreference(\n batch.recover,\n {\n ...options,\n reference: input.reference,\n historyPage: input.historyPage,\n },\n input.preferLocalCurrent === true ||\n input.preferLocalHistory === true,\n input.preferLocalHistory === true,\n )\n : await batch.discover(options);\n // Reused evidence stays bound to every producer lifetime.\n const resultCurrent = () =>\n stillCurrent() && result.stillCurrent();\n assertProjectionVerificationCurrent(resultCurrent);\n return {\n current: result.current,\n organizationId: input.organizationId,\n policy: result.policy,\n dependencies: result.dependencies,\n historyPage: result.historyPage,\n stillCurrent: resultCurrent,\n };\n } catch (error) {\n assertProjectionVerificationCurrent(stillCurrent);\n if (\n error instanceof PrincipalPolicyHistoryReadError ||\n error instanceof PrincipalHistoryRecoveryRaceError ||\n (error instanceof KeyingVerificationError &&\n error.code === \"missing_dependency\")\n )\n throw new ProjectionDependencyUnavailableError(error.message);\n throw error;\n }\n }),\n ),\n );\n}\n\nfunction assertHistoryPageRequest(input: PrincipalPolicyCurrentRequest) {\n if (!input.historyPage) return;\n const before = input.historyPage.beforeVersion;\n if (\n !input.reference ||\n (before !== undefined &&\n (!Number.isSafeInteger(before) ||\n before <= 1 ||\n before > input.reference.version + 1))\n )\n throw new KeyingVerificationError(\n \"invalid_shape\",\n \"History display requires a selected head and an in-range cursor\",\n );\n}\n"]}
@@ -3,10 +3,10 @@ import { ProjectionDependencyUnavailableError } from "../../data/keyingProjectio
3
3
  import { runWithSecurityIncidentReporting } from "../../data/keyingProjectionVerification/error.js";
4
4
  import { assertProjectionVerificationCurrent, } from "../../data/keyingProjectionVerification/types.js";
5
5
  import { readPrincipalHistoryProtection } from "../../data/principals/principalHistoryRuntime.js";
6
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../data/principals/principalPolicyCoordinator.js";
6
7
  import { PrincipalPolicyHistoryReadError } from "./principalHistoryRecoveryTypes.js";
7
8
  import { apiProjectionPolicyHistoryReader } from "./principalPolicyHistoryReader.js";
8
9
  import { isPrincipalRecoveryOutage } from "./principalRecoveryOutage.js";
9
- import { queuePrincipalRecovery } from "./principalRecoveryQueue.js";
10
10
  import { recoverProjectionPolicyHistory, } from "./recoverProjectionPolicyHistory.js";
11
11
  async function recoverWithOutageFallback(options) {
12
12
  try {
@@ -24,7 +24,7 @@ export function createRuntimeProjectionPolicyResolver(runtime) {
24
24
  const historyReader = apiProjectionPolicyHistoryReader(runtime.apiClient);
25
25
  if (!lease || !historyReader)
26
26
  return undefined;
27
- return (input) => queuePrincipalRecovery(runtime.infra.execSql, input.organizationId, () => runWithSecurityIncidentReporting(runtime.util.reportSecurityIncident, {
27
+ return (input) => getOrCreatePrincipalPolicyCoordinator(runtime.infra.execSql).queueRecovery(input.organizationId, () => runWithSecurityIncidentReporting(runtime.util.reportSecurityIncident, {
28
28
  objectId: input.organizationId,
29
29
  objectKind: "principal",
30
30
  operation: "principal.projection.recover",
@@ -1 +1 @@
1
- {"version":3,"file":"runtimeProjectionPolicyRecovery.js","sourceRoot":"","sources":["runtimeProjectionPolicyRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { ProjectionDependencyUnavailableError } from \"../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { runWithSecurityIncidentReporting } from \"../../data/keyingProjectionVerification/error\";\nimport {\n assertProjectionVerificationCurrent,\n type ReferencedPrincipalPolicyWarmer,\n} from \"../../data/keyingProjectionVerification/types\";\nimport { readPrincipalHistoryProtection } from \"../../data/principals/principalHistoryRuntime\";\nimport { PrincipalPolicyHistoryReadError } from \"./principalHistoryRecoveryTypes\";\nimport { apiProjectionPolicyHistoryReader } from \"./principalPolicyHistoryReader\";\nimport { isPrincipalRecoveryOutage } from \"./principalRecoveryOutage\";\nimport { queuePrincipalRecovery } from \"./principalRecoveryQueue\";\nimport {\n type ProjectionPolicyHistoryRecoveryOptions,\n recoverProjectionPolicyHistory,\n} from \"./recoverProjectionPolicyHistory\";\nimport type { PrincipalPolicyRecoveryRuntime } from \"./runtimePolicyRecovery\";\n\nasync function recoverWithOutageFallback(\n options: ProjectionPolicyHistoryRecoveryOptions,\n) {\n try {\n return await recoverProjectionPolicyHistory(options);\n } catch (error) {\n assertProjectionVerificationCurrent(options.stillCurrent);\n if (options.offline || !isPrincipalRecoveryOutage(error)) throw error;\n return recoverProjectionPolicyHistory({ ...options, offline: true });\n }\n}\n\nexport function createRuntimeProjectionPolicyResolver(\n runtime: PrincipalPolicyRecoveryRuntime,\n): ReferencedPrincipalPolicyWarmer[\"resolveProjectionHistory\"] {\n const lease = readPrincipalHistoryProtection(runtime);\n const historyReader = apiProjectionPolicyHistoryReader(runtime.apiClient);\n if (!lease || !historyReader) return undefined;\n return (input) =>\n queuePrincipalRecovery(runtime.infra.execSql, input.organizationId, () =>\n runWithSecurityIncidentReporting(\n runtime.util.reportSecurityIncident,\n {\n objectId: input.organizationId,\n objectKind: \"principal\",\n operation: \"principal.projection.recover\",\n organizationId: input.organizationId,\n },\n () =>\n lease(async ({ protection, stillCurrent: leaseCurrent }) => {\n const stillCurrent = () =>\n leaseCurrent() && input.stillCurrent?.() !== false;\n assertProjectionVerificationCurrent(stillCurrent);\n try {\n const policies = await recoverWithOutageFallback({\n ...input,\n historyReader,\n execSql: runtime.infra.execSql,\n protection,\n stillCurrent,\n offline: runtime.state?.online === false,\n resolveTrustedUserIdentity: runtime.resolveTrustedUserIdentity,\n });\n return { policies, stillCurrent };\n } catch (error) {\n assertProjectionVerificationCurrent(stillCurrent);\n if (\n error instanceof PrincipalPolicyHistoryReadError ||\n (error instanceof KeyingVerificationError &&\n error.code === \"missing_dependency\")\n )\n throw new ProjectionDependencyUnavailableError(error.message);\n throw error;\n }\n }),\n ),\n );\n}\n"]}
1
+ {"version":3,"file":"runtimeProjectionPolicyRecovery.js","sourceRoot":"","sources":["runtimeProjectionPolicyRecovery.ts"],"names":[],"mappings":"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","sourcesContent":["import { KeyingVerificationError } from \"@tearleads/crypto\";\nimport { ProjectionDependencyUnavailableError } from \"../../data/keyingProjectionVerification/dependencyUnavailable\";\nimport { runWithSecurityIncidentReporting } from \"../../data/keyingProjectionVerification/error\";\nimport {\n assertProjectionVerificationCurrent,\n type ReferencedPrincipalPolicyWarmer,\n} from \"../../data/keyingProjectionVerification/types\";\nimport { readPrincipalHistoryProtection } from \"../../data/principals/principalHistoryRuntime\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../data/principals/principalPolicyCoordinator\";\nimport { PrincipalPolicyHistoryReadError } from \"./principalHistoryRecoveryTypes\";\nimport { apiProjectionPolicyHistoryReader } from \"./principalPolicyHistoryReader\";\nimport { isPrincipalRecoveryOutage } from \"./principalRecoveryOutage\";\nimport {\n type ProjectionPolicyHistoryRecoveryOptions,\n recoverProjectionPolicyHistory,\n} from \"./recoverProjectionPolicyHistory\";\nimport type { PrincipalPolicyRecoveryRuntime } from \"./runtimePolicyRecovery\";\n\nasync function recoverWithOutageFallback(\n options: ProjectionPolicyHistoryRecoveryOptions,\n) {\n try {\n return await recoverProjectionPolicyHistory(options);\n } catch (error) {\n assertProjectionVerificationCurrent(options.stillCurrent);\n if (options.offline || !isPrincipalRecoveryOutage(error)) throw error;\n return recoverProjectionPolicyHistory({ ...options, offline: true });\n }\n}\n\nexport function createRuntimeProjectionPolicyResolver(\n runtime: PrincipalPolicyRecoveryRuntime,\n): ReferencedPrincipalPolicyWarmer[\"resolveProjectionHistory\"] {\n const lease = readPrincipalHistoryProtection(runtime);\n const historyReader = apiProjectionPolicyHistoryReader(runtime.apiClient);\n if (!lease || !historyReader) return undefined;\n return (input) =>\n getOrCreatePrincipalPolicyCoordinator(runtime.infra.execSql).queueRecovery(\n input.organizationId,\n () =>\n runWithSecurityIncidentReporting(\n runtime.util.reportSecurityIncident,\n {\n objectId: input.organizationId,\n objectKind: \"principal\",\n operation: \"principal.projection.recover\",\n organizationId: input.organizationId,\n },\n () =>\n lease(async ({ protection, stillCurrent: leaseCurrent }) => {\n const stillCurrent = () =>\n leaseCurrent() && input.stillCurrent?.() !== false;\n assertProjectionVerificationCurrent(stillCurrent);\n try {\n const policies = await recoverWithOutageFallback({\n ...input,\n historyReader,\n execSql: runtime.infra.execSql,\n protection,\n stillCurrent,\n offline: runtime.state?.online === false,\n resolveTrustedUserIdentity:\n runtime.resolveTrustedUserIdentity,\n });\n return { policies, stillCurrent };\n } catch (error) {\n assertProjectionVerificationCurrent(stillCurrent);\n if (\n error instanceof PrincipalPolicyHistoryReadError ||\n (error instanceof KeyingVerificationError &&\n error.code === \"missing_dependency\")\n )\n throw new ProjectionDependencyUnavailableError(error.message);\n throw error;\n }\n }),\n ),\n );\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { assertProjectionVerificationCurrent } from "../../data/keyingProjectionVerification/types.js";
2
2
  import { principalPolicyReferenceFromBundle } from "../../data/principals/principalPolicyAdminSigners.js";
3
+ import { getOrCreatePrincipalPolicyCoordinator } from "../../data/principals/principalPolicyCoordinator.js";
3
4
  import { createPrincipalCurrentRecoveryBatch } from "../principals/principalCurrentRecoveryBatch.js";
4
- import { queuePrincipalRecovery } from "../principals/principalRecoveryQueue.js";
5
5
  /**
6
6
  * Serves only the acknowledged initial heads, each complete in one page. The
7
7
  * ordinary verifier still checks every signature, the directory binding and
@@ -91,7 +91,7 @@ async function primeInProvisioningScope(runtime, input) {
91
91
  ? identity
92
92
  : null;
93
93
  };
94
- await queuePrincipalRecovery(execSql, input.organizationId, () => lease(({ protection, stillCurrent }) => primeProvisionedPrincipalHistory({
94
+ await getOrCreatePrincipalPolicyCoordinator(execSql).queueRecovery(input.organizationId, () => lease(({ protection, stillCurrent }) => primeProvisionedPrincipalHistory({
95
95
  execSql,
96
96
  organizationId: input.organizationId,
97
97
  policies: input.policies,
@@ -1 +1 @@
1
- {"version":3,"file":"provisionedPrincipalHistory.js","sourceRoot":"","sources":["provisionedPrincipalHistory.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mCAAmC,EAAE,MAAM,+CAA+C,CAAC;AAEpG,OAAO,EAAE,kCAAkC,EAAE,MAAM,mDAAmD,CAAC;AAMvG,OAAO,EAAE,mCAAmC,EAAE,MAAM,6CAA6C,CAAC;AAElG,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAyB9E;;;;GAIG;AACH,SAAS,sBAAsB,CAC7B,QAAsC;IAEtC,MAAM,OAAO,GAAG;QACd,QAAQ,CAAC,YAAY;QACrB,QAAQ,CAAC,UAAU;QACnB,QAAQ,CAAC,WAAW;KACrB,CAAC;IACF,OAAO;QACL,KAAK,CAAC,CAAC,uBAAuB,CAAC,aAAa,EAAE,WAAW,EAAE,OAAO,GAAG,EAAE;YACrE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CACzB,CAAC,EAAE,YAAY,EAAE,EAAE,EAAE,CACnB,YAAY,CAAC,aAAa,KAAK,aAAa;gBAC5C,YAAY,CAAC,WAAW,KAAK,WAAW,CAC3C,CAAC;YACF,IACE,CAAC,MAAM;gBACP,OAAO,CAAC,MAAM,KAAK,SAAS;gBAC5B,CAAC,OAAO,CAAC,YAAY,IAAI,CAAC,CAAC,KAAK,CAAC;gBACjC,CAAC,OAAO,CAAC,SAAS,KAAK,SAAS;oBAC9B,OAAO,CAAC,SAAS,KAAK,MAAM,CAAC,YAAY,CAAC,SAAS,CAAC;gBAEtD,MAAM,IAAI,KAAK,CACb,yEAAyE,CAC1E,CAAC;YACJ,MAAM;gBACJ,EAAE,EAAE,IAAI;gBACR,IAAI,EAAE;oBACJ,GAAG,eAAe,CAAC,MAAM,CAAC;oBAC1B,WAAW,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,gBAAgB,EAAE,IAAI,EAAE;iBACzD;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,gCAAgC,CAAC,KAO/C;IACC,MAAM,EAAE,OAAO,EAAE,GAAG,mCAAmC,EAAE,CAAC;IAC1D,MAAM,OAAO,GAAG;QACd,aAAa,EAAE,sBAAsB,CAAC,KAAK,CAAC,QAAQ,CAAC;QACrD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,KAAK;QACd,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,0BAA0B,EAAE,KAAK,CAAC,0BAA0B;QAC5D,YAAY,EAAE,KAAK,CAAC,YAAY;KACjC,CAAC;IACF,wEAAwE;IACxE,+DAA+D;IAC/D,KAAK,MAAM,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,UAAU,EAAE,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC;QACzE,MAAM,OAAO,CAAC;YACZ,GAAG,OAAO;YACV,SAAS,EAAE,kCAAkC,CAAC,KAAK,CAAC;SACrD,CAAC,CAAC;AACP,CAAC;AAgBD;;;;;;;GAOG;AACH,MAAM,UAAU,uCAAuC,CACrD,OAA2C;IAE3C,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;QACrB,IAAI,CAAC;YACH,MAAM,wBAAwB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,QAAQ,CACd,wEAAwE,EACxE,KAAK,CACN,CAAC;QACJ,CAAC;IACH,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,wBAAwB,CACrC,OAA2C,EAC3C,KAAuD;IAEvD,sEAAsE;IACtE,MAAM,kBAAkB,GACtB,KAAK,CAAC,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,wBAAwB,CAAC;IACpE,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnD,MAAM,YAAY,GAAG,GAAG,EAAE,CACxB,OAAO,KAAK,IAAI;QAChB,KAAK,CAAC,SAAS,EAAE;QACjB,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,OAAO;QAC9C,OAAO,CAAC,kBAAkB,EAAE,KAAK,kBAAkB,CAAC;IACtD,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/D,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK;QAAE,OAAO;IAC/B,MAAM,0BAA0B,GAAgC,KAAK,EACnE,YAAY,EACZ,EAAE;QACF,IAAI,YAAY,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAC/C,mCAAmC,CAAC,YAAY,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,kBAAkB,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;QACzE,mCAAmC,CAAC,YAAY,CAAC,CAAC;QAClD,OAAO,QAAQ,EAAE,qBAAqB,KAAK,kBAAkB;YAC3D,CAAC,CAAC,QAAQ;YACV,CAAC,CAAC,IAAI,CAAC;IACX,CAAC,CAAC;IACF,MAAM,sBAAsB,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,EAAE,GAAG,EAAE,CAC/D,KAAK,CAAC,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,EAAE,EAAE,CACrC,gCAAgC,CAAC;QAC/B,OAAO;QACP,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU;QACV,0BAA0B;QAC1B,wDAAwD;QACxD,YAAY,EAAE,GAAG,EAAE,CAAC,YAAY,EAAE,IAAI,YAAY,EAAE;KACrD,CAAC,CACH,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { PrincipalPolicyHistoryProgressOptions } from \"@tearleads/crypto\";\nimport type { PrincipalPolicyBundleResponse } from \"@tearleads/validators/response\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport type { PrincipalHistoryProtectionLease } from \"../../data/principals/principalHistoryProtection\";\nimport { principalPolicyReferenceFromBundle } from \"../../data/principals/principalPolicyAdminSigners\";\nimport type { ExecSql, ExecSqlClientLike } from \"../../data/sqlite/sqlSchema\";\nimport type {\n TrustedUserIdentity,\n TrustedUserIdentityResolver,\n} from \"../../data/trustedUserIdentity\";\nimport { createPrincipalCurrentRecoveryBatch } from \"../principals/principalCurrentRecoveryBatch\";\nimport type { PrincipalPolicyPageReader } from \"../principals/principalPolicyHistoryReader\";\nimport { queuePrincipalRecovery } from \"../principals/principalRecoveryQueue\";\n\n/** The version-1 policies a provisioning request signed and its response acknowledged. */\ninterface ProvisionedPrincipalPolicies {\n readonly adminGroup: PrincipalPolicyBundleResponse;\n readonly memberGroup: PrincipalPolicyBundleResponse;\n readonly organization: PrincipalPolicyBundleResponse;\n}\n\n/**\n * Records verified recovery progress for an organization's founding policy\n * heads, so the device's first scoped recovery of them needs no history read.\n * It is bound to the provisioning scope: the database the bootstrap committed\n * to and the identity that signed it. Failure leaves the online path.\n */\nexport type ProvisionedPrincipalHistoryPrimer = (input: {\n /** The database client the provisioning bootstrap committed to. */\n readonly database: ExecSqlClientLike;\n /** Whether the identity that provisioned is still the active one. */\n readonly isCurrent: () => boolean;\n readonly organizationId: string;\n readonly policies: ProvisionedPrincipalPolicies;\n readonly userId: string;\n}) => Promise<void>;\n\n/**\n * Serves only the acknowledged initial heads, each complete in one page. The\n * ordinary verifier still checks every signature, the directory binding and\n * strict Admins rules before any progress is saved.\n */\nfunction provisionedPolicyPages(\n policies: ProvisionedPrincipalPolicies,\n): PrincipalPolicyPageReader {\n const bundles = [\n policies.organization,\n policies.adminGroup,\n policies.memberGroup,\n ];\n return {\n async *getPrincipalPolicyPages(principalType, principalId, options = {}) {\n const bundle = bundles.find(\n ({ currentState }) =>\n currentState.principalType === principalType &&\n currentState.principalId === principalId,\n );\n if (\n !bundle ||\n options.resume !== undefined ||\n (options.afterVersion ?? 0) !== 0 ||\n (options.stateHash !== undefined &&\n options.stateHash !== bundle.currentState.stateHash)\n )\n throw new Error(\n \"Provisioned principal history holds only the acknowledged initial heads\",\n );\n yield {\n ok: true,\n data: {\n ...structuredClone(bundle),\n historyPage: { afterVersion: 0, nextAfterVersion: null },\n },\n };\n },\n };\n}\n\nasync function primeProvisionedPrincipalHistory(input: {\n readonly execSql: ExecSql;\n readonly organizationId: string;\n readonly policies: ProvisionedPrincipalPolicies;\n readonly protection: PrincipalPolicyHistoryProgressOptions;\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const { recover } = createPrincipalCurrentRecoveryBatch();\n const options = {\n historyReader: provisionedPolicyPages(input.policies),\n execSql: input.execSql,\n offline: false,\n organizationId: input.organizationId,\n protection: input.protection,\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n };\n // Scoped recovery of each group also completes the directory and strict\n // Admins stages; Members reuses them through the shared batch.\n for (const group of [input.policies.adminGroup, input.policies.memberGroup])\n await recover({\n ...options,\n reference: principalPolicyReferenceFromBundle(group),\n });\n}\n\n/** The runtime seams priming reads; none of them writes durable state. */\ninterface ProvisionedPrincipalHistoryRuntime {\n readonly bindLease: () => PrincipalHistoryProtectionLease | undefined;\n /** The runtime executor, only while `database` is its ready database. */\n readonly execSqlFor: (database: ExecSqlClientLike) => ExecSql | null;\n readonly logError: (message: string | Error, cause?: unknown) => void;\n /** Read-only: the identity the trust store already pins for a user. */\n readonly loadPinnedIdentity: (\n execSql: ExecSql,\n userId: string,\n ) => Promise<TrustedUserIdentity | null>;\n readonly signingFingerprint: () => string | null;\n}\n\n/**\n * Binds priming to the exact provisioning scope, captured before any await:\n * the bootstrap's database and runtime executor, the identity whose key signed\n * the founding states, and a protection lease for that identity. Every later\n * step, and every progress write inside its transaction, rechecks that scope,\n * so a switched identity or database aborts priming without a durable write.\n * Priming never writes a trust pin; it only reads the founder's existing one.\n */\nexport function createProvisionedPrincipalHistoryPrimer(\n runtime: ProvisionedPrincipalHistoryRuntime,\n): ProvisionedPrincipalHistoryPrimer {\n return async (input) => {\n try {\n await primeInProvisioningScope(runtime, input);\n } catch (error) {\n runtime.logError(\n \"Provisioned principal history was not primed; recovery reads it online\",\n error,\n );\n }\n };\n}\n\nasync function primeInProvisioningScope(\n runtime: ProvisionedPrincipalHistoryRuntime,\n input: Parameters<ProvisionedPrincipalHistoryPrimer>[0],\n): Promise<void> {\n // Everything below is captured synchronously, before the first await.\n const founderFingerprint =\n input.policies.organization.currentState.signerUserKeyFingerprint;\n const execSql = runtime.execSqlFor(input.database);\n const scopeCurrent = () =>\n execSql !== null &&\n input.isCurrent() &&\n runtime.execSqlFor(input.database) === execSql &&\n runtime.signingFingerprint() === founderFingerprint;\n const lease = scopeCurrent() ? runtime.bindLease() : undefined;\n if (!execSql || !lease) return;\n const resolveTrustedUserIdentity: TrustedUserIdentityResolver = async (\n signerUserId,\n ) => {\n if (signerUserId !== input.userId) return null;\n assertProjectionVerificationCurrent(scopeCurrent);\n const identity = await runtime.loadPinnedIdentity(execSql, input.userId);\n assertProjectionVerificationCurrent(scopeCurrent);\n return identity?.signingKeyFingerprint === founderFingerprint\n ? identity\n : null;\n };\n await queuePrincipalRecovery(execSql, input.organizationId, () =>\n lease(({ protection, stillCurrent }) =>\n primeProvisionedPrincipalHistory({\n execSql,\n organizationId: input.organizationId,\n policies: input.policies,\n protection,\n resolveTrustedUserIdentity,\n // Progress writes check this inside their transactions.\n stillCurrent: () => stillCurrent() && scopeCurrent(),\n }),\n ),\n );\n}\n"]}
1
+ {"version":3,"file":"provisionedPrincipalHistory.js","sourceRoot":"","sources":["provisionedPrincipalHistory.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PrincipalPolicyHistoryProgressOptions } from \"@tearleads/crypto\";\nimport type { PrincipalPolicyBundleResponse } from \"@tearleads/validators/response\";\nimport { assertProjectionVerificationCurrent } from \"../../data/keyingProjectionVerification/types\";\nimport type { PrincipalHistoryProtectionLease } from \"../../data/principals/principalHistoryProtection\";\nimport { principalPolicyReferenceFromBundle } from \"../../data/principals/principalPolicyAdminSigners\";\nimport { getOrCreatePrincipalPolicyCoordinator } from \"../../data/principals/principalPolicyCoordinator\";\nimport type { ExecSql, ExecSqlClientLike } from \"../../data/sqlite/sqlSchema\";\nimport type {\n TrustedUserIdentity,\n TrustedUserIdentityResolver,\n} from \"../../data/trustedUserIdentity\";\nimport { createPrincipalCurrentRecoveryBatch } from \"../principals/principalCurrentRecoveryBatch\";\nimport type { PrincipalPolicyPageReader } from \"../principals/principalPolicyHistoryReader\";\n\n/** The version-1 policies a provisioning request signed and its response acknowledged. */\ninterface ProvisionedPrincipalPolicies {\n readonly adminGroup: PrincipalPolicyBundleResponse;\n readonly memberGroup: PrincipalPolicyBundleResponse;\n readonly organization: PrincipalPolicyBundleResponse;\n}\n\n/**\n * Records verified recovery progress for an organization's founding policy\n * heads, so the device's first scoped recovery of them needs no history read.\n * It is bound to the provisioning scope: the database the bootstrap committed\n * to and the identity that signed it. Failure leaves the online path.\n */\nexport type ProvisionedPrincipalHistoryPrimer = (input: {\n /** The database client the provisioning bootstrap committed to. */\n readonly database: ExecSqlClientLike;\n /** Whether the identity that provisioned is still the active one. */\n readonly isCurrent: () => boolean;\n readonly organizationId: string;\n readonly policies: ProvisionedPrincipalPolicies;\n readonly userId: string;\n}) => Promise<void>;\n\n/**\n * Serves only the acknowledged initial heads, each complete in one page. The\n * ordinary verifier still checks every signature, the directory binding and\n * strict Admins rules before any progress is saved.\n */\nfunction provisionedPolicyPages(\n policies: ProvisionedPrincipalPolicies,\n): PrincipalPolicyPageReader {\n const bundles = [\n policies.organization,\n policies.adminGroup,\n policies.memberGroup,\n ];\n return {\n async *getPrincipalPolicyPages(principalType, principalId, options = {}) {\n const bundle = bundles.find(\n ({ currentState }) =>\n currentState.principalType === principalType &&\n currentState.principalId === principalId,\n );\n if (\n !bundle ||\n options.resume !== undefined ||\n (options.afterVersion ?? 0) !== 0 ||\n (options.stateHash !== undefined &&\n options.stateHash !== bundle.currentState.stateHash)\n )\n throw new Error(\n \"Provisioned principal history holds only the acknowledged initial heads\",\n );\n yield {\n ok: true,\n data: {\n ...structuredClone(bundle),\n historyPage: { afterVersion: 0, nextAfterVersion: null },\n },\n };\n },\n };\n}\n\nasync function primeProvisionedPrincipalHistory(input: {\n readonly execSql: ExecSql;\n readonly organizationId: string;\n readonly policies: ProvisionedPrincipalPolicies;\n readonly protection: PrincipalPolicyHistoryProgressOptions;\n readonly resolveTrustedUserIdentity: TrustedUserIdentityResolver;\n readonly stillCurrent: () => boolean;\n}): Promise<void> {\n const { recover } = createPrincipalCurrentRecoveryBatch();\n const options = {\n historyReader: provisionedPolicyPages(input.policies),\n execSql: input.execSql,\n offline: false,\n organizationId: input.organizationId,\n protection: input.protection,\n resolveTrustedUserIdentity: input.resolveTrustedUserIdentity,\n stillCurrent: input.stillCurrent,\n };\n // Scoped recovery of each group also completes the directory and strict\n // Admins stages; Members reuses them through the shared batch.\n for (const group of [input.policies.adminGroup, input.policies.memberGroup])\n await recover({\n ...options,\n reference: principalPolicyReferenceFromBundle(group),\n });\n}\n\n/** The runtime seams priming reads; none of them writes durable state. */\ninterface ProvisionedPrincipalHistoryRuntime {\n readonly bindLease: () => PrincipalHistoryProtectionLease | undefined;\n /** The runtime executor, only while `database` is its ready database. */\n readonly execSqlFor: (database: ExecSqlClientLike) => ExecSql | null;\n readonly logError: (message: string | Error, cause?: unknown) => void;\n /** Read-only: the identity the trust store already pins for a user. */\n readonly loadPinnedIdentity: (\n execSql: ExecSql,\n userId: string,\n ) => Promise<TrustedUserIdentity | null>;\n readonly signingFingerprint: () => string | null;\n}\n\n/**\n * Binds priming to the exact provisioning scope, captured before any await:\n * the bootstrap's database and runtime executor, the identity whose key signed\n * the founding states, and a protection lease for that identity. Every later\n * step, and every progress write inside its transaction, rechecks that scope,\n * so a switched identity or database aborts priming without a durable write.\n * Priming never writes a trust pin; it only reads the founder's existing one.\n */\nexport function createProvisionedPrincipalHistoryPrimer(\n runtime: ProvisionedPrincipalHistoryRuntime,\n): ProvisionedPrincipalHistoryPrimer {\n return async (input) => {\n try {\n await primeInProvisioningScope(runtime, input);\n } catch (error) {\n runtime.logError(\n \"Provisioned principal history was not primed; recovery reads it online\",\n error,\n );\n }\n };\n}\n\nasync function primeInProvisioningScope(\n runtime: ProvisionedPrincipalHistoryRuntime,\n input: Parameters<ProvisionedPrincipalHistoryPrimer>[0],\n): Promise<void> {\n // Everything below is captured synchronously, before the first await.\n const founderFingerprint =\n input.policies.organization.currentState.signerUserKeyFingerprint;\n const execSql = runtime.execSqlFor(input.database);\n const scopeCurrent = () =>\n execSql !== null &&\n input.isCurrent() &&\n runtime.execSqlFor(input.database) === execSql &&\n runtime.signingFingerprint() === founderFingerprint;\n const lease = scopeCurrent() ? runtime.bindLease() : undefined;\n if (!execSql || !lease) return;\n const resolveTrustedUserIdentity: TrustedUserIdentityResolver = async (\n signerUserId,\n ) => {\n if (signerUserId !== input.userId) return null;\n assertProjectionVerificationCurrent(scopeCurrent);\n const identity = await runtime.loadPinnedIdentity(execSql, input.userId);\n assertProjectionVerificationCurrent(scopeCurrent);\n return identity?.signingKeyFingerprint === founderFingerprint\n ? identity\n : null;\n };\n await getOrCreatePrincipalPolicyCoordinator(execSql).queueRecovery(\n input.organizationId,\n () =>\n lease(({ protection, stillCurrent }) =>\n primeProvisionedPrincipalHistory({\n execSql,\n organizationId: input.organizationId,\n policies: input.policies,\n protection,\n resolveTrustedUserIdentity,\n // Progress writes check this inside their transactions.\n stillCurrent: () => stillCurrent() && scopeCurrent(),\n }),\n ),\n );\n}\n"]}
@@ -1,18 +0,0 @@
1
- // Per database and organization, the number of applied organization hints in
2
- // this process. Operation-scoped principal recovery records the value before
3
- // producing a result and recovers again once it changes. Hints are lossy, so
4
- // this only shortens reuse; each operation still discovers the current head.
5
- const epochs = new WeakMap();
6
- export function principalRecoveryEpoch(execSql, organizationId) {
7
- return epochs.get(execSql)?.get(organizationId) ?? 0;
8
- }
9
- /** Evict recovered directory and Admins views for this organization. */
10
- export function invalidatePrincipalRecovery(execSql, organizationId) {
11
- let scoped = epochs.get(execSql);
12
- if (!scoped) {
13
- scoped = new Map();
14
- epochs.set(execSql, scoped);
15
- }
16
- scoped.set(organizationId, (scoped.get(organizationId) ?? 0) + 1);
17
- }
18
- //# sourceMappingURL=principalRecoveryEpoch.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"principalRecoveryEpoch.js","sourceRoot":"","sources":["principalRecoveryEpoch.ts"],"names":[],"mappings":"AAEA,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,6EAA6E;AAC7E,MAAM,MAAM,GAAG,IAAI,OAAO,EAAgC,CAAC;AAE3D,MAAM,UAAU,sBAAsB,CACpC,OAAgB,EAChB,cAAsB;IAEtB,OAAO,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;AACvD,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,2BAA2B,CACzC,OAAgB,EAChB,cAAsB;IAEtB,IAAI,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACnB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;AACpE,CAAC","sourcesContent":["import type { ExecSql } from \"../sqlite/sqlSchema\";\n\n// Per database and organization, the number of applied organization hints in\n// this process. Operation-scoped principal recovery records the value before\n// producing a result and recovers again once it changes. Hints are lossy, so\n// this only shortens reuse; each operation still discovers the current head.\nconst epochs = new WeakMap<ExecSql, Map<string, number>>();\n\nexport function principalRecoveryEpoch(\n execSql: ExecSql,\n organizationId: string,\n): number {\n return epochs.get(execSql)?.get(organizationId) ?? 0;\n}\n\n/** Evict recovered directory and Admins views for this organization. */\nexport function invalidatePrincipalRecovery(\n execSql: ExecSql,\n organizationId: string,\n): void {\n let scoped = epochs.get(execSql);\n if (!scoped) {\n scoped = new Map();\n epochs.set(execSql, scoped);\n }\n scoped.set(organizationId, (scoped.get(organizationId) ?? 0) + 1);\n}\n"]}
@@ -1,26 +0,0 @@
1
- import { principalMutationJournalScopeId, } from "../../data/principals/principalMutationJournal.js";
2
- import { resolveCanonicalExecSql, } from "../../data/sqlite/sqlSchema.js";
3
- const lanes = new WeakMap();
4
- /** An owned dispatch finishes before another caller can recover or abandon it. */
5
- export async function runPrincipalMutationJournalOperation(execSql, scope, run) {
6
- const executor = resolveCanonicalExecSql(execSql);
7
- const scopeId = await principalMutationJournalScopeId(scope);
8
- let active = lanes.get(executor);
9
- if (!active) {
10
- active = new Map();
11
- lanes.set(executor, active);
12
- }
13
- const pending = (active.get(scopeId) ?? Promise.resolve()).then(run);
14
- const settled = pending.then(() => { }, () => { });
15
- active.set(scopeId, settled);
16
- try {
17
- return await pending;
18
- }
19
- finally {
20
- if (active.get(scopeId) === settled)
21
- active.delete(scopeId);
22
- if (active.size === 0)
23
- lanes.delete(executor);
24
- }
25
- }
26
- //# sourceMappingURL=principalMutationJournalLane.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"principalMutationJournalLane.js","sourceRoot":"","sources":["principalMutationJournalLane.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,+BAA+B,GAChC,MAAM,gDAAgD,CAAC;AACxD,OAAO,EAEL,uBAAuB,GACxB,MAAM,6BAA6B,CAAC;AAErC,MAAM,KAAK,GAAG,IAAI,OAAO,EAAuC,CAAC;AAEjE,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,oCAAoC,CACxD,OAAgB,EAChB,KAAoC,EACpC,GAAqB;IAErB,MAAM,QAAQ,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,MAAM,+BAA+B,CAAC,KAAK,CAAC,CAAC;IAC7D,IAAI,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACnB,KAAK,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAC1B,GAAG,EAAE,GAAE,CAAC,EACR,GAAG,EAAE,GAAE,CAAC,CACT,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAC;IACvB,CAAC;YAAS,CAAC;QACT,IAAI,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,OAAO;YAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,MAAM,CAAC,IAAI,KAAK,CAAC;YAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAChD,CAAC;AACH,CAAC","sourcesContent":["import {\n type PrincipalMutationJournalScope,\n principalMutationJournalScopeId,\n} from \"../../data/principals/principalMutationJournal\";\nimport {\n type ExecSql,\n resolveCanonicalExecSql,\n} from \"../../data/sqlite/sqlSchema\";\n\nconst lanes = new WeakMap<ExecSql, Map<string, Promise<void>>>();\n\n/** An owned dispatch finishes before another caller can recover or abandon it. */\nexport async function runPrincipalMutationJournalOperation<T>(\n execSql: ExecSql,\n scope: PrincipalMutationJournalScope,\n run: () => Promise<T>,\n): Promise<T> {\n const executor = resolveCanonicalExecSql(execSql);\n const scopeId = await principalMutationJournalScopeId(scope);\n let active = lanes.get(executor);\n if (!active) {\n active = new Map();\n lanes.set(executor, active);\n }\n const pending = (active.get(scopeId) ?? Promise.resolve()).then(run);\n const settled = pending.then(\n () => {},\n () => {},\n );\n active.set(scopeId, settled);\n try {\n return await pending;\n } finally {\n if (active.get(scopeId) === settled) active.delete(scopeId);\n if (active.size === 0) lanes.delete(executor);\n }\n}\n"]}
@@ -1,22 +0,0 @@
1
- // Directory and Admins stages are shared by every reference in an organization.
2
- // Coordinate runtime callers across warmer instances; different databases and
3
- // organizations keep independent queues. Completed queues retain no work.
4
- // Queued work must not re-enter this queue for the same organization.
5
- const queues = new WeakMap();
6
- export function queuePrincipalRecovery(execSql, organizationId, work) {
7
- let scoped = queues.get(execSql);
8
- if (!scoped) {
9
- scoped = new Map();
10
- queues.set(execSql, scoped);
11
- }
12
- const operation = (scoped.get(organizationId) ?? Promise.resolve()).then(work);
13
- const settled = operation.then(() => undefined, () => undefined);
14
- scoped.set(organizationId, settled);
15
- const owned = scoped;
16
- void settled.then(() => {
17
- if (owned.get(organizationId) === settled)
18
- owned.delete(organizationId);
19
- });
20
- return operation;
21
- }
22
- //# sourceMappingURL=principalRecoveryQueue.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"principalRecoveryQueue.js","sourceRoot":"","sources":["principalRecoveryQueue.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,8EAA8E;AAC9E,0EAA0E;AAC1E,sEAAsE;AACtE,MAAM,MAAM,GAAG,IAAI,OAAO,EAAuC,CAAC;AAElE,MAAM,UAAU,sBAAsB,CACpC,OAAgB,EAChB,cAAsB,EACtB,IAAsB;IAEtB,IAAI,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,GAAG,IAAI,GAAG,EAAE,CAAC;QACnB,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9B,CAAC;IACD,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CACtE,IAAI,CACL,CAAC;IACF,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAC5B,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;IACF,MAAM,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACpC,MAAM,KAAK,GAAG,MAAM,CAAC;IACrB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;QACrB,IAAI,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,OAAO;YAAE,KAAK,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IACH,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import type { ExecSql } from \"../../data/sqlite/sqlSchema\";\n\n// Directory and Admins stages are shared by every reference in an organization.\n// Coordinate runtime callers across warmer instances; different databases and\n// organizations keep independent queues. Completed queues retain no work.\n// Queued work must not re-enter this queue for the same organization.\nconst queues = new WeakMap<ExecSql, Map<string, Promise<void>>>();\n\nexport function queuePrincipalRecovery<T>(\n execSql: ExecSql,\n organizationId: string,\n work: () => Promise<T>,\n): Promise<T> {\n let scoped = queues.get(execSql);\n if (!scoped) {\n scoped = new Map();\n queues.set(execSql, scoped);\n }\n const operation = (scoped.get(organizationId) ?? Promise.resolve()).then(\n work,\n );\n const settled = operation.then(\n () => undefined,\n () => undefined,\n );\n scoped.set(organizationId, settled);\n const owned = scoped;\n void settled.then(() => {\n if (owned.get(organizationId) === settled) owned.delete(organizationId);\n });\n return operation;\n}\n"]}