@opengeni/db 0.36.1 → 3.1.0-canary.0

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 (274) hide show
  1. package/dist/attached-browser-devices.d.ts +7 -0
  2. package/dist/automations.d.ts +154 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-integrations.d.ts +13 -0
  5. package/dist/child-lifecycle-notices.d.ts +68 -0
  6. package/dist/{chunk-GYO7ED2Q.js → chunk-EULAN2NP.js} +2 -2
  7. package/dist/{chunk-VTPRIXKG.js → chunk-NJVZPWXY.js} +3 -3
  8. package/dist/{chunk-HSOWSYOX.js → chunk-NMUBV7Z5.js} +19 -11
  9. package/dist/chunk-NMUBV7Z5.js.map +1 -0
  10. package/dist/chunk-OF5OBB76.js +4457 -0
  11. package/dist/chunk-OF5OBB76.js.map +1 -0
  12. package/dist/{chunk-5KTIEDX7.js → chunk-PR2WMLOQ.js} +7714 -4443
  13. package/dist/chunk-PR2WMLOQ.js.map +1 -0
  14. package/dist/{chunk-Z7VYMNPZ.js → chunk-SCPUJS53.js} +41 -4
  15. package/dist/chunk-SCPUJS53.js.map +1 -0
  16. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  17. package/dist/chunk-SIBYBA2D.js.map +1 -0
  18. package/dist/chunk-TIKF3KTI.js +383 -0
  19. package/dist/chunk-TIKF3KTI.js.map +1 -0
  20. package/dist/chunk-ZZ5LJEFX.js +516 -0
  21. package/dist/chunk-ZZ5LJEFX.js.map +1 -0
  22. package/dist/codemode-operations.d.ts +1 -6
  23. package/dist/company-brain-context-selection-schema.d.ts +705 -0
  24. package/dist/company-brain-context-selection.d.ts +94 -0
  25. package/dist/company-brain-governed-writes.d.ts +24 -0
  26. package/dist/company-brain.d.ts +55 -0
  27. package/dist/company-profile-agent-admin.d.ts +15 -0
  28. package/dist/company-profile-schema.d.ts +619 -2
  29. package/dist/company-profile.d.ts +9 -4
  30. package/dist/connection-authority.d.ts +95 -0
  31. package/dist/connection-scopes.d.ts +8 -0
  32. package/dist/connection-tenancy-backfill.d.ts +41 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +19 -0
  35. package/dist/editable-artifact-durable-export.d.ts +10 -0
  36. package/dist/editable-artifact-durable-export.js +18 -3
  37. package/dist/editable-artifact-durable-export.js.map +1 -1
  38. package/dist/editable-artifacts.d.ts +2 -2
  39. package/dist/editable-artifacts.js +3 -3
  40. package/dist/governed-learning-activation-schema.d.ts +984 -0
  41. package/dist/governed-learning-activation.d.ts +98 -0
  42. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  43. package/dist/governed-learning-evaluator.d.ts +28 -0
  44. package/dist/index.d.ts +1633 -87
  45. package/dist/index.js +22004 -8109
  46. package/dist/index.js.map +1 -1
  47. package/dist/insights.d.ts +18 -0
  48. package/dist/integration-bindings.d.ts +1 -0
  49. package/dist/integration-facets.d.ts +30 -0
  50. package/dist/interaction-placement-loss.d.ts +22 -0
  51. package/dist/knowledge-source-sync-schema.d.ts +730 -0
  52. package/dist/knowledge-source-sync.d.ts +60 -0
  53. package/dist/managed-human-provisioning.d.ts +25 -0
  54. package/dist/memory-domain.d.ts +3 -3
  55. package/dist/migrate.d.ts +11 -1
  56. package/dist/migrate.js +1 -1
  57. package/dist/new-session-drafts.d.ts +43 -5
  58. package/dist/organization-membership-backfill.d.ts +174 -0
  59. package/dist/organization-membership-lifecycle.d.ts +162 -0
  60. package/dist/pr-review.d.ts +107 -0
  61. package/dist/preference-registry-schema.d.ts +2 -0
  62. package/dist/preference-registry.d.ts +2 -0
  63. package/dist/provision-roles.js +1 -1
  64. package/dist/runtime-posture.d.ts +23 -9
  65. package/dist/schema.d.ts +24925 -13697
  66. package/dist/schema.js +103 -1
  67. package/dist/scoped-knowledge-schema.d.ts +52 -1
  68. package/dist/session-background-commands.d.ts +125 -0
  69. package/dist/session-background-commands.js +34 -0
  70. package/dist/session-background-commands.js.map +1 -0
  71. package/dist/session-control.d.ts +156 -3
  72. package/dist/session-goal-pacing.d.ts +68 -0
  73. package/dist/session-queue-commands.d.ts +42 -5
  74. package/dist/session-realtime-ledger.d.ts +3 -0
  75. package/dist/session-tenancy.d.ts +71 -1
  76. package/dist/session-tenancy.js +25 -3
  77. package/dist/session-tool-call-settlement.d.ts +9 -4
  78. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  79. package/dist/slack-routing.d.ts +153 -0
  80. package/dist/slack-task-policy-schema.d.ts +34 -0
  81. package/dist/slack-task-policy.d.ts +8 -0
  82. package/dist/slack-user-link-access.d.ts +1 -0
  83. package/dist/task-notes-schema.d.ts +594 -0
  84. package/dist/task-notes.d.ts +39 -0
  85. package/dist/turn-initiator.d.ts +2 -0
  86. package/dist/user-resource-authority.d.ts +109 -0
  87. package/dist/video-generation.js +3 -3
  88. package/dist/workspace-authority.d.ts +81 -0
  89. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  90. package/dist/workspace-instruction-policies.d.ts +62 -0
  91. package/dist/workspace-learning-policy.d.ts +1 -0
  92. package/dist/workspace-membership-permissions.d.ts +2 -0
  93. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  94. package/drizzle/0240_model_context_user_messages.sql +204 -0
  95. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  96. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  97. package/drizzle/0243_google_drive_object_acl_authority.sql +554 -0
  98. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  99. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  100. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  101. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  102. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  103. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  104. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  105. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  106. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  107. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  108. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  109. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  110. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  111. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  112. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  113. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  114. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  115. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  116. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  117. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  118. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  119. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  120. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  121. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  122. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  123. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  124. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  125. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  126. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  127. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  128. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  129. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  130. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  131. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  132. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  133. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  134. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  135. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  136. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  137. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  138. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  139. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  140. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  141. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  142. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  143. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  144. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  145. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  146. package/drizzle/0294_preference_activation_authority.sql +237 -0
  147. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  148. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  149. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  150. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  151. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  152. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  153. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  154. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  155. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  156. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  157. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  158. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  159. package/drizzle/0307_session_attention_state.sql +51 -0
  160. package/drizzle/0308_session_archives.sql +35 -0
  161. package/drizzle/0309_channel_project_pins.sql +6 -0
  162. package/drizzle/0310_channel_project_order.sql +27 -0
  163. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  164. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  165. package/drizzle/0313_private_child_session_authority.sql +365 -0
  166. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  167. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  168. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  169. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  170. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  171. package/drizzle/0319_event_triggered_automations.sql +254 -0
  172. package/drizzle/0320_pr_review_pack.sql +113 -0
  173. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  174. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  175. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  176. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  177. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  178. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  179. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  180. package/drizzle/0328_session_background_commands.sql +590 -0
  181. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  182. package/drizzle/0330_api_key_descriptions.sql +20 -0
  183. package/drizzle/0331_managed_organization_creation.sql +163 -0
  184. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  185. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  186. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  187. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  188. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  189. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  190. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  191. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  192. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  193. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  194. package/package.json +10 -6
  195. package/src/attached-browser-devices.ts +233 -5
  196. package/src/automations.ts +911 -0
  197. package/src/browser-sessions.ts +10 -0
  198. package/src/capability-integrations.ts +175 -1
  199. package/src/child-lifecycle-notices.ts +241 -0
  200. package/src/codemode-operations.ts +1 -35
  201. package/src/company-brain-context-selection-schema.ts +100 -0
  202. package/src/company-brain-context-selection.ts +359 -0
  203. package/src/company-brain-governed-writes.ts +1170 -0
  204. package/src/company-brain.ts +309 -0
  205. package/src/company-profile-agent-admin.ts +225 -0
  206. package/src/company-profile-schema.ts +121 -1
  207. package/src/company-profile.ts +21 -0
  208. package/src/computer-sessions.ts +10 -1
  209. package/src/connection-authority.ts +290 -0
  210. package/src/connection-scopes.ts +20 -0
  211. package/src/connection-tenancy-backfill.ts +81 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +75 -2
  214. package/src/editable-artifact-durable-export.ts +27 -0
  215. package/src/editable-artifacts-schema.ts +10 -5
  216. package/src/editable-artifacts.ts +21 -11
  217. package/src/governed-learning-activation-schema.ts +121 -0
  218. package/src/governed-learning-activation.ts +588 -0
  219. package/src/governed-learning-evaluator-schema.ts +72 -0
  220. package/src/governed-learning-evaluator.ts +217 -0
  221. package/src/index.ts +12001 -1699
  222. package/src/insights.ts +107 -0
  223. package/src/integration-bindings.ts +88 -6
  224. package/src/integration-facets.ts +287 -84
  225. package/src/interaction-placement-loss.ts +234 -0
  226. package/src/knowledge-source-sync-schema.ts +83 -0
  227. package/src/knowledge-source-sync.ts +530 -0
  228. package/src/managed-human-provisioning.ts +116 -0
  229. package/src/memory-domain.ts +27 -32
  230. package/src/migrate.ts +77 -1
  231. package/src/new-session-drafts.ts +229 -35
  232. package/src/organization-membership-backfill.ts +735 -0
  233. package/src/organization-membership-lifecycle.ts +985 -0
  234. package/src/pr-review.ts +890 -0
  235. package/src/preference-registry.ts +31 -1
  236. package/src/provision-roles.ts +931 -24
  237. package/src/runtime-posture-cli.ts +2 -0
  238. package/src/runtime-posture.ts +1315 -52
  239. package/src/schema.ts +3336 -438
  240. package/src/scoped-knowledge-schema.ts +16 -8
  241. package/src/scoped-knowledge.ts +1 -1
  242. package/src/session-background-commands.ts +759 -0
  243. package/src/session-control.ts +640 -74
  244. package/src/session-goal-pacing.ts +126 -0
  245. package/src/session-queue-commands.ts +450 -98
  246. package/src/session-realtime-context.ts +19 -3
  247. package/src/session-realtime-ledger.ts +75 -12
  248. package/src/session-realtime-mirror.ts +1 -1
  249. package/src/session-tenancy.ts +403 -49
  250. package/src/session-tool-call-settlement.ts +38 -29
  251. package/src/slack-routing-personal-workspace.ts +67 -0
  252. package/src/slack-routing.ts +469 -0
  253. package/src/slack-task-policy-schema.ts +5 -0
  254. package/src/slack-task-policy.ts +18 -0
  255. package/src/slack-user-link-access.ts +17 -1
  256. package/src/task-notes-schema.ts +74 -0
  257. package/src/task-notes.ts +124 -2
  258. package/src/turn-initiator.ts +8 -0
  259. package/src/user-resource-authority.ts +377 -0
  260. package/src/workspace-authority.ts +160 -0
  261. package/src/workspace-instruction-policies-schema.ts +53 -1
  262. package/src/workspace-instruction-policies.ts +528 -77
  263. package/src/workspace-learning-policy.ts +21 -5
  264. package/src/workspace-membership-permissions.ts +9 -0
  265. package/dist/chunk-5KTIEDX7.js.map +0 -1
  266. package/dist/chunk-FPVAD4CO.js +0 -117
  267. package/dist/chunk-FPVAD4CO.js.map +0 -1
  268. package/dist/chunk-HSOWSYOX.js.map +0 -1
  269. package/dist/chunk-SK7YYHBI.js.map +0 -1
  270. package/dist/chunk-VKLB2ZGI.js +0 -2389
  271. package/dist/chunk-VKLB2ZGI.js.map +0 -1
  272. package/dist/chunk-Z7VYMNPZ.js.map +0 -1
  273. /package/dist/{chunk-GYO7ED2Q.js.map → chunk-EULAN2NP.js.map} +0 -0
  274. /package/dist/{chunk-VTPRIXKG.js.map → chunk-NJVZPWXY.js.map} +0 -0
