@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
@@ -0,0 +1,1273 @@
1
+ -- deployment-mode: rolling
2
+ -- This migration activates organization-user ownership for personal Documents. Existing
3
+ -- personal rows remain fail-closed to their original workspace; new personal
4
+ -- rows may carry the common user-resource authority and follow their owner
5
+ -- across workspaces in the same organization. Agent reads require an exact
6
+ -- attempt snapshot backed by an explicit once/session/always grant.
7
+
8
+ SET LOCAL lock_timeout = '5s';
9
+ SET LOCAL statement_timeout = '10min';
10
+
11
+ ALTER TABLE "documents"
12
+ ADD COLUMN "authority_id" uuid,
13
+ ADD COLUMN "owner_organization_membership_id" uuid,
14
+ ADD COLUMN "origin_workspace_id" uuid;
15
+
16
+ ALTER TABLE "document_chunks"
17
+ ADD COLUMN "authority_id" uuid,
18
+ ADD COLUMN "owner_organization_membership_id" uuid;
19
+
20
+ UPDATE "documents" SET "origin_workspace_id" = "workspace_id";
21
+ ALTER TABLE "documents" ADD CONSTRAINT "documents_origin_workspace_not_null_chk"
22
+ CHECK ("origin_workspace_id" IS NOT NULL) NOT VALID;
23
+ ALTER TABLE "documents" VALIDATE CONSTRAINT "documents_origin_workspace_not_null_chk";
24
+ ALTER TABLE "documents" ALTER COLUMN "origin_workspace_id" SET NOT NULL;
25
+ ALTER TABLE "documents" DROP CONSTRAINT "documents_origin_workspace_not_null_chk";
26
+ ALTER TABLE "documents" ADD CONSTRAINT "documents_origin_workspace_chk"
27
+ CHECK ("origin_workspace_id" = "workspace_id") NOT VALID;
28
+ ALTER TABLE "documents" VALIDATE CONSTRAINT "documents_origin_workspace_chk";
29
+
30
+ ALTER TABLE "documents"
31
+ ADD CONSTRAINT "documents_user_authority_fk"
32
+ FOREIGN KEY ("authority_id", "account_id", "owner_organization_membership_id")
33
+ REFERENCES "organization_user_resource_authorities"(
34
+ "id", "account_id", "organization_membership_id"
35
+ ) ON DELETE RESTRICT NOT VALID;
36
+ ALTER TABLE "documents" VALIDATE CONSTRAINT "documents_user_authority_fk";
37
+
38
+ ALTER TABLE "document_chunks"
39
+ ADD CONSTRAINT "document_chunks_user_authority_fk"
40
+ FOREIGN KEY ("authority_id", "account_id", "owner_organization_membership_id")
41
+ REFERENCES "organization_user_resource_authorities"(
42
+ "id", "account_id", "organization_membership_id"
43
+ ) ON DELETE RESTRICT NOT VALID;
44
+ ALTER TABLE "document_chunks" VALIDATE CONSTRAINT "document_chunks_user_authority_fk";
45
+
46
+ ALTER TABLE "documents" DROP CONSTRAINT "documents_authority_chk";
47
+ ALTER TABLE "documents" ADD CONSTRAINT "documents_authority_chk" CHECK (
48
+ (
49
+ "authority_kind" = 'organization'
50
+ AND "authority_workspace_id" IS NULL
51
+ AND "authority_subject_id" IS NULL
52
+ AND "authority_id" IS NULL
53
+ AND "owner_organization_membership_id" IS NULL
54
+ ) OR (
55
+ "authority_kind" = 'workspace'
56
+ AND "authority_workspace_id" = "workspace_id"
57
+ AND "authority_subject_id" IS NULL
58
+ AND "authority_id" IS NULL
59
+ AND "owner_organization_membership_id" IS NULL
60
+ ) OR (
61
+ "authority_kind" = 'personal'
62
+ AND NULLIF(btrim("authority_subject_id"), '') IS NOT NULL
63
+ AND octet_length(convert_to("authority_subject_id", 'UTF8')) <= 1024
64
+ AND "authority_subject_id" = "created_by"
65
+ AND (
66
+ (
67
+ "authority_workspace_id" = "workspace_id"
68
+ AND "authority_id" IS NULL
69
+ AND "owner_organization_membership_id" IS NULL
70
+ ) OR (
71
+ "authority_workspace_id" IS NULL
72
+ AND "authority_id" IS NOT NULL
73
+ AND "owner_organization_membership_id" IS NOT NULL
74
+ )
75
+ )
76
+ )
77
+ ) NOT VALID;
78
+ ALTER TABLE "documents" VALIDATE CONSTRAINT "documents_authority_chk";
79
+
80
+ ALTER TABLE "document_chunks" DROP CONSTRAINT "document_chunks_authority_chk";
81
+ ALTER TABLE "document_chunks" ADD CONSTRAINT "document_chunks_authority_chk" CHECK (
82
+ (
83
+ "authority_kind" = 'organization'
84
+ AND "authority_workspace_id" IS NULL
85
+ AND "authority_subject_id" IS NULL
86
+ AND "authority_id" IS NULL
87
+ AND "owner_organization_membership_id" IS NULL
88
+ ) OR (
89
+ "authority_kind" = 'workspace'
90
+ AND "authority_workspace_id" = "workspace_id"
91
+ AND "authority_subject_id" IS NULL
92
+ AND "authority_id" IS NULL
93
+ AND "owner_organization_membership_id" IS NULL
94
+ ) OR (
95
+ "authority_kind" = 'personal'
96
+ AND NULLIF(btrim("authority_subject_id"), '') IS NOT NULL
97
+ AND octet_length(convert_to("authority_subject_id", 'UTF8')) <= 1024
98
+ AND (
99
+ (
100
+ "authority_workspace_id" = "workspace_id"
101
+ AND "authority_id" IS NULL
102
+ AND "owner_organization_membership_id" IS NULL
103
+ ) OR (
104
+ "authority_workspace_id" IS NULL
105
+ AND "authority_id" IS NOT NULL
106
+ AND "owner_organization_membership_id" IS NOT NULL
107
+ )
108
+ )
109
+ )
110
+ ) NOT VALID;
111
+ ALTER TABLE "document_chunks" VALIDATE CONSTRAINT "document_chunks_authority_chk";
112
+
113
+ CREATE INDEX "documents_user_authority_idx"
114
+ ON "documents" ("account_id", "owner_organization_membership_id", "status", "id")
115
+ WHERE "authority_kind" = 'personal' AND "authority_id" IS NOT NULL;
116
+
117
+ CREATE TABLE opengeni_private.personal_document_authority_capabilities (
118
+ "backend_pid" integer NOT NULL,
119
+ "transaction_id" xid8 NOT NULL,
120
+ "capability_kind" text NOT NULL,
121
+ "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
122
+ CONSTRAINT "personal_document_authority_capabilities_kind_chk" CHECK (
123
+ "capability_kind" IN ('write', 'admit', 'resolve')
124
+ ),
125
+ CONSTRAINT "personal_document_authority_capabilities_pk" PRIMARY KEY (
126
+ "backend_pid", "transaction_id", "capability_kind"
127
+ )
128
+ );
129
+ REVOKE ALL ON TABLE opengeni_private.personal_document_authority_capabilities FROM PUBLIC;
130
+
131
+ CREATE OR REPLACE FUNCTION opengeni_private.personal_document_authority_capability_active(
132
+ p_capability_kind text DEFAULT NULL
133
+ ) RETURNS boolean
134
+ LANGUAGE sql
135
+ STABLE
136
+ SECURITY DEFINER
137
+ SET search_path = pg_catalog
138
+ AS $capability_active$
139
+ SELECT EXISTS (
140
+ SELECT 1
141
+ FROM opengeni_private.personal_document_authority_capabilities capability
142
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
143
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
144
+ AND (
145
+ p_capability_kind IS NULL
146
+ OR capability.capability_kind = p_capability_kind
147
+ )
148
+ )
149
+ $capability_active$;
150
+ REVOKE ALL ON FUNCTION
151
+ opengeni_private.personal_document_authority_capability_active(text) FROM PUBLIC;
152
+
153
+ CREATE TABLE "session_attempt_personal_document_admissions" (
154
+ "attempt_id" uuid PRIMARY KEY REFERENCES "session_turn_attempts"("id") ON DELETE CASCADE,
155
+ "account_id" uuid NOT NULL,
156
+ "workspace_id" uuid NOT NULL,
157
+ "session_id" uuid NOT NULL,
158
+ "turn_id" uuid NOT NULL,
159
+ "execution_generation" integer NOT NULL,
160
+ "initiating_human_subject_id" text NOT NULL,
161
+ "owner_organization_membership_id" uuid NOT NULL,
162
+ "membership_authorization_revision" bigint NOT NULL,
163
+ "session_visibility" text NOT NULL,
164
+ "session_authority_epoch" integer NOT NULL,
165
+ "document_count" integer NOT NULL,
166
+ "admitted_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
167
+ CONSTRAINT "session_attempt_personal_document_admissions_attempt_fk"
168
+ FOREIGN KEY ("account_id", "workspace_id", "session_id", "turn_id", "attempt_id")
169
+ REFERENCES "session_turn_attempts"(
170
+ "account_id", "workspace_id", "session_id", "turn_id", "id"
171
+ ) ON DELETE CASCADE,
172
+ CONSTRAINT "session_attempt_personal_document_admissions_owner_fk"
173
+ FOREIGN KEY ("owner_organization_membership_id", "account_id")
174
+ REFERENCES "organization_memberships"("id", "account_id") ON DELETE RESTRICT,
175
+ CONSTRAINT "session_attempt_personal_document_admissions_shape_chk" CHECK (
176
+ "execution_generation" > 0
177
+ AND "membership_authorization_revision" > 0
178
+ AND "session_authority_epoch" > 0
179
+ AND "document_count" >= 0
180
+ AND "session_visibility" IN ('user_private', 'workspace_shared')
181
+ AND length(btrim("initiating_human_subject_id")) BETWEEN 1 AND 1024
182
+ )
183
+ );
184
+
185
+ CREATE TABLE "session_attempt_personal_document_snapshots" (
186
+ "attempt_id" uuid NOT NULL,
187
+ "document_id" uuid NOT NULL REFERENCES "documents"("id") ON DELETE CASCADE,
188
+ "account_id" uuid NOT NULL,
189
+ "workspace_id" uuid NOT NULL,
190
+ "session_id" uuid NOT NULL,
191
+ "turn_id" uuid NOT NULL,
192
+ "execution_generation" integer NOT NULL,
193
+ "owner_organization_membership_id" uuid NOT NULL,
194
+ "membership_authorization_revision" bigint NOT NULL,
195
+ "authority_id" uuid NOT NULL,
196
+ "authority_generation" bigint NOT NULL,
197
+ "target_workspace_id" uuid NOT NULL,
198
+ "session_visibility" text NOT NULL,
199
+ "session_authority_epoch" integer NOT NULL,
200
+ "grant_id" uuid NOT NULL,
201
+ "grant_generation" bigint NOT NULL,
202
+ "grant_mode" text NOT NULL,
203
+ "grant_context" text NOT NULL,
204
+ "grant_session_id" uuid,
205
+ "grant_authority_epoch" integer,
206
+ "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
207
+ CONSTRAINT "session_attempt_personal_document_snapshots_pk"
208
+ PRIMARY KEY ("attempt_id", "document_id"),
209
+ CONSTRAINT "session_attempt_personal_document_snapshots_admission_fk"
210
+ FOREIGN KEY ("attempt_id")
211
+ REFERENCES "session_attempt_personal_document_admissions"("attempt_id") ON DELETE CASCADE,
212
+ CONSTRAINT "session_attempt_personal_document_snapshots_authority_fk"
213
+ FOREIGN KEY ("authority_id", "account_id", "owner_organization_membership_id")
214
+ REFERENCES "organization_user_resource_authorities"(
215
+ "id", "account_id", "organization_membership_id"
216
+ ) ON DELETE RESTRICT,
217
+ CONSTRAINT "session_attempt_personal_document_snapshots_grant_fk"
218
+ FOREIGN KEY ("grant_id", "account_id")
219
+ REFERENCES "organization_user_resource_grants"("id", "account_id") ON DELETE RESTRICT,
220
+ CONSTRAINT "session_attempt_personal_document_snapshots_shape_chk" CHECK (
221
+ "execution_generation" > 0
222
+ AND "membership_authorization_revision" > 0
223
+ AND "authority_generation" > 0
224
+ AND "target_workspace_id" = "workspace_id"
225
+ AND "session_authority_epoch" > 0
226
+ AND "grant_generation" > 0
227
+ AND "session_visibility" IN ('user_private', 'workspace_shared')
228
+ AND "grant_context" = "session_visibility"
229
+ AND (
230
+ (
231
+ "grant_mode" = 'always'
232
+ AND "grant_session_id" IS NULL
233
+ AND "grant_authority_epoch" IS NULL
234
+ ) OR (
235
+ "grant_mode" IN ('once', 'session')
236
+ AND "grant_session_id" = "session_id"
237
+ AND "grant_authority_epoch" = "session_authority_epoch"
238
+ )
239
+ )
240
+ )
241
+ );
242
+
243
+ CREATE TABLE "personal_document_once_consumption_receipts" (
244
+ "grant_id" uuid PRIMARY KEY,
245
+ "account_id" uuid NOT NULL,
246
+ "attempt_id" uuid NOT NULL,
247
+ "document_id" uuid NOT NULL,
248
+ "authority_id" uuid NOT NULL,
249
+ "authority_generation" bigint NOT NULL,
250
+ "grant_generation" bigint NOT NULL,
251
+ "consumed_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
252
+ CONSTRAINT "personal_document_once_consumption_receipts_grant_fk"
253
+ FOREIGN KEY ("grant_id", "account_id")
254
+ REFERENCES "organization_user_resource_grants"("id", "account_id") ON DELETE RESTRICT,
255
+ CONSTRAINT "personal_document_once_consumption_receipts_snapshot_fk"
256
+ FOREIGN KEY ("attempt_id", "document_id")
257
+ REFERENCES "session_attempt_personal_document_snapshots"(
258
+ "attempt_id", "document_id"
259
+ ) ON DELETE CASCADE,
260
+ CONSTRAINT "personal_document_once_consumption_receipts_generation_chk" CHECK (
261
+ "authority_generation" > 0 AND "grant_generation" > 0
262
+ )
263
+ );
264
+
265
+ ALTER TABLE "session_attempt_personal_document_admissions" ENABLE ROW LEVEL SECURITY;
266
+ ALTER TABLE "session_attempt_personal_document_admissions" FORCE ROW LEVEL SECURITY;
267
+ ALTER TABLE "session_attempt_personal_document_snapshots" ENABLE ROW LEVEL SECURITY;
268
+ ALTER TABLE "session_attempt_personal_document_snapshots" FORCE ROW LEVEL SECURITY;
269
+ ALTER TABLE "personal_document_once_consumption_receipts" ENABLE ROW LEVEL SECURITY;
270
+ ALTER TABLE "personal_document_once_consumption_receipts" FORCE ROW LEVEL SECURITY;
271
+
272
+ DO $personal_document_capability_policies$
273
+ DECLARE
274
+ data_schema text := current_schema();
275
+ migration_owner text := current_user;
276
+ table_name text;
277
+ BEGIN
278
+ FOREACH table_name IN ARRAY ARRAY[
279
+ 'documents', 'files', 'organization_memberships', 'workspace_memberships',
280
+ 'organization_user_resource_authorities', 'organization_user_resource_grants',
281
+ 'sessions', 'session_turns', 'session_turn_attempts', 'session_attempt_interruptions',
282
+ 'session_attempt_personal_document_admissions',
283
+ 'session_attempt_personal_document_snapshots',
284
+ 'personal_document_once_consumption_receipts'
285
+ ] LOOP
286
+ EXECUTE format(
287
+ 'CREATE POLICY personal_document_authority_capability_read ON %I.%I '
288
+ || 'FOR SELECT USING (current_user = %L AND '
289
+ || 'opengeni_private.personal_document_authority_capability_active())',
290
+ data_schema, table_name, migration_owner
291
+ );
292
+ END LOOP;
293
+
294
+ EXECUTE format(
295
+ 'CREATE POLICY personal_document_authority_capability_insert '
296
+ || 'ON %I.organization_user_resource_authorities FOR INSERT WITH CHECK ('
297
+ || 'current_user = %L AND '
298
+ || 'opengeni_private.personal_document_authority_capability_active(''write''))',
299
+ data_schema, migration_owner
300
+ );
301
+ EXECUTE format(
302
+ 'CREATE POLICY personal_document_authority_capability_update '
303
+ || 'ON %I.organization_user_resource_authorities FOR UPDATE USING ('
304
+ || 'current_user = %L AND '
305
+ || 'opengeni_private.personal_document_authority_capability_active(''write'')) '
306
+ || 'WITH CHECK (current_user = %L AND '
307
+ || 'opengeni_private.personal_document_authority_capability_active(''write''))',
308
+ data_schema, migration_owner, migration_owner
309
+ );
310
+ EXECUTE format(
311
+ 'CREATE POLICY personal_document_grant_capability_update '
312
+ || 'ON %I.organization_user_resource_grants FOR UPDATE USING ('
313
+ || 'current_user = %L AND '
314
+ || 'opengeni_private.personal_document_authority_capability_active()) '
315
+ || 'WITH CHECK (current_user = %L AND '
316
+ || 'opengeni_private.personal_document_authority_capability_active())',
317
+ data_schema, migration_owner, migration_owner
318
+ );
319
+
320
+ FOREACH table_name IN ARRAY ARRAY[
321
+ 'session_attempt_personal_document_admissions',
322
+ 'session_attempt_personal_document_snapshots',
323
+ 'personal_document_once_consumption_receipts'
324
+ ] LOOP
325
+ EXECUTE format(
326
+ 'CREATE POLICY personal_document_authority_capability_insert ON %I.%I '
327
+ || 'FOR INSERT WITH CHECK (current_user = %L AND '
328
+ || 'opengeni_private.personal_document_authority_capability_active(''admit''))',
329
+ data_schema, table_name, migration_owner
330
+ );
331
+ END LOOP;
332
+ EXECUTE format(
333
+ 'CREATE POLICY personal_document_authority_capability_update '
334
+ || 'ON %I.session_attempt_personal_document_admissions FOR UPDATE USING ('
335
+ || 'current_user = %L AND '
336
+ || 'opengeni_private.personal_document_authority_capability_active(''admit'')) '
337
+ || 'WITH CHECK (current_user = %L AND '
338
+ || 'opengeni_private.personal_document_authority_capability_active(''admit''))',
339
+ data_schema, migration_owner, migration_owner
340
+ );
341
+ END
342
+ $personal_document_capability_policies$;
343
+
344
+ DO $personal_document_functions$
345
+ DECLARE
346
+ data_schema text := current_schema();
347
+ BEGIN
348
+ EXECUTE format($ddl$
349
+ CREATE OR REPLACE FUNCTION %1$I.create_personal_document_authority(
350
+ p_account_id uuid,
351
+ p_workspace_id uuid,
352
+ p_document_id uuid
353
+ ) RETURNS TABLE (
354
+ authority_id uuid,
355
+ owner_organization_membership_id uuid,
356
+ authority_generation bigint
357
+ )
358
+ LANGUAGE plpgsql
359
+ SECURITY DEFINER
360
+ SET search_path = %1$I, pg_catalog
361
+ AS $body$
362
+ DECLARE
363
+ caller_subject text := nullif(pg_catalog.current_setting('opengeni.subject_id', true), '');
364
+ member_row organization_memberships%%ROWTYPE;
365
+ created_authority_id uuid := pg_catalog.gen_random_uuid();
366
+ BEGIN
367
+ INSERT INTO opengeni_private.personal_document_authority_capabilities (
368
+ backend_pid, transaction_id, capability_kind
369
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'write')
370
+ ON CONFLICT DO NOTHING;
371
+
372
+ IF p_account_id IS DISTINCT FROM nullif(
373
+ pg_catalog.current_setting('opengeni.account_id', true), ''
374
+ )::uuid
375
+ OR p_workspace_id IS DISTINCT FROM nullif(
376
+ pg_catalog.current_setting('opengeni.workspace_id', true), ''
377
+ )::uuid
378
+ OR caller_subject IS NULL
379
+ THEN
380
+ RAISE EXCEPTION 'personal document creation scope mismatch' USING ERRCODE = '42501';
381
+ END IF;
382
+
383
+ SELECT membership.* INTO member_row
384
+ FROM organization_memberships membership
385
+ WHERE membership.account_id = p_account_id
386
+ AND membership.subject_id = caller_subject
387
+ AND membership.status = 'active'
388
+ AND membership.revoked_at IS NULL
389
+ FOR SHARE;
390
+
391
+ -- Configured/local and rolling-legacy subjects may legitimately use the
392
+ -- pre-organization personal Document lane. Only create the portable
393
+ -- organization-user authority when there is one deterministic active
394
+ -- organization membership; otherwise the caller inserts the existing
395
+ -- workspace-anchored personal tuple in the same transaction.
396
+ IF NOT FOUND THEN
397
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
398
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
399
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
400
+ AND capability_kind = 'write';
401
+ RETURN;
402
+ END IF;
403
+
404
+ IF member_row.personal_workspace_id IS DISTINCT FROM p_workspace_id THEN
405
+ PERFORM 1 FROM workspace_memberships workspace_membership
406
+ WHERE workspace_membership.account_id = p_account_id
407
+ AND workspace_membership.workspace_id = p_workspace_id
408
+ AND workspace_membership.subject_id = caller_subject
409
+ FOR KEY SHARE;
410
+ IF NOT FOUND THEN
411
+ RAISE EXCEPTION 'personal document owner lacks current workspace access'
412
+ USING ERRCODE = '42501';
413
+ END IF;
414
+ END IF;
415
+
416
+ INSERT INTO organization_user_resource_authorities (
417
+ id, account_id, organization_membership_id, resource_kind,
418
+ resource_id, origin_workspace_id, generation, status
419
+ ) VALUES (
420
+ created_authority_id, p_account_id, member_row.id, 'document',
421
+ p_document_id, p_workspace_id, 1, 'active'
422
+ );
423
+
424
+ authority_id := created_authority_id;
425
+ owner_organization_membership_id := member_row.id;
426
+ authority_generation := 1;
427
+ RETURN NEXT;
428
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
429
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
430
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
431
+ AND capability_kind = 'write';
432
+ RETURN;
433
+ EXCEPTION WHEN OTHERS THEN
434
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
435
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
436
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
437
+ AND capability_kind = 'write';
438
+ RAISE;
439
+ END
440
+ $body$;
441
+ $ddl$, data_schema);
442
+
443
+ EXECUTE format($ddl$
444
+ CREATE OR REPLACE FUNCTION %1$I.prepare_session_attempt_personal_document_reads(
445
+ p_account_id uuid,
446
+ p_workspace_id uuid,
447
+ p_session_id uuid,
448
+ p_attempt_id uuid
449
+ ) RETURNS integer
450
+ LANGUAGE plpgsql
451
+ SECURITY DEFINER
452
+ SET search_path = %1$I, pg_catalog
453
+ AS $body$
454
+ DECLARE
455
+ session_row sessions%%ROWTYPE;
456
+ turn_row session_turns%%ROWTYPE;
457
+ attempt_row session_turn_attempts%%ROWTYPE;
458
+ member_row organization_memberships%%ROWTYPE;
459
+ document_row record;
460
+ grant_row organization_user_resource_grants%%ROWTYPE;
461
+ existing_count integer;
462
+ admitted_count integer := 0;
463
+ initiating_subject text;
464
+ affected integer;
465
+ BEGIN
466
+ INSERT INTO opengeni_private.personal_document_authority_capabilities (
467
+ backend_pid, transaction_id, capability_kind
468
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'admit')
469
+ ON CONFLICT DO NOTHING;
470
+
471
+ SELECT attempt.* INTO STRICT attempt_row
472
+ FROM session_turn_attempts attempt
473
+ WHERE attempt.id = p_attempt_id
474
+ AND attempt.account_id = p_account_id
475
+ AND attempt.workspace_id = p_workspace_id
476
+ AND attempt.session_id = p_session_id
477
+ FOR UPDATE;
478
+ SELECT session_value.* INTO STRICT session_row
479
+ FROM sessions session_value
480
+ WHERE session_value.id = attempt_row.session_id
481
+ AND session_value.account_id = attempt_row.account_id
482
+ AND session_value.workspace_id = attempt_row.workspace_id
483
+ FOR SHARE;
484
+ SELECT turn_value.* INTO STRICT turn_row
485
+ FROM session_turns turn_value
486
+ WHERE turn_value.id = attempt_row.turn_id
487
+ AND turn_value.account_id = attempt_row.account_id
488
+ AND turn_value.workspace_id = attempt_row.workspace_id
489
+ AND turn_value.session_id = attempt_row.session_id
490
+ FOR SHARE;
491
+
492
+ IF attempt_row.execution_generation <= 0
493
+ OR attempt_row.state NOT IN ('claimed', 'running')
494
+ OR attempt_row.closed_at IS NOT NULL
495
+ OR attempt_row.quiesced_at IS NOT NULL
496
+ OR session_row.active_turn_id IS DISTINCT FROM attempt_row.turn_id
497
+ OR turn_row.active_attempt_id IS DISTINCT FROM attempt_row.id
498
+ OR turn_row.execution_generation IS DISTINCT FROM attempt_row.execution_generation
499
+ OR turn_row.status <> 'running'
500
+ OR attempt_row.authority_visibility IS DISTINCT FROM session_row.visibility
501
+ OR attempt_row.authority_epoch IS DISTINCT FROM session_row.authority_epoch
502
+ OR EXISTS (
503
+ SELECT 1 FROM session_attempt_interruptions interruption
504
+ WHERE interruption.account_id = p_account_id
505
+ AND interruption.workspace_id = p_workspace_id
506
+ AND interruption.session_id = p_session_id
507
+ AND interruption.attempt_id = p_attempt_id
508
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
509
+ )
510
+ THEN
511
+ RAISE EXCEPTION 'personal document admission requires the exact current uninterrupted attempt'
512
+ USING ERRCODE = '42501';
513
+ END IF;
514
+
515
+ initiating_subject := coalesce(
516
+ nullif(btrim(turn_row.initiating_human_subject_id), ''),
517
+ CASE WHEN turn_row.initiator_kind = 'subject'
518
+ THEN nullif(btrim(turn_row.initiator_subject_id), '') END
519
+ );
520
+ IF initiating_subject IS NULL THEN
521
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
522
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
523
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
524
+ AND capability_kind = 'admit';
525
+ RETURN 0;
526
+ END IF;
527
+
528
+ SELECT membership.* INTO member_row
529
+ FROM organization_memberships membership
530
+ WHERE membership.account_id = p_account_id
531
+ AND membership.subject_id = initiating_subject
532
+ AND membership.status = 'active'
533
+ AND membership.revoked_at IS NULL
534
+ FOR SHARE;
535
+ IF NOT FOUND THEN
536
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
537
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
538
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
539
+ AND capability_kind = 'admit';
540
+ RETURN 0;
541
+ END IF;
542
+ IF member_row.personal_workspace_id IS DISTINCT FROM p_workspace_id THEN
543
+ PERFORM 1 FROM workspace_memberships workspace_membership
544
+ WHERE workspace_membership.account_id = p_account_id
545
+ AND workspace_membership.workspace_id = p_workspace_id
546
+ AND workspace_membership.subject_id = initiating_subject
547
+ FOR KEY SHARE;
548
+ IF NOT FOUND THEN
549
+ RAISE EXCEPTION 'initiating human lacks target-workspace membership'
550
+ USING ERRCODE = '42501';
551
+ END IF;
552
+ END IF;
553
+
554
+ SELECT admission.document_count INTO existing_count
555
+ FROM session_attempt_personal_document_admissions admission
556
+ WHERE admission.attempt_id = p_attempt_id;
557
+ IF FOUND THEN
558
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
559
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
560
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
561
+ AND capability_kind = 'admit';
562
+ RETURN existing_count;
563
+ END IF;
564
+
565
+ INSERT INTO session_attempt_personal_document_admissions (
566
+ attempt_id, account_id, workspace_id, session_id, turn_id,
567
+ execution_generation, initiating_human_subject_id,
568
+ owner_organization_membership_id, membership_authorization_revision,
569
+ session_visibility, session_authority_epoch, document_count
570
+ ) VALUES (
571
+ p_attempt_id, p_account_id, p_workspace_id, p_session_id, attempt_row.turn_id,
572
+ attempt_row.execution_generation, initiating_subject, member_row.id,
573
+ member_row.authorization_revision, session_row.visibility,
574
+ session_row.authority_epoch, 0
575
+ ) ON CONFLICT (attempt_id) DO NOTHING;
576
+ IF NOT FOUND THEN
577
+ SELECT admission.document_count INTO STRICT existing_count
578
+ FROM session_attempt_personal_document_admissions admission
579
+ WHERE admission.attempt_id = p_attempt_id;
580
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
581
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
582
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
583
+ AND capability_kind = 'admit';
584
+ RETURN existing_count;
585
+ END IF;
586
+
587
+ FOR document_row IN
588
+ SELECT document_value.id, document_value.authority_id,
589
+ authority.generation AS authority_generation
590
+ FROM documents document_value
591
+ JOIN organization_user_resource_authorities authority
592
+ ON authority.id = document_value.authority_id
593
+ AND authority.account_id = document_value.account_id
594
+ AND authority.organization_membership_id
595
+ = document_value.owner_organization_membership_id
596
+ AND authority.resource_kind = 'document'
597
+ AND authority.resource_id = document_value.id
598
+ AND authority.status = 'active'
599
+ AND authority.revoked_at IS NULL
600
+ WHERE document_value.account_id = p_account_id
601
+ AND document_value.authority_kind = 'personal'
602
+ AND document_value.authority_workspace_id IS NULL
603
+ AND document_value.authority_subject_id = initiating_subject
604
+ AND document_value.owner_organization_membership_id = member_row.id
605
+ AND document_value.status = 'ready'
606
+ AND document_value.agent_access = true
607
+ ORDER BY document_value.id
608
+ FOR SHARE OF document_value, authority
609
+ LOOP
610
+ SELECT grant_value.* INTO grant_row
611
+ FROM organization_user_resource_grants grant_value
612
+ WHERE grant_value.account_id = p_account_id
613
+ AND grant_value.authority_id = document_row.authority_id
614
+ AND grant_value.owner_organization_membership_id = member_row.id
615
+ AND grant_value.workspace_id = p_workspace_id
616
+ AND grant_value.action = 'document.read'
617
+ AND grant_value.context = session_row.visibility
618
+ AND grant_value.status = 'active'
619
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
620
+ AND (
621
+ (
622
+ grant_value.mode IN ('once', 'session')
623
+ AND grant_value.session_id = p_session_id
624
+ AND grant_value.authority_epoch = session_row.authority_epoch
625
+ ) OR (
626
+ grant_value.mode = 'always'
627
+ AND grant_value.session_id IS NULL
628
+ AND grant_value.authority_epoch IS NULL
629
+ )
630
+ )
631
+ ORDER BY CASE grant_value.mode WHEN 'once' THEN 1 WHEN 'session' THEN 2 ELSE 3 END,
632
+ grant_value.generation DESC, grant_value.id
633
+ LIMIT 1
634
+ FOR UPDATE;
635
+ IF NOT FOUND THEN CONTINUE; END IF;
636
+
637
+ INSERT INTO session_attempt_personal_document_snapshots (
638
+ attempt_id, document_id, account_id, workspace_id, session_id, turn_id,
639
+ execution_generation, owner_organization_membership_id,
640
+ membership_authorization_revision, authority_id, authority_generation,
641
+ target_workspace_id, session_visibility, session_authority_epoch,
642
+ grant_id, grant_generation, grant_mode, grant_context,
643
+ grant_session_id, grant_authority_epoch
644
+ ) VALUES (
645
+ p_attempt_id, document_row.id, p_account_id, p_workspace_id, p_session_id,
646
+ attempt_row.turn_id, attempt_row.execution_generation, member_row.id,
647
+ member_row.authorization_revision, document_row.authority_id,
648
+ document_row.authority_generation, p_workspace_id, session_row.visibility,
649
+ session_row.authority_epoch, grant_row.id, grant_row.generation,
650
+ grant_row.mode, grant_row.context, grant_row.session_id,
651
+ grant_row.authority_epoch
652
+ );
653
+
654
+ IF grant_row.mode = 'once' THEN
655
+ UPDATE organization_user_resource_grants
656
+ SET status = 'consumed', updated_at = clock_timestamp()
657
+ WHERE id = grant_row.id AND account_id = p_account_id
658
+ AND generation = grant_row.generation AND status = 'active';
659
+ GET DIAGNOSTICS affected = ROW_COUNT;
660
+ IF affected <> 1 THEN
661
+ RAISE EXCEPTION 'once document grant lost its first-use race'
662
+ USING ERRCODE = '40001';
663
+ END IF;
664
+ INSERT INTO personal_document_once_consumption_receipts (
665
+ grant_id, account_id, attempt_id, document_id, authority_id,
666
+ authority_generation, grant_generation
667
+ ) VALUES (
668
+ grant_row.id, p_account_id, p_attempt_id, document_row.id,
669
+ document_row.authority_id, document_row.authority_generation,
670
+ grant_row.generation
671
+ );
672
+ END IF;
673
+ admitted_count := admitted_count + 1;
674
+ END LOOP;
675
+
676
+ UPDATE session_attempt_personal_document_admissions
677
+ SET document_count = admitted_count
678
+ WHERE attempt_id = p_attempt_id;
679
+
680
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
681
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
682
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
683
+ AND capability_kind = 'admit';
684
+ RETURN admitted_count;
685
+ EXCEPTION WHEN OTHERS THEN
686
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
687
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
688
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
689
+ AND capability_kind = 'admit';
690
+ RAISE;
691
+ END
692
+ $body$;
693
+ $ddl$, data_schema);
694
+
695
+ EXECUTE format($ddl$
696
+ CREATE OR REPLACE FUNCTION %1$I.resolve_session_attempt_personal_document_reads(
697
+ p_account_id uuid,
698
+ p_workspace_id uuid,
699
+ p_session_id uuid,
700
+ p_attempt_id uuid
701
+ ) RETURNS SETOF uuid
702
+ LANGUAGE plpgsql
703
+ SECURITY DEFINER
704
+ SET search_path = %1$I, pg_catalog
705
+ AS $body$
706
+ DECLARE
707
+ admission_row session_attempt_personal_document_admissions%%ROWTYPE;
708
+ invalid_count integer;
709
+ actual_count integer;
710
+ caller_subject text := coalesce(
711
+ nullif(pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), ''),
712
+ nullif(pg_catalog.current_setting('opengeni.subject_id', true), '')
713
+ );
714
+ BEGIN
715
+ IF p_account_id IS DISTINCT FROM nullif(
716
+ pg_catalog.current_setting('opengeni.account_id', true), ''
717
+ )::uuid
718
+ OR p_workspace_id IS DISTINCT FROM nullif(
719
+ pg_catalog.current_setting('opengeni.workspace_id', true), ''
720
+ )::uuid
721
+ THEN
722
+ RAISE EXCEPTION 'personal document resolve scope mismatch' USING ERRCODE = '42501';
723
+ END IF;
724
+
725
+ INSERT INTO opengeni_private.personal_document_authority_capabilities (
726
+ backend_pid, transaction_id, capability_kind
727
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'resolve')
728
+ ON CONFLICT DO NOTHING;
729
+
730
+ SELECT admission.* INTO admission_row
731
+ FROM session_attempt_personal_document_admissions admission
732
+ WHERE admission.attempt_id = p_attempt_id
733
+ AND admission.account_id = p_account_id
734
+ AND admission.workspace_id = p_workspace_id
735
+ AND admission.session_id = p_session_id;
736
+ IF NOT FOUND THEN
737
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
738
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
739
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
740
+ AND capability_kind = 'resolve';
741
+ RETURN;
742
+ END IF;
743
+
744
+ IF caller_subject IS DISTINCT FROM admission_row.initiating_human_subject_id THEN
745
+ RAISE EXCEPTION 'personal document resolve initiating human mismatch'
746
+ USING ERRCODE = '42501';
747
+ END IF;
748
+
749
+ SELECT count(*)::integer INTO invalid_count
750
+ FROM session_attempt_personal_document_snapshots snapshot
751
+ WHERE snapshot.attempt_id = p_attempt_id
752
+ AND NOT (
753
+ EXISTS (
754
+ SELECT 1
755
+ FROM session_turn_attempts attempt
756
+ JOIN sessions session_value
757
+ ON session_value.id = attempt.session_id
758
+ AND session_value.account_id = attempt.account_id
759
+ AND session_value.workspace_id = attempt.workspace_id
760
+ JOIN session_turns turn_value
761
+ ON turn_value.id = attempt.turn_id
762
+ AND turn_value.account_id = attempt.account_id
763
+ AND turn_value.workspace_id = attempt.workspace_id
764
+ AND turn_value.session_id = attempt.session_id
765
+ WHERE attempt.id = snapshot.attempt_id
766
+ AND attempt.account_id = snapshot.account_id
767
+ AND attempt.workspace_id = snapshot.workspace_id
768
+ AND attempt.session_id = snapshot.session_id
769
+ AND attempt.turn_id = snapshot.turn_id
770
+ AND attempt.execution_generation = snapshot.execution_generation
771
+ AND attempt.state IN ('claimed', 'running')
772
+ AND attempt.closed_at IS NULL
773
+ AND attempt.quiesced_at IS NULL
774
+ AND session_value.active_turn_id = snapshot.turn_id
775
+ AND turn_value.active_attempt_id = snapshot.attempt_id
776
+ AND turn_value.execution_generation = snapshot.execution_generation
777
+ AND turn_value.status = 'running'
778
+ AND session_value.visibility = snapshot.session_visibility
779
+ AND session_value.authority_epoch = snapshot.session_authority_epoch
780
+ )
781
+ AND NOT EXISTS (
782
+ SELECT 1 FROM session_attempt_interruptions interruption
783
+ WHERE interruption.account_id = snapshot.account_id
784
+ AND interruption.workspace_id = snapshot.workspace_id
785
+ AND interruption.session_id = snapshot.session_id
786
+ AND interruption.attempt_id = snapshot.attempt_id
787
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
788
+ )
789
+ AND EXISTS (
790
+ SELECT 1 FROM organization_memberships membership
791
+ WHERE membership.id = snapshot.owner_organization_membership_id
792
+ AND membership.account_id = snapshot.account_id
793
+ AND membership.subject_id = admission_row.initiating_human_subject_id
794
+ AND membership.status = 'active'
795
+ AND membership.revoked_at IS NULL
796
+ AND membership.authorization_revision
797
+ = snapshot.membership_authorization_revision
798
+ AND (
799
+ membership.personal_workspace_id = snapshot.workspace_id
800
+ OR EXISTS (
801
+ SELECT 1 FROM workspace_memberships workspace_membership
802
+ WHERE workspace_membership.account_id = membership.account_id
803
+ AND workspace_membership.workspace_id = snapshot.workspace_id
804
+ AND workspace_membership.subject_id = membership.subject_id
805
+ )
806
+ )
807
+ )
808
+ AND EXISTS (
809
+ SELECT 1
810
+ FROM documents document_value
811
+ JOIN organization_user_resource_authorities authority
812
+ ON authority.id = document_value.authority_id
813
+ AND authority.account_id = document_value.account_id
814
+ AND authority.organization_membership_id
815
+ = document_value.owner_organization_membership_id
816
+ WHERE document_value.id = snapshot.document_id
817
+ AND document_value.account_id = snapshot.account_id
818
+ AND document_value.authority_kind = 'personal'
819
+ AND document_value.authority_workspace_id IS NULL
820
+ AND document_value.authority_subject_id
821
+ = admission_row.initiating_human_subject_id
822
+ AND document_value.owner_organization_membership_id
823
+ = snapshot.owner_organization_membership_id
824
+ AND document_value.authority_id = snapshot.authority_id
825
+ AND document_value.status = 'ready'
826
+ AND document_value.agent_access = true
827
+ AND authority.resource_kind = 'document'
828
+ AND authority.resource_id = document_value.id
829
+ AND authority.generation = snapshot.authority_generation
830
+ AND authority.status = 'active'
831
+ AND authority.revoked_at IS NULL
832
+ )
833
+ AND EXISTS (
834
+ SELECT 1 FROM organization_user_resource_grants grant_value
835
+ WHERE grant_value.id = snapshot.grant_id
836
+ AND grant_value.account_id = snapshot.account_id
837
+ AND grant_value.authority_id = snapshot.authority_id
838
+ AND grant_value.owner_organization_membership_id
839
+ = snapshot.owner_organization_membership_id
840
+ AND grant_value.workspace_id = snapshot.target_workspace_id
841
+ AND grant_value.action = 'document.read'
842
+ AND grant_value.mode = snapshot.grant_mode
843
+ AND grant_value.context = snapshot.grant_context
844
+ AND grant_value.generation = snapshot.grant_generation
845
+ AND grant_value.session_id IS NOT DISTINCT FROM snapshot.grant_session_id
846
+ AND grant_value.authority_epoch
847
+ IS NOT DISTINCT FROM snapshot.grant_authority_epoch
848
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
849
+ AND (
850
+ (
851
+ snapshot.grant_mode = 'once'
852
+ AND grant_value.status = 'consumed'
853
+ AND EXISTS (
854
+ SELECT 1 FROM personal_document_once_consumption_receipts receipt
855
+ WHERE receipt.grant_id = snapshot.grant_id
856
+ AND receipt.account_id = snapshot.account_id
857
+ AND receipt.attempt_id = snapshot.attempt_id
858
+ AND receipt.document_id = snapshot.document_id
859
+ AND receipt.authority_id = snapshot.authority_id
860
+ AND receipt.authority_generation = snapshot.authority_generation
861
+ AND receipt.grant_generation = snapshot.grant_generation
862
+ )
863
+ ) OR (
864
+ snapshot.grant_mode IN ('session', 'always')
865
+ AND grant_value.status = 'active'
866
+ )
867
+ )
868
+ )
869
+ );
870
+ IF invalid_count <> 0 THEN
871
+ RAISE EXCEPTION 'personal document authority snapshot is no longer live'
872
+ USING ERRCODE = '42501';
873
+ END IF;
874
+
875
+ SELECT count(*)::integer INTO actual_count
876
+ FROM session_attempt_personal_document_snapshots snapshot
877
+ WHERE snapshot.attempt_id = p_attempt_id;
878
+ IF actual_count <> admission_row.document_count THEN
879
+ RAISE EXCEPTION 'personal document snapshot collection is incomplete'
880
+ USING ERRCODE = '42501';
881
+ END IF;
882
+
883
+ RETURN QUERY
884
+ SELECT snapshot.document_id
885
+ FROM session_attempt_personal_document_snapshots snapshot
886
+ WHERE snapshot.attempt_id = p_attempt_id
887
+ ORDER BY snapshot.document_id;
888
+
889
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
890
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
891
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
892
+ AND capability_kind = 'resolve';
893
+ RETURN;
894
+ EXCEPTION WHEN OTHERS THEN
895
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
896
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
897
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
898
+ AND capability_kind = 'resolve';
899
+ RAISE;
900
+ END
901
+ $body$;
902
+ $ddl$, data_schema);
903
+
904
+ EXECUTE format($ddl$
905
+ CREATE OR REPLACE FUNCTION %1$I.resolve_document_original_file(
906
+ p_account_id uuid,
907
+ p_request_workspace_id uuid,
908
+ p_actor_id text,
909
+ p_document_id uuid
910
+ ) RETURNS SETOF %1$I.files
911
+ LANGUAGE plpgsql
912
+ SECURITY DEFINER
913
+ SET search_path = %1$I, pg_catalog
914
+ AS $body$
915
+ DECLARE
916
+ document_row %1$I.documents%%ROWTYPE;
917
+ authority_row %1$I.organization_user_resource_authorities%%ROWTYPE;
918
+ owner_membership_row %1$I.organization_memberships%%ROWTYPE;
919
+ BEGIN
920
+ IF p_account_id IS DISTINCT FROM nullif(
921
+ pg_catalog.current_setting('opengeni.account_id', true), ''
922
+ )::uuid
923
+ OR p_request_workspace_id IS DISTINCT FROM nullif(
924
+ pg_catalog.current_setting('opengeni.workspace_id', true), ''
925
+ )::uuid
926
+ OR p_actor_id IS NULL
927
+ OR p_actor_id IS DISTINCT FROM nullif(
928
+ pg_catalog.current_setting('opengeni.subject_id', true), ''
929
+ )
930
+ OR p_document_id IS NULL
931
+ THEN
932
+ RAISE EXCEPTION 'document original-file scope mismatch' USING ERRCODE = '42501';
933
+ END IF;
934
+
935
+ INSERT INTO opengeni_private.personal_document_authority_capabilities (
936
+ backend_pid, transaction_id, capability_kind
937
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'resolve')
938
+ ON CONFLICT DO NOTHING;
939
+
940
+ SELECT document_value.* INTO document_row
941
+ FROM %1$I.documents document_value
942
+ WHERE document_value.id = p_document_id
943
+ AND document_value.account_id = p_account_id
944
+ AND (
945
+ (
946
+ document_value.authority_kind = 'organization'
947
+ AND document_value.authority_workspace_id IS NULL
948
+ AND document_value.authority_subject_id IS NULL
949
+ ) OR (
950
+ document_value.authority_kind = 'workspace'
951
+ AND document_value.authority_workspace_id = p_request_workspace_id
952
+ AND document_value.authority_subject_id IS NULL
953
+ ) OR (
954
+ document_value.authority_kind = 'personal'
955
+ AND document_value.authority_subject_id = p_actor_id
956
+ AND (
957
+ (
958
+ document_value.authority_id IS NULL
959
+ AND document_value.owner_organization_membership_id IS NULL
960
+ AND document_value.authority_workspace_id = p_request_workspace_id
961
+ ) OR (
962
+ document_value.authority_id IS NOT NULL
963
+ AND document_value.owner_organization_membership_id IS NOT NULL
964
+ AND document_value.authority_workspace_id IS NULL
965
+ )
966
+ )
967
+ )
968
+ )
969
+ FOR SHARE;
970
+ IF NOT FOUND THEN
971
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
972
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
973
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
974
+ AND capability_kind = 'resolve';
975
+ RETURN;
976
+ END IF;
977
+
978
+ IF document_row.authority_kind = 'personal' AND document_row.authority_id IS NOT NULL THEN
979
+ SELECT authority.* INTO authority_row
980
+ FROM %1$I.organization_user_resource_authorities authority
981
+ WHERE authority.id = document_row.authority_id
982
+ AND authority.account_id = document_row.account_id
983
+ AND authority.organization_membership_id
984
+ = document_row.owner_organization_membership_id
985
+ AND authority.resource_kind = 'document'
986
+ AND authority.resource_id = document_row.id
987
+ AND authority.status = 'active'
988
+ AND authority.revoked_at IS NULL
989
+ FOR SHARE;
990
+ IF NOT FOUND THEN
991
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
992
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
993
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
994
+ AND capability_kind = 'resolve';
995
+ RETURN;
996
+ END IF;
997
+
998
+ SELECT membership.* INTO owner_membership_row
999
+ FROM %1$I.organization_memberships membership
1000
+ WHERE membership.id = authority_row.organization_membership_id
1001
+ AND membership.account_id = authority_row.account_id
1002
+ AND membership.subject_id = p_actor_id
1003
+ AND membership.status = 'active'
1004
+ AND membership.revoked_at IS NULL
1005
+ FOR SHARE;
1006
+ IF NOT FOUND THEN
1007
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
1008
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
1009
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
1010
+ AND capability_kind = 'resolve';
1011
+ RETURN;
1012
+ END IF;
1013
+
1014
+ IF owner_membership_row.personal_workspace_id IS DISTINCT FROM p_request_workspace_id THEN
1015
+ PERFORM 1
1016
+ FROM %1$I.workspace_memberships workspace_membership
1017
+ WHERE workspace_membership.account_id = p_account_id
1018
+ AND workspace_membership.workspace_id = p_request_workspace_id
1019
+ AND workspace_membership.subject_id = p_actor_id
1020
+ FOR SHARE;
1021
+ IF NOT FOUND THEN
1022
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
1023
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
1024
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
1025
+ AND capability_kind = 'resolve';
1026
+ RETURN;
1027
+ END IF;
1028
+ END IF;
1029
+ END IF;
1030
+
1031
+ RETURN QUERY
1032
+ SELECT file_value.*
1033
+ FROM %1$I.files file_value
1034
+ WHERE file_value.id = document_row.file_id
1035
+ AND file_value.account_id = document_row.account_id
1036
+ AND file_value.workspace_id = document_row.origin_workspace_id
1037
+ AND %1$I.google_drive_file_authorized(
1038
+ p_account_id, p_request_workspace_id, p_actor_id, file_value.id
1039
+ )
1040
+ FOR SHARE;
1041
+
1042
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
1043
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
1044
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
1045
+ AND capability_kind = 'resolve';
1046
+ RETURN;
1047
+ EXCEPTION WHEN OTHERS THEN
1048
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
1049
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
1050
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
1051
+ AND capability_kind = 'resolve';
1052
+ RAISE;
1053
+ END
1054
+ $body$;
1055
+ $ddl$, data_schema);
1056
+
1057
+ EXECUTE format($ddl$
1058
+ CREATE OR REPLACE FUNCTION %1$I.admit_session_attempt_personal_documents()
1059
+ RETURNS trigger
1060
+ LANGUAGE plpgsql
1061
+ SECURITY DEFINER
1062
+ SET search_path = %1$I, pg_catalog
1063
+ AS $body$
1064
+ BEGIN
1065
+ PERFORM prepare_session_attempt_personal_document_reads(
1066
+ NEW.account_id, NEW.workspace_id, NEW.session_id, NEW.id
1067
+ );
1068
+ RETURN NEW;
1069
+ END
1070
+ $body$;
1071
+ $ddl$, data_schema);
1072
+
1073
+ EXECUTE format($ddl$
1074
+ CREATE OR REPLACE FUNCTION opengeni_private.apply_document_authority()
1075
+ RETURNS trigger
1076
+ LANGUAGE plpgsql
1077
+ SET search_path = %1$I, pg_catalog
1078
+ AS $body$
1079
+ BEGIN
1080
+ IF TG_OP = 'UPDATE' AND (
1081
+ NEW.authority_kind IS DISTINCT FROM OLD.authority_kind
1082
+ OR NEW.authority_workspace_id IS DISTINCT FROM OLD.authority_workspace_id
1083
+ OR NEW.authority_subject_id IS DISTINCT FROM OLD.authority_subject_id
1084
+ OR NEW.authority_id IS DISTINCT FROM OLD.authority_id
1085
+ OR NEW.owner_organization_membership_id
1086
+ IS DISTINCT FROM OLD.owner_organization_membership_id
1087
+ OR NEW.origin_workspace_id IS DISTINCT FROM OLD.origin_workspace_id
1088
+ OR NEW.created_by IS DISTINCT FROM OLD.created_by
1089
+ OR NEW.visibility IS DISTINCT FROM OLD.visibility
1090
+ ) THEN
1091
+ RAISE EXCEPTION 'document authority is immutable';
1092
+ END IF;
1093
+ IF NEW.authority_kind IS NULL OR (
1094
+ NEW.authority_kind = 'workspace'
1095
+ AND NEW.authority_workspace_id IS NULL
1096
+ AND NEW.visibility = 'private'
1097
+ ) THEN
1098
+ NEW.authority_kind := CASE
1099
+ WHEN NEW.visibility = 'private' THEN 'personal'
1100
+ ELSE 'workspace'
1101
+ END;
1102
+ END IF;
1103
+ NEW.origin_workspace_id := NEW.workspace_id;
1104
+ CASE NEW.authority_kind
1105
+ WHEN 'organization' THEN
1106
+ NEW.authority_workspace_id := NULL;
1107
+ NEW.authority_subject_id := NULL;
1108
+ NEW.authority_id := NULL;
1109
+ NEW.owner_organization_membership_id := NULL;
1110
+ NEW.visibility := 'workspace';
1111
+ WHEN 'workspace' THEN
1112
+ NEW.authority_workspace_id := NEW.workspace_id;
1113
+ NEW.authority_subject_id := NULL;
1114
+ NEW.authority_id := NULL;
1115
+ NEW.owner_organization_membership_id := NULL;
1116
+ NEW.visibility := 'workspace';
1117
+ WHEN 'personal' THEN
1118
+ NEW.authority_subject_id := coalesce(
1119
+ nullif(btrim(NEW.authority_subject_id), ''),
1120
+ nullif(btrim(NEW.created_by), '')
1121
+ );
1122
+ IF NEW.authority_id IS NULL THEN
1123
+ NEW.authority_workspace_id := NEW.workspace_id;
1124
+ NEW.owner_organization_membership_id := NULL;
1125
+ ELSE
1126
+ NEW.authority_workspace_id := NULL;
1127
+ END IF;
1128
+ NEW.visibility := 'private';
1129
+ ELSE
1130
+ RAISE EXCEPTION 'invalid document authority kind: %%', NEW.authority_kind;
1131
+ END CASE;
1132
+ RETURN NEW;
1133
+ END
1134
+ $body$;
1135
+ $ddl$, data_schema);
1136
+
1137
+ EXECUTE format($ddl$
1138
+ CREATE OR REPLACE FUNCTION opengeni_private.apply_document_chunk_authority()
1139
+ RETURNS trigger
1140
+ LANGUAGE plpgsql
1141
+ SET search_path = %1$I, pg_catalog
1142
+ AS $body$
1143
+ DECLARE parent %1$I.documents%%ROWTYPE;
1144
+ BEGIN
1145
+ IF TG_OP = 'UPDATE' AND (
1146
+ NEW.authority_kind IS DISTINCT FROM OLD.authority_kind
1147
+ OR NEW.authority_workspace_id IS DISTINCT FROM OLD.authority_workspace_id
1148
+ OR NEW.authority_subject_id IS DISTINCT FROM OLD.authority_subject_id
1149
+ OR NEW.authority_id IS DISTINCT FROM OLD.authority_id
1150
+ OR NEW.owner_organization_membership_id
1151
+ IS DISTINCT FROM OLD.owner_organization_membership_id
1152
+ OR NEW.document_id IS DISTINCT FROM OLD.document_id
1153
+ ) THEN
1154
+ RAISE EXCEPTION 'document chunk authority is immutable';
1155
+ END IF;
1156
+ SELECT * INTO parent FROM %1$I.documents WHERE id = NEW.document_id;
1157
+ IF NOT FOUND
1158
+ OR parent.account_id IS DISTINCT FROM NEW.account_id
1159
+ OR parent.workspace_id IS DISTINCT FROM NEW.workspace_id
1160
+ OR parent.base_id IS DISTINCT FROM NEW.base_id
1161
+ OR parent.file_id IS DISTINCT FROM NEW.file_id
1162
+ THEN
1163
+ RAISE EXCEPTION 'document chunk parent identity mismatch';
1164
+ END IF;
1165
+ NEW.authority_kind := parent.authority_kind;
1166
+ NEW.authority_workspace_id := parent.authority_workspace_id;
1167
+ NEW.authority_subject_id := parent.authority_subject_id;
1168
+ NEW.authority_id := parent.authority_id;
1169
+ NEW.owner_organization_membership_id := parent.owner_organization_membership_id;
1170
+ RETURN NEW;
1171
+ END
1172
+ $body$;
1173
+ $ddl$, data_schema);
1174
+
1175
+ EXECUTE format($ddl$
1176
+ CREATE OR REPLACE FUNCTION %1$I.revoke_personal_document_authority_after_delete()
1177
+ RETURNS trigger
1178
+ LANGUAGE plpgsql
1179
+ SECURITY DEFINER
1180
+ SET search_path = %1$I, pg_catalog
1181
+ AS $body$
1182
+ BEGIN
1183
+ IF OLD.authority_kind <> 'personal' OR OLD.authority_id IS NULL THEN
1184
+ RETURN OLD;
1185
+ END IF;
1186
+ INSERT INTO opengeni_private.personal_document_authority_capabilities (
1187
+ backend_pid, transaction_id, capability_kind
1188
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'write')
1189
+ ON CONFLICT DO NOTHING;
1190
+ UPDATE organization_user_resource_grants
1191
+ SET status = 'revoked', generation = generation + 1,
1192
+ revoked_at = coalesce(revoked_at, clock_timestamp()),
1193
+ updated_at = clock_timestamp()
1194
+ WHERE authority_id = OLD.authority_id AND account_id = OLD.account_id
1195
+ AND status IN ('active', 'consumed');
1196
+ UPDATE organization_user_resource_authorities
1197
+ SET status = 'revoked', generation = generation + 1,
1198
+ revoked_at = clock_timestamp(), updated_at = clock_timestamp()
1199
+ WHERE id = OLD.authority_id AND account_id = OLD.account_id
1200
+ AND status <> 'revoked';
1201
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
1202
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
1203
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
1204
+ AND capability_kind = 'write';
1205
+ RETURN OLD;
1206
+ EXCEPTION WHEN OTHERS THEN
1207
+ DELETE FROM opengeni_private.personal_document_authority_capabilities
1208
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
1209
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
1210
+ AND capability_kind = 'write';
1211
+ RAISE;
1212
+ END
1213
+ $body$;
1214
+ $ddl$, data_schema);
1215
+ END
1216
+ $personal_document_functions$;
1217
+
1218
+ DROP TRIGGER "documents_authority_guard" ON "documents";
1219
+ CREATE TRIGGER "documents_authority_guard"
1220
+ BEFORE INSERT OR UPDATE ON "documents"
1221
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.apply_document_authority();
1222
+
1223
+ DROP TRIGGER "document_chunks_authority_guard" ON "document_chunks";
1224
+ CREATE TRIGGER "document_chunks_authority_guard"
1225
+ BEFORE INSERT OR UPDATE ON "document_chunks"
1226
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.apply_document_chunk_authority();
1227
+
1228
+ CREATE TRIGGER "documents_personal_authority_revoke"
1229
+ AFTER DELETE ON "documents"
1230
+ FOR EACH ROW EXECUTE FUNCTION revoke_personal_document_authority_after_delete();
1231
+
1232
+ CREATE TRIGGER "session_attempt_personal_document_admission"
1233
+ AFTER INSERT ON "session_turn_attempts"
1234
+ FOR EACH ROW EXECUTE FUNCTION admit_session_attempt_personal_documents();
1235
+
1236
+ REVOKE ALL ON FUNCTION create_personal_document_authority(uuid, uuid, uuid) FROM PUBLIC;
1237
+ REVOKE ALL ON FUNCTION
1238
+ prepare_session_attempt_personal_document_reads(uuid, uuid, uuid, uuid) FROM PUBLIC;
1239
+ REVOKE ALL ON FUNCTION
1240
+ resolve_session_attempt_personal_document_reads(uuid, uuid, uuid, uuid) FROM PUBLIC;
1241
+ REVOKE ALL ON FUNCTION
1242
+ resolve_document_original_file(uuid, uuid, text, uuid) FROM PUBLIC;
1243
+ REVOKE ALL ON FUNCTION admit_session_attempt_personal_documents() FROM PUBLIC;
1244
+
1245
+ DO $personal_document_runtime_grants$
1246
+ BEGIN
1247
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1248
+ GRANT EXECUTE ON FUNCTION
1249
+ opengeni_private.personal_document_authority_capability_active(text)
1250
+ TO opengeni_app;
1251
+ GRANT EXECUTE ON FUNCTION create_personal_document_authority(uuid, uuid, uuid)
1252
+ TO opengeni_app;
1253
+ GRANT EXECUTE ON FUNCTION
1254
+ resolve_session_attempt_personal_document_reads(uuid, uuid, uuid, uuid)
1255
+ TO opengeni_app;
1256
+ GRANT EXECUTE ON FUNCTION
1257
+ resolve_document_original_file(uuid, uuid, text, uuid)
1258
+ TO opengeni_app;
1259
+ REVOKE ALL ON TABLE session_attempt_personal_document_admissions FROM opengeni_app;
1260
+ REVOKE ALL ON TABLE session_attempt_personal_document_snapshots FROM opengeni_app;
1261
+ REVOKE ALL ON TABLE personal_document_once_consumption_receipts FROM opengeni_app;
1262
+ REVOKE ALL ON TABLE organization_user_resource_authorities FROM opengeni_app;
1263
+ REVOKE ALL ON TABLE organization_user_resource_grants FROM opengeni_app;
1264
+ END IF;
1265
+ END
1266
+ $personal_document_runtime_grants$;
1267
+
1268
+ COMMENT ON COLUMN "documents"."origin_workspace_id" IS
1269
+ 'Upload provenance only. It never narrows activated organization-user document authority.';
1270
+ COMMENT ON COLUMN "documents"."authority_id" IS
1271
+ 'Common organization-user resource authority for activated personal Documents; null on legacy anchored personal rows.';
1272
+ COMMENT ON TABLE "session_attempt_personal_document_snapshots" IS
1273
+ 'Immutable exact-attempt personal Document grants; shared-session snapshots require the grant lifecycle acknowledgement.';