@@ -4,6 +4,7 @@ import {
4
4
  KnowledgeSourceSyncRunSummary,
5
5
  type ScheduledTask,
6
6
  } from "@opengeni/contracts";
7
+ import { createHash } from "node:crypto";
7
8
  import { and, eq, inArray, sql } from "drizzle-orm";
8
9
  import type { Database } from "./database";
9
10
  import { setSubjectRlsContext, withRlsContext } from "./database";
@@ -43,6 +44,40 @@ export type KnowledgeSourceSyncObservationFence = {
43
44
  observations: KnowledgeSourceSyncObservationFloor[];
44
45
  };
45
46
 
47
+ export type GoogleDriveObjectAclPrincipal = {
48
+ type: "user" | "group" | "domain" | "anyone";
49
+ permissionId?: string | null;
50
+ emailAddress?: string | null;
51
+ domain?: string | null;
52
+ role: "owner" | "organizer" | "fileOrganizer" | "writer" | "commenter" | "reader";
53
+ inherited?: boolean;
54
+ allowFileDiscovery?: boolean | null;
55
+ expirationTime?: string | null;
56
+ };
57
+
58
+ export type GoogleDriveObjectAclEvidenceInput = {
59
+ accountId: string;
60
+ workspaceId: string;
61
+ obligationId: string;
62
+ connectionId: string;
63
+ connectionVersion: number;
64
+ sourceGooglePermissionId: string;
65
+ sourceSyncGeneration: number;
66
+ sourceConfigGeneration: number;
67
+ sourceLifecycleGeneration: number;
68
+ objectLifecycleGeneration: number;
69
+ objectVersionGeneration: number;
70
+ providerRevision: string | null;
71
+ driveId: string | null;
72
+ aclRevision: string;
73
+ eligibility: "eligible" | "denied";
74
+ observedAt: string;
75
+ expiresAt: string;
76
+ citationLocator: Record<string, unknown>;
77
+ operationId: string;
78
+ principals: GoogleDriveObjectAclPrincipal[];
79
+ };
80
+
46
81
  export type KnowledgeSourceSyncObjectObservationResult = KnowledgeSourceSyncObservationFloor & {
47
82
  disposition: "accepted" | "replayed" | "stale" | "conflict";
48
83
  currentVersion: {
@@ -1614,6 +1649,501 @@ export async function recordKnowledgeSourceSyncAclEvidence(
1614
1649
  );
1615
1650
  }
1616
1651
 
1652
+ /**
1653
+ * Persist one append-only Google Drive object ACL observation and atomically
1654
+ * make it the current authorization pointer for the exact index obligation.
1655
+ * Raw provider principals never enter storage: only domain-separated hashes
1656
+ * are retained. Denial deletes chunks in the same transaction so stale vector
1657
+ * or keyword rows cannot survive permission loss.
1658
+ */
1659
+ export async function recordGoogleDriveObjectAclEvidence(
1660
+ db: Database,
1661
+ input: GoogleDriveObjectAclEvidenceInput,
1662
+ ): Promise<{ evidenceId: string; aclHash: string }> {
1663
+ const observedAt = exactDate(input.observedAt, "Google Drive ACL observedAt");
1664
+ const expiresAt = exactDate(input.expiresAt, "Google Drive ACL expiresAt");
1665
+ if (expiresAt.getTime() <= observedAt.getTime()) {
1666
+ throw new Error("Google Drive ACL evidence must expire after it was observed");
1667
+ }
1668
+ if (input.principals.length > 1_000) {
1669
+ throw new Error("Google Drive ACL evidence exceeds 1000 principals");
1670
+ }
1671
+ const principals = input.principals.map(normalizeGoogleDriveAclPrincipal);
1672
+ const canonicalAcl = principals
1673
+ .map((principal) => JSON.stringify(principal))
1674
+ .sort((left, right) => left.localeCompare(right));
1675
+ const aclHash = sha256Hex(JSON.stringify(canonicalAcl));
1676
+ const sourcePrincipalHash = principalHash("permission", input.sourceGooglePermissionId);
1677
+ const inputHash = sha256Hex(
1678
+ JSON.stringify({
1679
+ version: 1,
1680
+ obligationId: input.obligationId,
1681
+ connectionId: input.connectionId,
1682
+ connectionVersion: input.connectionVersion,
1683
+ sourcePrincipalHash,
1684
+ sourceSyncGeneration: input.sourceSyncGeneration,
1685
+ sourceConfigGeneration: input.sourceConfigGeneration,
1686
+ sourceLifecycleGeneration: input.sourceLifecycleGeneration,
1687
+ objectLifecycleGeneration: input.objectLifecycleGeneration,
1688
+ objectVersionGeneration: input.objectVersionGeneration,
1689
+ providerRevision: input.providerRevision,
1690
+ driveId: input.driveId,
1691
+ aclRevision: input.aclRevision,
1692
+ aclHash,
1693
+ eligibility: input.eligibility,
1694
+ observedAt: observedAt.toISOString(),
1695
+ expiresAt: expiresAt.toISOString(),
1696
+ citationLocator: input.citationLocator,
1697
+ }),
1698
+ );
1699
+
1700
+ return await withRlsContext(
1701
+ db,
1702
+ { accountId: input.accountId, workspaceId: input.workspaceId },
1703
+ async (scopedDb) =>
1704
+ await scopedDb.transaction(async (tx) => {
1705
+ const [observedObligation] = await tx
1706
+ .select()
1707
+ .from(schema.knowledgeSourceSyncIndexObligations)
1708
+ .where(eq(schema.knowledgeSourceSyncIndexObligations.id, input.obligationId))
1709
+ .limit(1);
1710
+ if (!observedObligation) {
1711
+ throw new Error("Google Drive ACL evidence obligation was not found");
1712
+ }
1713
+ await setSubjectRlsContext(tx, observedObligation.initiatingSubjectId);
1714
+ const [state] = await tx
1715
+ .select()
1716
+ .from(schema.knowledgeSourceSyncStates)
1717
+ .where(eq(schema.knowledgeSourceSyncStates.sourceId, observedObligation.sourceId))
1718
+ .for("update")
1719
+ .limit(1);
1720
+ if (!state) throw new Error("Google Drive sync authorization is no longer active");
1721
+ await tx.execute(sql`
1722
+ SELECT knowledge_source_sync_lock_authority(
1723
+ ${input.accountId}::uuid,
1724
+ ${observedObligation.sourceId}::uuid,
1725
+ ${observedObligation.knowledgeSourceObjectId}::uuid
1726
+ )
1727
+ `);
1728
+ const [[source], [object], [version], [obligation], [connection]] = await Promise.all([
1729
+ tx
1730
+ .select()
1731
+ .from(schema.knowledgeSources)
1732
+ .where(eq(schema.knowledgeSources.id, observedObligation.sourceId))
1733
+ .limit(1),
1734
+ tx
1735
+ .select()
1736
+ .from(schema.knowledgeSourceObjects)
1737
+ .where(eq(schema.knowledgeSourceObjects.id, observedObligation.knowledgeSourceObjectId))
1738
+ .limit(1),
1739
+ tx
1740
+ .select()
1741
+ .from(schema.knowledgeDocumentVersions)
1742
+ .where(
1743
+ eq(
1744
+ schema.knowledgeDocumentVersions.id,
1745
+ observedObligation.knowledgeDocumentVersionId,
1746
+ ),
1747
+ )
1748
+ .limit(1),
1749
+ tx
1750
+ .select()
1751
+ .from(schema.knowledgeSourceSyncIndexObligations)
1752
+ .where(eq(schema.knowledgeSourceSyncIndexObligations.id, input.obligationId))
1753
+ .for("update")
1754
+ .limit(1),
1755
+ tx
1756
+ .select()
1757
+ .from(schema.connections)
1758
+ .where(eq(schema.connections.id, input.connectionId))
1759
+ .limit(1),
1760
+ ]);
1761
+ const [provider] = source
1762
+ ? await tx
1763
+ .select()
1764
+ .from(schema.knowledgeProviders)
1765
+ .where(eq(schema.knowledgeProviders.id, source.providerId))
1766
+ .limit(1)
1767
+ : [];
1768
+ const [document] = obligation
1769
+ ? await tx
1770
+ .select()
1771
+ .from(schema.documents)
1772
+ .where(eq(schema.documents.id, obligation.documentId))
1773
+ .for("update")
1774
+ .limit(1)
1775
+ : [];
1776
+ const metadata = connection?.metadata ?? {};
1777
+ const connectionPermissionId = cleanBoundedString(metadata.googlePermissionId, 256);
1778
+ const connectionLifecycle =
1779
+ metadata.lifecycle && typeof metadata.lifecycle === "object"
1780
+ ? cleanBoundedString((metadata.lifecycle as Record<string, unknown>).state, 64)
1781
+ : null;
1782
+ const hasDriveReadScope =
1783
+ Array.isArray(connection?.grantedScopes) &&
1784
+ connection.grantedScopes.some(
1785
+ (scope) =>
1786
+ scope === "https://www.googleapis.com/auth/drive" ||
1787
+ scope === "https://www.googleapis.com/auth/drive.readonly",
1788
+ );
1789
+ if (
1790
+ !obligation ||
1791
+ !source ||
1792
+ !object ||
1793
+ !version ||
1794
+ !provider ||
1795
+ !document ||
1796
+ !connection ||
1797
+ provider.providerKey !== "google-drive" ||
1798
+ provider.lifecycleState !== "active" ||
1799
+ state.connectionId !== input.connectionId ||
1800
+ state.connectionProviderDomain !== "googleapis.com" ||
1801
+ state.connectionKind !== "oauth2" ||
1802
+ state.connectionOwnerSubjectId !== connection.subjectId ||
1803
+ state.reconnectRequired ||
1804
+ connection.accountId !== input.accountId ||
1805
+ connection.subjectId !== observedObligation.initiatingSubjectId ||
1806
+ connection.providerDomain !== "googleapis.com" ||
1807
+ connection.kind !== "oauth2" ||
1808
+ connection.status !== "active" ||
1809
+ connection.version !== input.connectionVersion ||
1810
+ connection.version < state.connectionVersion ||
1811
+ metadata.accessMode !== "readonly" ||
1812
+ (connectionLifecycle !== null && connectionLifecycle !== "active") ||
1813
+ !hasDriveReadScope ||
1814
+ !connectionPermissionId ||
1815
+ principalHash("permission", connectionPermissionId) !== sourcePrincipalHash ||
1816
+ input.sourceSyncGeneration !== obligation.sourceSyncGeneration ||
1817
+ state.sourceSyncGeneration < obligation.sourceSyncGeneration ||
1818
+ state.sourceConfigGeneration !== input.sourceConfigGeneration ||
1819
+ state.sourceConfigGeneration !== obligation.sourceConfigGeneration ||
1820
+ state.sourceLifecycleGeneration !== input.sourceLifecycleGeneration ||
1821
+ state.sourceLifecycleGeneration !== obligation.sourceLifecycleGeneration ||
1822
+ source.lifecycleState !== "active" ||
1823
+ source.syncGeneration < obligation.sourceSyncGeneration ||
1824
+ source.lifecycleGeneration !== obligation.sourceLifecycleGeneration ||
1825
+ source.currentAclGeneration !== version.aclGeneration ||
1826
+ object.sourceId !== obligation.sourceId ||
1827
+ object.externalObjectId !== obligation.externalObjectId ||
1828
+ object.lifecycleState !== "active" ||
1829
+ object.lifecycleGeneration !== obligation.objectLifecycleGeneration ||
1830
+ object.versionGeneration !== obligation.objectVersionGeneration ||
1831
+ object.currentVersionId !== obligation.knowledgeDocumentVersionId ||
1832
+ version.sourceId !== obligation.sourceId ||
1833
+ version.objectId !== obligation.knowledgeSourceObjectId ||
1834
+ version.versionGeneration !== obligation.objectVersionGeneration ||
1835
+ version.documentId !== obligation.documentId ||
1836
+ version.fileId !== document.fileId ||
1837
+ document.accountId !== input.accountId ||
1838
+ document.workspaceId !== input.workspaceId ||
1839
+ document.sourceExternalId !== obligation.externalObjectId ||
1840
+ !document.knowledgeSourceIdentity ||
1841
+ obligation.sourceLifecycleGeneration !== input.sourceLifecycleGeneration ||
1842
+ obligation.objectLifecycleGeneration !== input.objectLifecycleGeneration ||
1843
+ obligation.objectVersionGeneration !== input.objectVersionGeneration
1844
+ ) {
1845
+ throw new Error("Google Drive ACL evidence authority changed");
1846
+ }
1847
+ if (input.eligibility === "eligible" && obligation.status !== "indexed") {
1848
+ throw new Error("Google Drive ACL eligibility requires a completed index obligation");
1849
+ }
1850
+
1851
+ const evidenceValues = {
1852
+ accountId: input.accountId,
1853
+ workspaceId: input.workspaceId,
1854
+ sourceId: obligation.sourceId,
1855
+ knowledgeSourceObjectId: obligation.knowledgeSourceObjectId,
1856
+ knowledgeDocumentVersionId: obligation.knowledgeDocumentVersionId,
1857
+ documentId: obligation.documentId,
1858
+ fileId: document.fileId,
1859
+ indexObligationId: obligation.id,
1860
+ connectionId: input.connectionId,
1861
+ connectionVersion: input.connectionVersion,
1862
+ sourcePrincipalHash,
1863
+ sourceSyncGeneration: input.sourceSyncGeneration,
1864
+ sourceConfigGeneration: input.sourceConfigGeneration,
1865
+ sourceLifecycleGeneration: input.sourceLifecycleGeneration,
1866
+ objectLifecycleGeneration: input.objectLifecycleGeneration,
1867
+ objectVersionGeneration: input.objectVersionGeneration,
1868
+ providerRevision: input.providerRevision,
1869
+ driveId: cleanBoundedString(input.driveId, 1024),
1870
+ aclRevision: exactBoundedString(input.aclRevision, 1024, "Google Drive ACL revision"),
1871
+ aclHash,
1872
+ eligibility: input.eligibility,
1873
+ observedAt,
1874
+ expiresAt,
1875
+ citationLocator: input.citationLocator,
1876
+ operationId: exactBoundedString(input.operationId, 256, "Google Drive ACL operation id"),
1877
+ inputHash,
1878
+ };
1879
+ const [inserted] = await tx
1880
+ .insert(schema.googleDriveObjectAclEvidence)
1881
+ .values(evidenceValues)
1882
+ .onConflictDoNothing({
1883
+ target: [
1884
+ schema.googleDriveObjectAclEvidence.indexObligationId,
1885
+ schema.googleDriveObjectAclEvidence.operationId,
1886
+ ],
1887
+ })
1888
+ .returning();
1889
+ const [evidence] = inserted
1890
+ ? [inserted]
1891
+ : await tx
1892
+ .select()
1893
+ .from(schema.googleDriveObjectAclEvidence)
1894
+ .where(
1895
+ and(
1896
+ eq(schema.googleDriveObjectAclEvidence.indexObligationId, obligation.id),
1897
+ eq(schema.googleDriveObjectAclEvidence.operationId, evidenceValues.operationId),
1898
+ ),
1899
+ )
1900
+ .limit(1);
1901
+ if (!evidence || evidence.inputHash !== inputHash) {
1902
+ throw new Error("Google Drive ACL evidence operation conflicted");
1903
+ }
1904
+ if (inserted && principals.length > 0) {
1905
+ await tx.insert(schema.googleDriveObjectAclPrincipals).values(
1906
+ principals.map((principal, ordinal) => ({
1907
+ evidenceId: evidence.id,
1908
+ ordinal,
1909
+ accountId: input.accountId,
1910
+ workspaceId: input.workspaceId,
1911
+ ...principal,
1912
+ })),
1913
+ );
1914
+ }
1915
+ const now = new Date();
1916
+ await tx
1917
+ .update(schema.knowledgeSourceSyncIndexObligations)
1918
+ .set({
1919
+ googleDriveAclEvidenceId: evidence.id,
1920
+ aclEligibility: input.eligibility,
1921
+ updatedAt: now,
1922
+ })
1923
+ .where(eq(schema.knowledgeSourceSyncIndexObligations.id, obligation.id));
1924
+ await tx
1925
+ .update(schema.knowledgeSourceSyncItemOutcomes)
1926
+ .set({
1927
+ aclEligibility: input.eligibility,
1928
+ aclEvidence: {
1929
+ version: 1,
1930
+ provider: "google_drive",
1931
+ evidenceId: evidence.id,
1932
+ aclRevision: evidence.aclRevision,
1933
+ observedAt: evidence.observedAt.toISOString(),
1934
+ expiresAt: evidence.expiresAt.toISOString(),
1935
+ },
1936
+ })
1937
+ .where(eq(schema.knowledgeSourceSyncItemOutcomes.indexObligationId, obligation.id));
1938
+ const [updatedDocument] = await tx
1939
+ .update(schema.documents)
1940
+ .set({
1941
+ agentAccess: input.eligibility === "eligible",
1942
+ ...(input.eligibility === "denied" ? { chunkCount: 0 } : {}),
1943
+ updatedAt: now,
1944
+ })
1945
+ .where(eq(schema.documents.id, obligation.documentId))
1946
+ .returning({ id: schema.documents.id });
1947
+ if (!updatedDocument) {
1948
+ throw new Error("Google Drive ACL evidence document authority changed");
1949
+ }
1950
+ if (input.eligibility === "denied") {
1951
+ await tx
1952
+ .delete(schema.documentChunks)
1953
+ .where(eq(schema.documentChunks.documentId, obligation.documentId));
1954
+ }
1955
+ return { evidenceId: evidence.id, aclHash };
1956
+ }),
1957
+ );
1958
+ }
1959
+
1960
+ /**
1961
+ * Clear the current Drive authorization pointer before provider I/O without
1962
+ * destroying an otherwise valid index. All read surfaces immediately deny
1963
+ * because agent_access is false and the current evidence pointer is null. A
1964
+ * successful refresh can restore access without re-embedding; a final denied
1965
+ * observation uses recordGoogleDriveObjectAclEvidence and deletes chunks.
1966
+ */
1967
+ export async function beginGoogleDriveObjectAclRefresh(
1968
+ db: Database,
1969
+ input: {
1970
+ accountId: string;
1971
+ workspaceId: string;
1972
+ obligationId: string;
1973
+ sourceSyncGeneration: number;
1974
+ sourceConfigGeneration: number;
1975
+ sourceLifecycleGeneration: number;
1976
+ objectLifecycleGeneration: number;
1977
+ objectVersionGeneration: number;
1978
+ },
1979
+ ): Promise<void> {
1980
+ await withRlsContext(
1981
+ db,
1982
+ { accountId: input.accountId, workspaceId: input.workspaceId },
1983
+ async (scopedDb) =>
1984
+ await scopedDb.transaction(async (tx) => {
1985
+ const [observed] = await tx
1986
+ .select()
1987
+ .from(schema.knowledgeSourceSyncIndexObligations)
1988
+ .where(eq(schema.knowledgeSourceSyncIndexObligations.id, input.obligationId))
1989
+ .limit(1);
1990
+ if (!observed) throw new Error("Google Drive ACL refresh obligation was not found");
1991
+ await setSubjectRlsContext(tx, observed.initiatingSubjectId);
1992
+ const [state] = await tx
1993
+ .select()
1994
+ .from(schema.knowledgeSourceSyncStates)
1995
+ .where(eq(schema.knowledgeSourceSyncStates.sourceId, observed.sourceId))
1996
+ .for("update")
1997
+ .limit(1);
1998
+ if (!state) throw new Error("Google Drive sync authorization is no longer active");
1999
+ await tx.execute(sql`
2000
+ SELECT knowledge_source_sync_lock_authority(
2001
+ ${input.accountId}::uuid,
2002
+ ${observed.sourceId}::uuid,
2003
+ ${observed.knowledgeSourceObjectId}::uuid
2004
+ )
2005
+ `);
2006
+ const [[source], [object], [version], [obligation]] = await Promise.all([
2007
+ tx
2008
+ .select()
2009
+ .from(schema.knowledgeSources)
2010
+ .where(eq(schema.knowledgeSources.id, observed.sourceId))
2011
+ .limit(1),
2012
+ tx
2013
+ .select()
2014
+ .from(schema.knowledgeSourceObjects)
2015
+ .where(eq(schema.knowledgeSourceObjects.id, observed.knowledgeSourceObjectId))
2016
+ .limit(1),
2017
+ tx
2018
+ .select()
2019
+ .from(schema.knowledgeDocumentVersions)
2020
+ .where(eq(schema.knowledgeDocumentVersions.id, observed.knowledgeDocumentVersionId))
2021
+ .limit(1),
2022
+ tx
2023
+ .select()
2024
+ .from(schema.knowledgeSourceSyncIndexObligations)
2025
+ .where(eq(schema.knowledgeSourceSyncIndexObligations.id, input.obligationId))
2026
+ .for("update")
2027
+ .limit(1),
2028
+ ]);
2029
+ const [document] = obligation
2030
+ ? await tx
2031
+ .select()
2032
+ .from(schema.documents)
2033
+ .where(eq(schema.documents.id, obligation.documentId))
2034
+ .for("update")
2035
+ .limit(1)
2036
+ : [];
2037
+ if (
2038
+ !source ||
2039
+ !object ||
2040
+ !version ||
2041
+ !obligation ||
2042
+ !document ||
2043
+ state.reconnectRequired ||
2044
+ state.sourceSyncGeneration < input.sourceSyncGeneration ||
2045
+ state.sourceConfigGeneration !== input.sourceConfigGeneration ||
2046
+ state.sourceLifecycleGeneration !== input.sourceLifecycleGeneration ||
2047
+ source.lifecycleState !== "active" ||
2048
+ source.syncGeneration < obligation.sourceSyncGeneration ||
2049
+ source.lifecycleGeneration !== input.sourceLifecycleGeneration ||
2050
+ source.currentAclGeneration !== version.aclGeneration ||
2051
+ object.lifecycleState !== "active" ||
2052
+ object.lifecycleGeneration !== input.objectLifecycleGeneration ||
2053
+ object.versionGeneration !== input.objectVersionGeneration ||
2054
+ object.currentVersionId !== version.id ||
2055
+ version.documentId !== document.id ||
2056
+ version.fileId !== document.fileId ||
2057
+ obligation.sourceSyncGeneration !== input.sourceSyncGeneration ||
2058
+ obligation.sourceConfigGeneration !== input.sourceConfigGeneration ||
2059
+ obligation.sourceLifecycleGeneration !== input.sourceLifecycleGeneration ||
2060
+ obligation.objectLifecycleGeneration !== input.objectLifecycleGeneration ||
2061
+ obligation.objectVersionGeneration !== input.objectVersionGeneration ||
2062
+ obligation.status !== "indexed"
2063
+ ) {
2064
+ throw new Error("Google Drive ACL refresh authority changed");
2065
+ }
2066
+ const now = new Date();
2067
+ await tx
2068
+ .update(schema.knowledgeSourceSyncIndexObligations)
2069
+ .set({
2070
+ googleDriveAclEvidenceId: null,
2071
+ aclEligibility: "denied",
2072
+ updatedAt: now,
2073
+ })
2074
+ .where(eq(schema.knowledgeSourceSyncIndexObligations.id, obligation.id));
2075
+ await tx
2076
+ .update(schema.knowledgeSourceSyncItemOutcomes)
2077
+ .set({
2078
+ aclEligibility: "denied",
2079
+ aclEvidence: { version: 1, provider: "google_drive", state: "refresh_pending" },
2080
+ })
2081
+ .where(eq(schema.knowledgeSourceSyncItemOutcomes.indexObligationId, obligation.id));
2082
+ await tx
2083
+ .update(schema.documents)
2084
+ .set({ agentAccess: false, updatedAt: now })
2085
+ .where(eq(schema.documents.id, obligation.documentId));
2086
+ }),
2087
+ );
2088
+ }
2089
+
2090
+ function normalizeGoogleDriveAclPrincipal(principal: GoogleDriveObjectAclPrincipal) {
2091
+ const permissionId = cleanBoundedString(principal.permissionId, 1024);
2092
+ const emailAddress = cleanBoundedString(principal.emailAddress, 320)?.toLowerCase() ?? null;
2093
+ const domain = cleanBoundedString(principal.domain, 320)?.toLowerCase() ?? null;
2094
+ if (principal.type === "user" || principal.type === "group") {
2095
+ if (!permissionId && !emailAddress) {
2096
+ throw new Error(`Google Drive ${principal.type} principal is missing identity`);
2097
+ }
2098
+ } else if (principal.type === "domain") {
2099
+ if (!domain) throw new Error("Google Drive domain principal is missing its domain");
2100
+ } else if (principal.type !== "anyone") {
2101
+ throw new Error("Google Drive ACL principal type is invalid");
2102
+ }
2103
+ const expirationTime = principal.expirationTime
2104
+ ? exactDate(principal.expirationTime, "Google Drive ACL principal expiration")
2105
+ : null;
2106
+ return {
2107
+ principalType: principal.type,
2108
+ permissionIdHash: permissionId ? principalHash("permission", permissionId) : null,
2109
+ emailHash: emailAddress ? principalHash("email", emailAddress) : null,
2110
+ domainHash: domain ? principalHash("domain", domain) : null,
2111
+ role: principal.role,
2112
+ inherited: principal.inherited === true,
2113
+ allowFileDiscovery:
2114
+ principal.allowFileDiscovery === undefined ? null : principal.allowFileDiscovery,
2115
+ expirationTime,
2116
+ };
2117
+ }
2118
+
2119
+ function principalHash(kind: "permission" | "email" | "domain", value: string): string {
2120
+ return sha256Hex(`${kind}:${value.trim().toLowerCase()}`);
2121
+ }
2122
+
2123
+ function sha256Hex(value: string): string {
2124
+ return createHash("sha256").update(value).digest("hex");
2125
+ }
2126
+
2127
+ function exactDate(value: string, label: string): Date {
2128
+ const parsed = new Date(value);
2129
+ if (!value || !Number.isFinite(parsed.getTime()) || parsed.toISOString() !== value) {
2130
+ throw new Error(`${label} must be an exact UTC timestamp`);
2131
+ }
2132
+ return parsed;
2133
+ }
2134
+
2135
+ function exactBoundedString(value: unknown, max: number, label: string): string {
2136
+ const normalized = cleanBoundedString(value, max);
2137
+ if (!normalized || normalized !== value) throw new Error(`${label} is invalid`);
2138
+ return normalized;
2139
+ }
2140
+
2141
+ function cleanBoundedString(value: unknown, max: number): string | null {
2142
+ if (typeof value !== "string") return null;
2143
+ const normalized = value.trim();
2144
+ return normalized && normalized.length <= max ? normalized : null;
2145
+ }
2146
+
1617
2147
  /** Bounded retention hook. Callers choose policy and cadence; pending index or
1618
2148
  * ACL obligations are never deleted by age alone. */
1619
2149
  export async function pruneKnowledgeSourceSyncEvidence(
@@ -0,0 +1,116 @@
1
+ import { and, eq, sql } from "drizzle-orm";
2
+ import type { Database } from "./database";
3
+ import { rawRows, setRlsContext } from "./database";
4
+ import * as schema from "./schema";
5
+
6
+ export type ManagedHumanPersonalWorkspaceProvisioning = {
7
+ organizationMembershipId: string;
8
+ personalWorkspaceId: string;
9
+ };
10
+
11
+ /**
12
+ * Converge the deterministic managed-human personal workspace, its inference
13
+ * control row, and the organization-membership anchor through the exact 0219
14
+ * `ensure_managed_human_personal_workspace` lifecycle seam.
15
+ *
16
+ * This is the single implementation of that preparation. The Better Auth
17
+ * managed-access hook and the operator membership backfill driver both call it,
18
+ * so neither owns a second copy of the deterministic personal-workspace
19
+ * identity. All authority - account binding, owner-membership proof, workspace
20
+ * identity, control-row validation, membership status - stays inside the
21
+ * SECURITY DEFINER seam, which fails closed; nothing here writes an
22
+ * organization-tenancy table directly.
23
+ *
24
+ * Must be called inside a transaction whose RLS context is already the exact
25
+ * account with no workspace. It restores that context before returning.
26
+ */
27
+ export async function ensureManagedHumanPersonalWorkspace(
28
+ tx: Database,
29
+ input: { accountId: string; subjectId: string },
30
+ ): Promise<ManagedHumanPersonalWorkspaceProvisioning> {
31
+ const personalWorkspaceExternalSource = "opengeni:organization-membership";
32
+ const personalWorkspaceExternalId = `${input.accountId}:${input.subjectId}`;
33
+ let [personalWorkspace] = await tx
34
+ .select()
35
+ .from(schema.workspaces)
36
+ .where(
37
+ and(
38
+ eq(schema.workspaces.externalSource, personalWorkspaceExternalSource),
39
+ eq(schema.workspaces.externalId, personalWorkspaceExternalId),
40
+ ),
41
+ )
42
+ .limit(1);
43
+ if (!personalWorkspace) {
44
+ [personalWorkspace] = await tx
45
+ .insert(schema.workspaces)
46
+ .values({
47
+ accountId: input.accountId,
48
+ name: "Personal workspace",
49
+ slug: null,
50
+ externalSource: personalWorkspaceExternalSource,
51
+ externalId: personalWorkspaceExternalId,
52
+ })
53
+ .onConflictDoUpdate({
54
+ target: [schema.workspaces.externalSource, schema.workspaces.externalId],
55
+ set: { updatedAt: new Date() },
56
+ })
57
+ .returning();
58
+ }
59
+ if (!personalWorkspace) {
60
+ throw new Error("Failed to ensure personal workspace");
61
+ }
62
+ if (
63
+ personalWorkspace.accountId !== input.accountId ||
64
+ personalWorkspace.externalSource !== personalWorkspaceExternalSource ||
65
+ personalWorkspace.externalId !== personalWorkspaceExternalId
66
+ ) {
67
+ throw new Error("Managed personal workspace identity conflict");
68
+ }
69
+
70
+ await setRlsContext(tx, {
71
+ accountId: input.accountId,
72
+ workspaceId: personalWorkspace.id,
73
+ });
74
+ const [personalWorkspaceControl] = await tx
75
+ .select({ workspaceId: schema.workspaceInferenceControls.workspaceId })
76
+ .from(schema.workspaceInferenceControls)
77
+ .where(eq(schema.workspaceInferenceControls.workspaceId, personalWorkspace.id))
78
+ .limit(1);
79
+ if (!personalWorkspaceControl) {
80
+ await tx
81
+ .insert(schema.workspaceInferenceControls)
82
+ .values({
83
+ workspaceId: personalWorkspace.id,
84
+ accountId: input.accountId,
85
+ })
86
+ .onConflictDoNothing();
87
+ }
88
+ await setRlsContext(tx, {
89
+ accountId: input.accountId,
90
+ workspaceId: null,
91
+ });
92
+
93
+ const [provisionedMembership] = await rawRows<{
94
+ organization_membership_id: string;
95
+ personal_workspace_id: string;
96
+ }>(
97
+ tx,
98
+ sql`
99
+ select * from ensure_managed_human_personal_workspace(
100
+ ${input.accountId},
101
+ ${input.subjectId},
102
+ ${personalWorkspace.id}
103
+ )
104
+ `,
105
+ );
106
+ if (
107
+ !provisionedMembership ||
108
+ provisionedMembership.personal_workspace_id !== personalWorkspace.id
109
+ ) {
110
+ throw new Error("Managed organization membership provisioning did not converge");
111
+ }
112
+ return {
113
+ organizationMembershipId: provisionedMembership.organization_membership_id,
114
+ personalWorkspaceId: provisionedMembership.personal_workspace_id,
115
+ };
116
+ }