@opengeni/db 1.0.1 → 3.2.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 (271) 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-4SR4HKKA.js → chunk-2EH4WRHL.js} +19 -11
  7. package/dist/chunk-2EH4WRHL.js.map +1 -0
  8. package/dist/{chunk-CUPM3FGB.js → chunk-2PRS4J5Q.js} +3 -3
  9. package/dist/chunk-6HMM2R5T.js +390 -0
  10. package/dist/chunk-6HMM2R5T.js.map +1 -0
  11. package/dist/{chunk-ZWREYEWA.js → chunk-AZHW7R5V.js} +56 -11
  12. package/dist/chunk-AZHW7R5V.js.map +1 -0
  13. package/dist/{chunk-LTG3S6ZY.js → chunk-E6CLMKEE.js} +2 -2
  14. package/dist/{chunk-6ZYKCRFO.js → chunk-KRLPEMLH.js} +7591 -4417
  15. package/dist/chunk-KRLPEMLH.js.map +1 -0
  16. package/dist/chunk-NAB6FKFN.js +516 -0
  17. package/dist/chunk-NAB6FKFN.js.map +1 -0
  18. package/dist/{chunk-SK7YYHBI.js → chunk-SIBYBA2D.js} +46 -1
  19. package/dist/chunk-SIBYBA2D.js.map +1 -0
  20. package/dist/chunk-UZNXG44K.js +4508 -0
  21. package/dist/chunk-UZNXG44K.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 +85 -0
  33. package/dist/connection-token-resolver.d.ts +30 -1
  34. package/dist/database.d.ts +23 -4
  35. package/dist/editable-artifact-durable-export.js +2 -2
  36. package/dist/editable-artifacts.d.ts +2 -2
  37. package/dist/editable-artifacts.js +3 -3
  38. package/dist/governed-learning-activation-schema.d.ts +984 -0
  39. package/dist/governed-learning-activation.d.ts +98 -0
  40. package/dist/governed-learning-evaluator-schema.d.ts +633 -0
  41. package/dist/governed-learning-evaluator.d.ts +28 -0
  42. package/dist/index.d.ts +1606 -81
  43. package/dist/index.js +21898 -8147
  44. package/dist/index.js.map +1 -1
  45. package/dist/insights.d.ts +18 -0
  46. package/dist/integration-bindings.d.ts +1 -0
  47. package/dist/integration-facets.d.ts +30 -0
  48. package/dist/interaction-placement-loss.d.ts +22 -0
  49. package/dist/managed-human-provisioning.d.ts +25 -0
  50. package/dist/memory-domain.d.ts +3 -3
  51. package/dist/migrate.d.ts +11 -1
  52. package/dist/migrate.js +1 -1
  53. package/dist/new-session-drafts.d.ts +43 -5
  54. package/dist/organization-membership-backfill.d.ts +174 -0
  55. package/dist/organization-membership-lifecycle.d.ts +162 -0
  56. package/dist/pr-review.d.ts +107 -0
  57. package/dist/preference-registry-schema.d.ts +2 -0
  58. package/dist/preference-registry.d.ts +2 -0
  59. package/dist/provision-roles.js +1 -1
  60. package/dist/runtime-posture.d.ts +29 -8
  61. package/dist/schema.d.ts +24914 -13703
  62. package/dist/schema.js +99 -1
  63. package/dist/scoped-knowledge-schema.d.ts +52 -1
  64. package/dist/session-background-commands.d.ts +125 -0
  65. package/dist/session-background-commands.js +34 -0
  66. package/dist/session-background-commands.js.map +1 -0
  67. package/dist/session-control.d.ts +156 -3
  68. package/dist/session-goal-pacing.d.ts +68 -0
  69. package/dist/session-queue-commands.d.ts +43 -4
  70. package/dist/session-realtime-ledger.d.ts +2 -0
  71. package/dist/session-tenancy.d.ts +71 -1
  72. package/dist/session-tenancy.js +25 -3
  73. package/dist/session-tool-call-settlement.d.ts +9 -4
  74. package/dist/slack-routing-personal-workspace.d.ts +23 -0
  75. package/dist/slack-routing.d.ts +268 -0
  76. package/dist/slack-task-policy-schema.d.ts +34 -0
  77. package/dist/slack-task-policy.d.ts +8 -0
  78. package/dist/slack-user-link-access.d.ts +28 -0
  79. package/dist/task-notes-schema.d.ts +594 -0
  80. package/dist/task-notes.d.ts +39 -0
  81. package/dist/turn-initiator.d.ts +2 -0
  82. package/dist/user-resource-authority.d.ts +109 -0
  83. package/dist/video-generation.js +3 -3
  84. package/dist/workspace-authority.d.ts +81 -0
  85. package/dist/workspace-instruction-policies-schema.d.ts +279 -0
  86. package/dist/workspace-instruction-policies.d.ts +62 -0
  87. package/dist/workspace-learning-policy.d.ts +1 -0
  88. package/dist/workspace-membership-permissions.d.ts +2 -0
  89. package/drizzle/0240_enrollment_connection_authority.sql +39 -0
  90. package/drizzle/0241_atomic_personal_resource_delegation.sql +1000 -0
  91. package/drizzle/0241_enrollment_agent_runtime.sql +70 -0
  92. package/drizzle/0244_slack_app_home_refresh_queue.sql +120 -0
  93. package/drizzle/0245_model_context_contribution_facts.sql +88 -0
  94. package/drizzle/0246_integration_personal_instance_authority.sql +70 -0
  95. package/drizzle/0247_terraform_stacks_provenance_repair.sql +428 -0
  96. package/drizzle/0248_terraform_stacks_component_resolution_fence.sql +83 -0
  97. package/drizzle/0249_personal_resource_delegation_authority_correction.sql +662 -0
  98. package/drizzle/0250_direct_retained_process_owner_liveness.sql +199 -0
  99. package/drizzle/0251_connected_machine_operation_policy.sql +45 -0
  100. package/drizzle/0252_scheduled_personal_resource_delegation.sql +1867 -0
  101. package/drizzle/0253_common_user_resource_authority_lifecycle.sql +1555 -0
  102. package/drizzle/0254_scoped_variable_set_authority.sql +1255 -0
  103. package/drizzle/0255_company_brain_governed_write_proposals.sql +524 -0
  104. package/drizzle/0256_connection_authority_delegation.sql +703 -0
  105. package/drizzle/0257_goal_revision_decisions_and_root_constraints.sql +436 -0
  106. package/drizzle/0258_three_scope_document_knowledge_authority.sql +1273 -0
  107. package/drizzle/0259_company_brain_context_selection_receipts.sql +869 -0
  108. package/drizzle/0260_task_note_knowledge_promotion.sql +820 -0
  109. package/drizzle/0261_preference_knowledge_proposal_actor_binding.sql +471 -0
  110. package/drizzle/0262_scoped_connected_machines_and_rigs.sql +1700 -0
  111. package/drizzle/0263_organization_membership_lifecycle.sql +3284 -0
  112. package/drizzle/0264_connection_authority_runtime_activation.sql +1084 -0
  113. package/drizzle/0266_company_brain_context_receipt_inspection.sql +156 -0
  114. package/drizzle/0268_governed_learning_decision_receipts.sql +825 -0
  115. package/drizzle/0269_governed_learning_activation_controller.sql +1874 -0
  116. package/drizzle/0270_governed_learning_history_inspection.sql +170 -0
  117. package/drizzle/0271_company_brain_retrieval_only_default.sql +524 -0
  118. package/drizzle/0272_human_confirmed_learning_activation.sql +591 -0
  119. package/drizzle/0273_scheduled_variable_set_materialization.sql +268 -0
  120. package/drizzle/0274_human_confirmed_knowledge_review.sql +310 -0
  121. package/drizzle/0275_scheduled_connection_authority.sql +5527 -0
  122. package/drizzle/0276_onboarding_proposal_initiating_human_guc.sql +198 -0
  123. package/drizzle/0277_workspace_writer_authority_attribution.sql +393 -0
  124. package/drizzle/0278_workspace_membership_removal_fencing.sql +1174 -0
  125. package/drizzle/0279_workspace_connection_use_lane.sql +343 -0
  126. package/drizzle/0280_connection_and_variable_set_audit_attribution.sql +773 -0
  127. package/drizzle/0281_viewer_holder_authority_claims.sql +52 -0
  128. package/drizzle/0282_variable_set_session_attach_attribution.sql +178 -0
  129. package/drizzle/0283_editable_spreadsheet_authored_state.sql +82 -0
  130. package/drizzle/0284_truthful_human_confirmed_review_reason.sql +930 -0
  131. package/drizzle/0285_organization_tenancy_inventory.sql +329 -0
  132. package/drizzle/0286_widen_task_note_expiry_ceiling.sql +391 -0
  133. package/drizzle/0287_open_suffix_pending_tool_calls.sql +18 -0
  134. package/drizzle/0288_attached_browser_reenrollment.sql +64 -0
  135. package/drizzle/0289_session_composer_policy_authority.sql +597 -0
  136. package/drizzle/0290_organization_membership_backfill.sql +268 -0
  137. package/drizzle/0291_resource_authority_classification_assertion.sql +371 -0
  138. package/drizzle/0292_truthful_tenancy_inventory_counters.sql +291 -0
  139. package/drizzle/0293_confirm_time_rule_rebaseline.sql +615 -0
  140. package/drizzle/0294_preference_activation_authority.sql +237 -0
  141. package/drizzle/0295_retire_legacy_standing_memory_mode.sql +476 -0
  142. package/drizzle/0296_force_rls_backfill_noop_repair.sql +215 -0
  143. package/drizzle/0297_session_ownership_classification_and_backfill.sql +752 -0
  144. package/drizzle/0298_organization_tenancy_parity.sql +609 -0
  145. package/drizzle/0299_organization_membership_lock_order.sql +291 -0
  146. package/drizzle/0300_tenancy_backfill_ledger.sql +459 -0
  147. package/drizzle/0301_session_snapshot_and_pin_visibility.sql +181 -0
  148. package/drizzle/0302_personal_workspace_session_ownership.sql +303 -0
  149. package/drizzle/0303_session_tenancy_product_activation.sql +919 -0
  150. package/drizzle/0304_personal_workspace_private_session_reads.sql +97 -0
  151. package/drizzle/0305_personal_resource_grant_management.sql +584 -0
  152. package/drizzle/0306_atomic_personal_resource_attachments.sql +1374 -0
  153. package/drizzle/0307_session_attention_state.sql +51 -0
  154. package/drizzle/0308_session_archives.sql +35 -0
  155. package/drizzle/0309_channel_project_pins.sql +6 -0
  156. package/drizzle/0310_channel_project_order.sql +27 -0
  157. package/drizzle/0311_company_scope_and_private_session_create.sql +656 -0
  158. package/drizzle/0312_quiescent_session_tree_deletion.sql +39 -0
  159. package/drizzle/0313_private_child_session_authority.sql +365 -0
  160. package/drizzle/0314_unregistered_organization_invitations.sql +606 -0
  161. package/drizzle/0315_personal_github_repository_selection.sql +624 -0
  162. package/drizzle/0316_human_confirmed_activation_resumed_generation.sql +869 -0
  163. package/drizzle/0317_session_goal_continuation_hold.sql +64 -0
  164. package/drizzle/0318_session_turn_startup_milestones.sql +115 -0
  165. package/drizzle/0319_event_triggered_automations.sql +254 -0
  166. package/drizzle/0320_pr_review_pack.sql +113 -0
  167. package/drizzle/0321_slack_bot_environment_display_name.sql +15 -0
  168. package/drizzle/0322_session_turns_unclaimed_prompt_trigger_index.sql +5 -0
  169. package/drizzle/0323_organization_private_session_enablement.sql +489 -0
  170. package/drizzle/0324_human_confirmed_company_profile_agent_admin.sql +856 -0
  171. package/drizzle/0325_child_lifecycle_notices.sql +44 -0
  172. package/drizzle/0326_interaction_operation_error_codes.sql +62 -0
  173. package/drizzle/0327_slack_first_task_hint.sql +29 -0
  174. package/drizzle/0328_session_background_commands.sql +590 -0
  175. package/drizzle/0329_slack_orchestration_delivery_events.sql +91 -0
  176. package/drizzle/0330_api_key_descriptions.sql +20 -0
  177. package/drizzle/0331_managed_organization_creation.sql +163 -0
  178. package/drizzle/0332_organization_shared_workspace_control_plane.sql +539 -0
  179. package/drizzle/0333_session_turn_prompt_routing.sql +45 -0
  180. package/drizzle/0334_connected_machine_workspace_root.sql +99 -0
  181. package/drizzle/0335_slack_workspace_routing.sql +460 -0
  182. package/drizzle/0336_atomic_session_fork_visibility.sql +667 -0
  183. package/drizzle/0337_slack_routed_action_handles.sql +189 -0
  184. package/drizzle/0338_atomic_connected_machine_attachments.sql +410 -0
  185. package/drizzle/0339_document_authority_reclassification.sql +1153 -0
  186. package/drizzle/0340_tenancy_backfill_activation_evidence.sql +1347 -0
  187. package/drizzle/0341_slack_routing_probe_organization_fence.sql +109 -0
  188. package/drizzle/0342_slack_route_prompt_single_pending.sql +32 -0
  189. package/drizzle/0343_personal_document_force_rls_lock_repair.sql +146 -0
  190. package/drizzle/0344_private_session_visibility_transition_gate.sql +85 -0
  191. package/drizzle/0345_tenant_scoped_session_tenancy_fence.sql +2839 -0
  192. package/drizzle/0346_document_migration_audit_surface.sql +454 -0
  193. package/drizzle/0347_connection_authority_convergence_evidence.sql +396 -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 +167 -0
  212. package/src/connection-token-resolver.ts +263 -31
  213. package/src/database.ts +87 -2
  214. package/src/editable-artifacts-schema.ts +10 -5
  215. package/src/editable-artifacts.ts +21 -11
  216. package/src/governed-learning-activation-schema.ts +121 -0
  217. package/src/governed-learning-activation.ts +588 -0
  218. package/src/governed-learning-evaluator-schema.ts +72 -0
  219. package/src/governed-learning-evaluator.ts +217 -0
  220. package/src/index.ts +11676 -1550
  221. package/src/insights.ts +107 -0
  222. package/src/integration-bindings.ts +88 -6
  223. package/src/integration-facets.ts +287 -84
  224. package/src/interaction-placement-loss.ts +234 -0
  225. package/src/managed-human-provisioning.ts +116 -0
  226. package/src/memory-domain.ts +27 -32
  227. package/src/migrate.ts +77 -1
  228. package/src/new-session-drafts.ts +229 -35
  229. package/src/organization-membership-backfill.ts +735 -0
  230. package/src/organization-membership-lifecycle.ts +995 -0
  231. package/src/pr-review.ts +890 -0
  232. package/src/preference-registry.ts +31 -1
  233. package/src/provision-roles.ts +905 -6
  234. package/src/runtime-posture-cli.ts +2 -0
  235. package/src/runtime-posture.ts +1317 -72
  236. package/src/schema.ts +3316 -428
  237. package/src/scoped-knowledge-schema.ts +16 -8
  238. package/src/scoped-knowledge.ts +1 -1
  239. package/src/session-background-commands.ts +759 -0
  240. package/src/session-control.ts +640 -74
  241. package/src/session-goal-pacing.ts +126 -0
  242. package/src/session-queue-commands.ts +441 -85
  243. package/src/session-realtime-context.ts +4 -6
  244. package/src/session-realtime-ledger.ts +28 -14
  245. package/src/session-realtime-mirror.ts +1 -1
  246. package/src/session-tenancy.ts +410 -49
  247. package/src/session-tool-call-settlement.ts +38 -29
  248. package/src/slack-routing-personal-workspace.ts +67 -0
  249. package/src/slack-routing.ts +867 -0
  250. package/src/slack-task-policy-schema.ts +5 -0
  251. package/src/slack-task-policy.ts +18 -0
  252. package/src/slack-user-link-access.ts +138 -11
  253. package/src/task-notes-schema.ts +74 -0
  254. package/src/task-notes.ts +124 -2
  255. package/src/turn-initiator.ts +8 -0
  256. package/src/user-resource-authority.ts +377 -0
  257. package/src/workspace-authority.ts +160 -0
  258. package/src/workspace-instruction-policies-schema.ts +53 -1
  259. package/src/workspace-instruction-policies.ts +528 -77
  260. package/src/workspace-learning-policy.ts +21 -5
  261. package/src/workspace-membership-permissions.ts +9 -0
  262. package/dist/chunk-4SR4HKKA.js.map +0 -1
  263. package/dist/chunk-6ZYKCRFO.js.map +0 -1
  264. package/dist/chunk-NXAFLCQX.js +0 -2468
  265. package/dist/chunk-NXAFLCQX.js.map +0 -1
  266. package/dist/chunk-SK7YYHBI.js.map +0 -1
  267. package/dist/chunk-WD5ROSLL.js +0 -117
  268. package/dist/chunk-WD5ROSLL.js.map +0 -1
  269. package/dist/chunk-ZWREYEWA.js.map +0 -1
  270. /package/dist/{chunk-CUPM3FGB.js.map → chunk-2PRS4J5Q.js.map} +0 -0
  271. /package/dist/{chunk-LTG3S6ZY.js.map → chunk-E6CLMKEE.js.map} +0 -0
@@ -0,0 +1,1555 @@
1
+ -- deployment-mode: rolling
2
+ -- Activate generic owner-only user-resource grant lifecycle and
3
+ -- correct direct/scheduled personal-resource authority so workspace location
4
+ -- and origin are provenance/lifecycle facts, never ownership authority.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ CREATE UNIQUE INDEX IF NOT EXISTS "organization_user_resource_grants_active_identity_uq"
10
+ ON "organization_user_resource_grants" (
11
+ "account_id", "authority_id", "workspace_id", "action", "mode", "context",
12
+ coalesce("session_id", '00000000-0000-0000-0000-000000000000'::uuid),
13
+ coalesce("authority_epoch", 0)
14
+ ) WHERE "status" = 'active';
15
+
16
+ CREATE OR REPLACE FUNCTION list_self_user_resource_authorities(
17
+ p_account_id uuid
18
+ ) RETURNS TABLE (
19
+ authority_id uuid, resource_kind text, authority_generation bigint,
20
+ authority_status text, grant_id uuid, target_workspace_id uuid,
21
+ target_session_id uuid, action text, grant_mode text, grant_context text,
22
+ grant_generation bigint, grant_status text, expires_at timestamptz
23
+ )
24
+ LANGUAGE plpgsql
25
+ SECURITY DEFINER
26
+ SET search_path = pg_catalog, pg_temp
27
+ AS $body$
28
+ #variable_conflict use_column
29
+ DECLARE
30
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
31
+ owner_membership_id uuid;
32
+ BEGIN
33
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
34
+ OR caller_subject IS NULL
35
+ THEN
36
+ RAISE EXCEPTION 'user-resource authority scope mismatch' USING ERRCODE = '42501';
37
+ END IF;
38
+ SELECT membership.id INTO STRICT owner_membership_id
39
+ FROM organization_memberships membership
40
+ WHERE membership.account_id = p_account_id
41
+ AND membership.subject_id = caller_subject
42
+ AND membership.status = 'active'
43
+ AND membership.revoked_at IS NULL
44
+ FOR SHARE;
45
+
46
+ RETURN QUERY
47
+ SELECT authority.id, authority.resource_kind, authority.generation, authority.status,
48
+ grant_value.id, grant_value.workspace_id, grant_value.session_id,
49
+ grant_value.action, grant_value.mode, grant_value.context,
50
+ grant_value.generation, grant_value.status, grant_value.expires_at
51
+ FROM organization_user_resource_authorities authority
52
+ LEFT JOIN organization_user_resource_grants grant_value
53
+ ON grant_value.account_id = authority.account_id
54
+ AND grant_value.authority_id = authority.id
55
+ AND grant_value.owner_organization_membership_id = owner_membership_id
56
+ WHERE authority.account_id = p_account_id
57
+ AND authority.organization_membership_id = owner_membership_id
58
+ ORDER BY authority.created_at, authority.id, grant_value.created_at, grant_value.id;
59
+ END
60
+ $body$;
61
+
62
+ CREATE OR REPLACE FUNCTION issue_self_user_resource_grant(
63
+ p_account_id uuid,
64
+ p_authority_id uuid,
65
+ p_workspace_id uuid,
66
+ p_action text,
67
+ p_mode text,
68
+ p_context text,
69
+ p_session_id uuid DEFAULT NULL,
70
+ p_workspace_shared_acknowledged boolean DEFAULT false
71
+ ) RETURNS TABLE (
72
+ grant_id uuid, target_workspace_id uuid, target_session_id uuid,
73
+ action text, grant_mode text, grant_context text, grant_generation bigint,
74
+ grant_status text, expires_at timestamptz
75
+ )
76
+ LANGUAGE plpgsql
77
+ SECURITY DEFINER
78
+ SET search_path = pg_catalog, pg_temp
79
+ AS $body$
80
+ #variable_conflict use_column
81
+ DECLARE
82
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
83
+ owner_membership_id uuid;
84
+ target_epoch integer;
85
+ target_visibility text;
86
+ normalized_action text := lower(btrim(p_action));
87
+ BEGIN
88
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
89
+ OR caller_subject IS NULL
90
+ THEN
91
+ RAISE EXCEPTION 'user-resource grant scope mismatch' USING ERRCODE = '42501';
92
+ END IF;
93
+ IF p_mode NOT IN ('once', 'session', 'always')
94
+ OR p_context NOT IN ('user_private', 'workspace_shared')
95
+ OR normalized_action = '' OR length(normalized_action) > 64
96
+ OR normalized_action !~ '^[a-z0-9](?:[a-z0-9._:-]*[a-z0-9])?$'
97
+ THEN
98
+ RAISE EXCEPTION 'invalid user-resource grant request' USING ERRCODE = '22023';
99
+ END IF;
100
+ IF p_context = 'workspace_shared' AND NOT p_workspace_shared_acknowledged THEN
101
+ RAISE EXCEPTION 'workspace_shared requires durable shared-output acknowledgement'
102
+ USING ERRCODE = '42501';
103
+ END IF;
104
+ IF (p_mode = 'always' AND p_session_id IS NOT NULL)
105
+ OR (p_mode IN ('once', 'session') AND p_session_id IS NULL)
106
+ THEN
107
+ RAISE EXCEPTION 'user-resource grant session fence is invalid' USING ERRCODE = '22023';
108
+ END IF;
109
+
110
+ SELECT membership.id INTO STRICT owner_membership_id
111
+ FROM organization_memberships membership
112
+ WHERE membership.account_id = p_account_id
113
+ AND membership.subject_id = caller_subject
114
+ AND membership.status = 'active'
115
+ AND membership.revoked_at IS NULL
116
+ FOR SHARE;
117
+
118
+ PERFORM 1 FROM organization_user_resource_authorities authority
119
+ WHERE authority.id = p_authority_id
120
+ AND authority.account_id = p_account_id
121
+ AND authority.organization_membership_id = owner_membership_id
122
+ AND authority.status = 'active'
123
+ AND authority.revoked_at IS NULL
124
+ FOR UPDATE;
125
+ IF NOT FOUND THEN
126
+ RAISE EXCEPTION 'active self-owned user-resource authority required' USING ERRCODE = '42501';
127
+ END IF;
128
+
129
+ PERFORM 1 FROM workspaces workspace_value
130
+ WHERE workspace_value.id = p_workspace_id AND workspace_value.account_id = p_account_id
131
+ FOR SHARE;
132
+ IF NOT FOUND THEN
133
+ RAISE EXCEPTION 'target workspace is outside the organization' USING ERRCODE = '42501';
134
+ END IF;
135
+ PERFORM 1 FROM organization_memberships membership
136
+ WHERE membership.id = owner_membership_id
137
+ AND (membership.personal_workspace_id = p_workspace_id OR EXISTS (
138
+ SELECT 1 FROM workspace_memberships workspace_membership
139
+ WHERE workspace_membership.account_id = p_account_id
140
+ AND workspace_membership.workspace_id = p_workspace_id
141
+ AND workspace_membership.subject_id = caller_subject
142
+ ));
143
+ IF NOT FOUND THEN
144
+ RAISE EXCEPTION 'owner lacks current target-workspace access' USING ERRCODE = '42501';
145
+ END IF;
146
+
147
+ IF p_session_id IS NOT NULL THEN
148
+ SELECT session_value.authority_epoch, session_value.visibility
149
+ INTO STRICT target_epoch, target_visibility
150
+ FROM sessions session_value
151
+ WHERE session_value.id = p_session_id
152
+ AND session_value.account_id = p_account_id
153
+ AND session_value.workspace_id = p_workspace_id
154
+ AND session_value.status <> 'cancelled'
155
+ FOR SHARE;
156
+ IF target_visibility IS DISTINCT FROM p_context THEN
157
+ RAISE EXCEPTION 'grant context does not match current session visibility'
158
+ USING ERRCODE = '42501';
159
+ END IF;
160
+ END IF;
161
+
162
+ INSERT INTO organization_user_resource_grants (
163
+ account_id, authority_id, owner_organization_membership_id, workspace_id,
164
+ session_id, action, mode, context, authority_epoch, generation, status
165
+ ) VALUES (
166
+ p_account_id, p_authority_id, owner_membership_id, p_workspace_id,
167
+ p_session_id, normalized_action, p_mode, p_context, target_epoch, 1, 'active'
168
+ )
169
+ ON CONFLICT (account_id, authority_id, workspace_id, action, mode, context,
170
+ (coalesce(session_id, '00000000-0000-0000-0000-000000000000'::uuid)),
171
+ (coalesce(authority_epoch, 0))) WHERE status = 'active'
172
+ DO UPDATE SET updated_at = organization_user_resource_grants.updated_at
173
+ RETURNING id, workspace_id, session_id, organization_user_resource_grants.action,
174
+ mode, context, generation, status, organization_user_resource_grants.expires_at
175
+ INTO grant_id, target_workspace_id, target_session_id, action, grant_mode,
176
+ grant_context, grant_generation, grant_status, expires_at;
177
+ RETURN NEXT;
178
+ END
179
+ $body$;
180
+
181
+ CREATE OR REPLACE FUNCTION revoke_self_user_resource_grant(
182
+ p_account_id uuid,
183
+ p_grant_id uuid
184
+ ) RETURNS TABLE (
185
+ grant_id uuid, grant_generation bigint, grant_status text, revoked_at timestamptz
186
+ )
187
+ LANGUAGE plpgsql
188
+ SECURITY DEFINER
189
+ SET search_path = pg_catalog, pg_temp
190
+ AS $body$
191
+ #variable_conflict use_column
192
+ DECLARE
193
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
194
+ owner_membership_id uuid;
195
+ BEGIN
196
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
197
+ OR caller_subject IS NULL
198
+ THEN
199
+ RAISE EXCEPTION 'user-resource revoke scope mismatch' USING ERRCODE = '42501';
200
+ END IF;
201
+ SELECT membership.id INTO STRICT owner_membership_id
202
+ FROM organization_memberships membership
203
+ WHERE membership.account_id = p_account_id
204
+ AND membership.subject_id = caller_subject
205
+ AND membership.status = 'active'
206
+ AND membership.revoked_at IS NULL
207
+ FOR SHARE;
208
+
209
+ UPDATE organization_user_resource_grants grant_value
210
+ SET status = 'revoked', revoked_at = clock_timestamp(),
211
+ generation = grant_value.generation + 1, updated_at = clock_timestamp()
212
+ FROM organization_user_resource_authorities authority
213
+ WHERE grant_value.id = p_grant_id
214
+ AND grant_value.account_id = p_account_id
215
+ AND authority.id = grant_value.authority_id
216
+ AND authority.account_id = grant_value.account_id
217
+ AND authority.organization_membership_id = owner_membership_id
218
+ AND grant_value.owner_organization_membership_id = owner_membership_id
219
+ AND grant_value.status <> 'revoked'
220
+ RETURNING grant_value.id, grant_value.generation, grant_value.status, grant_value.revoked_at
221
+ INTO grant_id, grant_generation, grant_status, revoked_at;
222
+ IF FOUND THEN RETURN NEXT; RETURN; END IF;
223
+
224
+ SELECT grant_value.id, grant_value.generation, grant_value.status, grant_value.revoked_at
225
+ INTO grant_id, grant_generation, grant_status, revoked_at
226
+ FROM organization_user_resource_grants grant_value
227
+ JOIN organization_user_resource_authorities authority
228
+ ON authority.id = grant_value.authority_id AND authority.account_id = grant_value.account_id
229
+ WHERE grant_value.id = p_grant_id
230
+ AND grant_value.account_id = p_account_id
231
+ AND authority.organization_membership_id = owner_membership_id
232
+ AND grant_value.owner_organization_membership_id = owner_membership_id
233
+ AND grant_value.status = 'revoked';
234
+ IF NOT FOUND THEN
235
+ RAISE EXCEPTION 'self-owned user-resource grant not found' USING ERRCODE = '42501';
236
+ END IF;
237
+ RETURN NEXT;
238
+ END
239
+ $body$;
240
+
241
+ REVOKE ALL ON FUNCTION list_self_user_resource_authorities(uuid) FROM PUBLIC;
242
+ REVOKE ALL ON FUNCTION issue_self_user_resource_grant(uuid, uuid, uuid, text, text, text, uuid, boolean) FROM PUBLIC;
243
+ REVOKE ALL ON FUNCTION revoke_self_user_resource_grant(uuid, uuid) FROM PUBLIC;
244
+ DO $grants$
245
+ BEGIN
246
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
247
+ GRANT EXECUTE ON FUNCTION list_self_user_resource_authorities(uuid) TO opengeni_app;
248
+ GRANT EXECUTE ON FUNCTION issue_self_user_resource_grant(uuid, uuid, uuid, text, text, text, uuid, boolean) TO opengeni_app;
249
+ GRANT EXECUTE ON FUNCTION revoke_self_user_resource_grant(uuid, uuid) TO opengeni_app;
250
+ REVOKE ALL ON TABLE organization_user_resource_authorities FROM opengeni_app;
251
+ REVOKE ALL ON TABLE organization_user_resource_grants FROM opengeni_app;
252
+ END IF;
253
+ END
254
+ $grants$;
255
+
256
+ CREATE OR REPLACE FUNCTION authorize_session_attempt_personal_resource_reads(
257
+ p_account_id uuid,
258
+ p_workspace_id uuid,
259
+ p_attempt_id uuid
260
+ ) RETURNS void
261
+ LANGUAGE plpgsql
262
+ SECURITY DEFINER
263
+ SET search_path = pg_catalog, pg_temp
264
+ AS $body$
265
+ DECLARE
266
+ personal_count integer;
267
+ BEGIN
268
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
269
+ OR p_workspace_id IS DISTINCT FROM nullif(
270
+ current_setting('opengeni.workspace_id', true), ''
271
+ )::uuid
272
+ THEN
273
+ RAISE EXCEPTION 'personal-resource read scope mismatch' USING ERRCODE = '42501';
274
+ END IF;
275
+ SELECT count(*)::integer INTO personal_count
276
+ FROM session_turn_attempts attempt
277
+ JOIN sessions session_value
278
+ ON session_value.id = attempt.session_id
279
+ AND session_value.account_id = attempt.account_id
280
+ AND session_value.workspace_id = attempt.workspace_id
281
+ LEFT JOIN workspace_variable_sets variable_set
282
+ ON variable_set.id = session_value.variable_set_id
283
+ AND variable_set.account_id = session_value.account_id
284
+ AND variable_set.authority_scope = 'user'
285
+ LEFT JOIN rigs rig
286
+ ON rig.id = session_value.rig_id
287
+ AND rig.account_id = session_value.account_id
288
+ AND rig.authority_scope = 'user'
289
+ LEFT JOIN rig_versions rig_version
290
+ ON rig_version.id = session_value.rig_version_id
291
+ AND rig_version.rig_id = session_value.rig_id
292
+ AND rig_version.account_id = session_value.account_id
293
+ LEFT JOIN LATERAL jsonb_array_elements_text(
294
+ coalesce(rig_version.default_variable_set_ids, '[]'::jsonb)
295
+ ) default_id(value) ON true
296
+ LEFT JOIN workspace_variable_sets default_variable_set
297
+ ON default_variable_set.id = default_id.value::uuid
298
+ AND default_variable_set.account_id = session_value.account_id
299
+ AND default_variable_set.authority_scope = 'user'
300
+ WHERE attempt.id = p_attempt_id
301
+ AND attempt.account_id = p_account_id
302
+ AND attempt.workspace_id = p_workspace_id
303
+ AND (variable_set.id IS NOT NULL OR rig.id IS NOT NULL OR default_variable_set.id IS NOT NULL);
304
+ IF personal_count = 0 THEN RETURN; END IF;
305
+ PERFORM * FROM resolve_session_attempt_personal_resources(
306
+ p_account_id, p_workspace_id, p_attempt_id
307
+ );
308
+ END
309
+ $body$;
310
+
311
+ REVOKE ALL ON FUNCTION authorize_session_attempt_personal_resource_reads(uuid, uuid, uuid)
312
+ FROM PUBLIC;
313
+ DO $read_grant$
314
+ BEGIN
315
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
316
+ GRANT EXECUTE ON FUNCTION
317
+ authorize_session_attempt_personal_resource_reads(uuid, uuid, uuid)
318
+ TO opengeni_app;
319
+ END IF;
320
+ END
321
+ $read_grant$;
322
+
323
+ DO $personal_resource_delegation_authority_correction$
324
+ DECLARE
325
+ data_schema text := current_schema();
326
+ BEGIN
327
+ EXECUTE format($ddl$
328
+ CREATE OR REPLACE FUNCTION %1$I.admit_session_attempt_personal_resources()
329
+ RETURNS trigger
330
+ LANGUAGE plpgsql
331
+ SECURITY DEFINER
332
+ SET search_path = %1$I, pg_catalog
333
+ AS $body$
334
+ DECLARE
335
+ session_row sessions%%ROWTYPE;
336
+ turn_row session_turns%%ROWTYPE;
337
+ member_row organization_memberships%%ROWTYPE;
338
+ resource_row record;
339
+ grant_row organization_user_resource_grants%%ROWTYPE;
340
+ resource_total integer := 0;
341
+ snapshot_total integer := 0;
342
+ initiating_subject text;
343
+ affected integer;
344
+ BEGIN
345
+ INSERT INTO opengeni_private.personal_resource_delegation_capabilities (
346
+ backend_pid, transaction_id, capability_kind
347
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'admit')
348
+ ON CONFLICT DO NOTHING;
349
+
350
+ SELECT session_value.* INTO STRICT session_row
351
+ FROM sessions session_value
352
+ WHERE session_value.id = NEW.session_id
353
+ AND session_value.account_id = NEW.account_id
354
+ AND session_value.workspace_id = NEW.workspace_id
355
+ FOR SHARE;
356
+
357
+ SELECT turn_value.* INTO STRICT turn_row
358
+ FROM session_turns turn_value
359
+ WHERE turn_value.id = NEW.turn_id
360
+ AND turn_value.account_id = NEW.account_id
361
+ AND turn_value.workspace_id = NEW.workspace_id
362
+ AND turn_value.session_id = NEW.session_id
363
+ FOR SHARE;
364
+
365
+ SELECT count(*)::integer INTO resource_total
366
+ FROM (
367
+ SELECT DISTINCT selected.resource_kind, selected.resource_id
368
+ FROM (
369
+ SELECT 'variable_set'::text AS resource_kind, variable_set.id AS resource_id
370
+ FROM workspace_variable_sets variable_set
371
+ WHERE variable_set.id = session_row.variable_set_id
372
+ AND variable_set.account_id = NEW.account_id
373
+ AND variable_set.authority_scope = 'user'
374
+ UNION ALL
375
+ SELECT 'rig'::text, rig.id
376
+ FROM rigs rig
377
+ WHERE rig.id = session_row.rig_id
378
+ AND rig.account_id = NEW.account_id
379
+ AND rig.authority_scope = 'user'
380
+ UNION ALL
381
+ SELECT 'variable_set'::text, default_variable_set.id
382
+ FROM rig_versions rig_version
383
+ CROSS JOIN LATERAL jsonb_array_elements_text(
384
+ rig_version.default_variable_set_ids
385
+ ) default_id(value)
386
+ JOIN workspace_variable_sets default_variable_set
387
+ ON default_variable_set.id = default_id.value::uuid
388
+ AND default_variable_set.account_id = NEW.account_id
389
+ AND default_variable_set.authority_scope = 'user'
390
+ WHERE rig_version.id = session_row.rig_version_id
391
+ AND rig_version.rig_id = session_row.rig_id
392
+ AND rig_version.account_id = NEW.account_id
393
+ ) selected
394
+ ) selected_personal_resources;
395
+
396
+ IF resource_total = 0 THEN
397
+ DELETE FROM opengeni_private.personal_resource_delegation_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 = 'admit';
401
+ RETURN NEW;
402
+ END IF;
403
+
404
+ IF NEW.execution_generation <= 0
405
+ OR NEW.state NOT IN ('claimed', 'running')
406
+ OR NEW.closed_at IS NOT NULL
407
+ OR NEW.quiesced_at IS NOT NULL
408
+ OR session_row.active_turn_id IS DISTINCT FROM NEW.turn_id
409
+ OR turn_row.active_attempt_id IS DISTINCT FROM NEW.id
410
+ OR turn_row.execution_generation IS DISTINCT FROM NEW.execution_generation
411
+ OR turn_row.status <> 'running'
412
+ OR EXISTS (
413
+ SELECT 1
414
+ FROM session_attempt_interruptions interruption
415
+ WHERE interruption.account_id = NEW.account_id
416
+ AND interruption.workspace_id = NEW.workspace_id
417
+ AND interruption.session_id = NEW.session_id
418
+ AND interruption.attempt_id = NEW.id
419
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
420
+ )
421
+ THEN
422
+ RAISE EXCEPTION 'personal-resource admission requires the exact current uninterrupted attempt'
423
+ USING ERRCODE = '42501';
424
+ END IF;
425
+
426
+ IF NEW.authority_visibility IS DISTINCT FROM session_row.visibility
427
+ OR NEW.authority_epoch IS DISTINCT FROM session_row.authority_epoch
428
+ OR NEW.authority_owner_organization_membership_id
429
+ IS DISTINCT FROM session_row.owner_organization_membership_id
430
+ THEN
431
+ RAISE EXCEPTION 'personal-resource admission requires the exact session authority'
432
+ USING ERRCODE = '42501';
433
+ END IF;
434
+
435
+ initiating_subject := coalesce(
436
+ nullif(btrim(turn_row.initiating_human_subject_id), ''),
437
+ CASE WHEN turn_row.initiator_kind = 'subject'
438
+ THEN nullif(btrim(turn_row.initiator_subject_id), '') END
439
+ );
440
+ IF initiating_subject IS NULL THEN
441
+ RAISE EXCEPTION 'personal-resource admission requires an initiating human subject'
442
+ USING ERRCODE = '42501';
443
+ END IF;
444
+
445
+ SELECT membership.* INTO STRICT member_row
446
+ FROM organization_memberships membership
447
+ WHERE membership.account_id = NEW.account_id
448
+ AND membership.subject_id = initiating_subject
449
+ AND membership.status = 'active'
450
+ AND membership.revoked_at IS NULL
451
+ FOR SHARE;
452
+
453
+ IF member_row.personal_workspace_id IS DISTINCT FROM NEW.workspace_id THEN
454
+ PERFORM 1
455
+ FROM workspace_memberships workspace_membership
456
+ WHERE workspace_membership.account_id = NEW.account_id
457
+ AND workspace_membership.workspace_id = NEW.workspace_id
458
+ AND workspace_membership.subject_id = initiating_subject
459
+ FOR KEY SHARE;
460
+ IF NOT FOUND THEN
461
+ RAISE EXCEPTION 'initiating human lacks target-workspace membership'
462
+ USING ERRCODE = '42501';
463
+ END IF;
464
+ END IF;
465
+
466
+ INSERT INTO session_attempt_personal_resource_admissions (
467
+ attempt_id, account_id, workspace_id, session_id, turn_id,
468
+ execution_generation, initiating_human_subject_id,
469
+ owner_organization_membership_id, membership_authorization_revision,
470
+ session_visibility, session_authority_epoch, resource_count
471
+ ) VALUES (
472
+ NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id, NEW.turn_id,
473
+ NEW.execution_generation, initiating_subject, member_row.id,
474
+ member_row.authorization_revision, session_row.visibility,
475
+ session_row.authority_epoch, resource_total
476
+ );
477
+
478
+ FOR resource_row IN
479
+ WITH selected AS (
480
+ SELECT
481
+ 'variable_set'::text AS resource_kind,
482
+ variable_set.id AS resource_id,
483
+ NULL::uuid AS resource_version_id,
484
+ 'variable_set.use'::text AS action,
485
+ 'session_variable_set'::text AS selection_source,
486
+ variable_set.workspace_id AS resource_workspace_id,
487
+ variable_set.authority_id,
488
+ variable_set.owner_organization_membership_id,
489
+ variable_set.origin_workspace_id
490
+ FROM workspace_variable_sets variable_set
491
+ WHERE variable_set.id = session_row.variable_set_id
492
+ AND variable_set.account_id = NEW.account_id
493
+ AND variable_set.authority_scope = 'user'
494
+ UNION ALL
495
+ SELECT
496
+ 'rig'::text, rig.id, session_row.rig_version_id, 'rig.use'::text,
497
+ 'session_rig'::text, rig.workspace_id, rig.authority_id,
498
+ rig.owner_organization_membership_id, rig.origin_workspace_id
499
+ FROM rigs rig
500
+ WHERE rig.id = session_row.rig_id
501
+ AND rig.account_id = NEW.account_id
502
+ AND rig.authority_scope = 'user'
503
+ UNION ALL
504
+ SELECT
505
+ 'variable_set'::text, default_variable_set.id, NULL::uuid,
506
+ 'variable_set.use'::text,
507
+ ('rig_default_variable_set:' || default_id.ordinality::text)::text,
508
+ default_variable_set.workspace_id, default_variable_set.authority_id,
509
+ default_variable_set.owner_organization_membership_id,
510
+ default_variable_set.origin_workspace_id
511
+ FROM rig_versions rig_version
512
+ CROSS JOIN LATERAL jsonb_array_elements_text(
513
+ rig_version.default_variable_set_ids
514
+ ) WITH ORDINALITY default_id(value, ordinality)
515
+ JOIN workspace_variable_sets default_variable_set
516
+ ON default_variable_set.id = default_id.value::uuid
517
+ AND default_variable_set.account_id = NEW.account_id
518
+ AND default_variable_set.authority_scope = 'user'
519
+ WHERE rig_version.id = session_row.rig_version_id
520
+ AND rig_version.rig_id = session_row.rig_id
521
+ AND rig_version.account_id = NEW.account_id
522
+ )
523
+ SELECT resource_kind, resource_id,
524
+ min(resource_version_id::text)::uuid AS resource_version_id,
525
+ action, array_agg(selection_source ORDER BY selection_source) AS selection_sources,
526
+ min(resource_workspace_id::text)::uuid AS resource_workspace_id,
527
+ min(authority_id::text)::uuid AS authority_id,
528
+ min(owner_organization_membership_id::text)::uuid
529
+ AS owner_organization_membership_id,
530
+ min(origin_workspace_id::text)::uuid AS origin_workspace_id
531
+ FROM selected
532
+ GROUP BY resource_kind, resource_id, action
533
+ ORDER BY resource_kind, resource_id
534
+ LOOP
535
+ IF resource_row.owner_organization_membership_id IS DISTINCT FROM member_row.id
536
+ THEN
537
+ RAISE EXCEPTION 'personal resource owner/origin does not match initiating membership'
538
+ USING ERRCODE = '42501';
539
+ END IF;
540
+
541
+ IF resource_row.resource_kind = 'variable_set' THEN
542
+ PERFORM 1 FROM workspace_variable_sets variable_set
543
+ WHERE variable_set.id = resource_row.resource_id
544
+ AND variable_set.account_id = NEW.account_id
545
+ AND variable_set.authority_scope = 'user'
546
+ AND variable_set.authority_id = resource_row.authority_id
547
+ AND variable_set.owner_organization_membership_id = member_row.id
548
+ FOR SHARE;
549
+ ELSE
550
+ PERFORM 1 FROM rigs rig
551
+ WHERE rig.id = resource_row.resource_id
552
+ AND rig.account_id = NEW.account_id
553
+ AND rig.authority_scope = 'user'
554
+ AND rig.authority_id = resource_row.authority_id
555
+ AND rig.owner_organization_membership_id = member_row.id
556
+ FOR SHARE;
557
+ IF FOUND THEN
558
+ PERFORM 1 FROM rig_versions rig_version
559
+ WHERE rig_version.id = resource_row.resource_version_id
560
+ AND rig_version.account_id = NEW.account_id
561
+ AND rig_version.rig_id = resource_row.resource_id
562
+ FOR SHARE;
563
+ END IF;
564
+ END IF;
565
+ IF NOT FOUND THEN
566
+ RAISE EXCEPTION 'personal resource identity changed during admission'
567
+ USING ERRCODE = '42501';
568
+ END IF;
569
+
570
+ PERFORM 1
571
+ FROM organization_user_resource_authorities authority
572
+ WHERE authority.id = resource_row.authority_id
573
+ AND authority.account_id = NEW.account_id
574
+ AND authority.organization_membership_id = member_row.id
575
+ AND authority.resource_kind = resource_row.resource_kind
576
+ AND authority.resource_id = resource_row.resource_id
577
+ AND authority.status = 'active'
578
+ AND authority.revoked_at IS NULL
579
+ FOR SHARE;
580
+ IF NOT FOUND THEN
581
+ RAISE EXCEPTION 'personal resource authority is not live'
582
+ USING ERRCODE = '42501';
583
+ END IF;
584
+
585
+ SELECT grant_value.* INTO grant_row
586
+ FROM organization_user_resource_grants grant_value
587
+ WHERE grant_value.account_id = NEW.account_id
588
+ AND grant_value.authority_id = resource_row.authority_id
589
+ AND grant_value.owner_organization_membership_id = member_row.id
590
+ AND grant_value.workspace_id = NEW.workspace_id
591
+ AND grant_value.action = resource_row.action
592
+ AND grant_value.context = session_row.visibility
593
+ AND grant_value.status = 'active'
594
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
595
+ AND (
596
+ (
597
+ grant_value.mode IN ('once', 'session')
598
+ AND grant_value.session_id = NEW.session_id
599
+ AND grant_value.authority_epoch = session_row.authority_epoch
600
+ ) OR (
601
+ grant_value.mode = 'always'
602
+ AND grant_value.session_id IS NULL
603
+ AND grant_value.authority_epoch IS NULL
604
+ )
605
+ )
606
+ ORDER BY
607
+ CASE grant_value.mode WHEN 'once' THEN 1 WHEN 'session' THEN 2 ELSE 3 END,
608
+ grant_value.generation DESC,
609
+ grant_value.id
610
+ LIMIT 1
611
+ FOR UPDATE;
612
+ IF NOT FOUND THEN
613
+ RAISE EXCEPTION 'matching personal-resource grant required'
614
+ USING ERRCODE = '42501';
615
+ END IF;
616
+
617
+ INSERT INTO session_attempt_personal_resource_snapshots (
618
+ attempt_id, account_id, workspace_id, session_id, turn_id,
619
+ execution_generation, resource_kind, resource_id, resource_version_id,
620
+ selection_sources, action, origin_workspace_id,
621
+ owner_organization_membership_id, membership_authorization_revision,
622
+ authority_id, authority_generation, target_workspace_id,
623
+ session_visibility, session_authority_epoch, grant_id, grant_generation,
624
+ grant_mode, grant_context, grant_session_id, grant_authority_epoch
625
+ )
626
+ SELECT
627
+ NEW.id, NEW.account_id, NEW.workspace_id, NEW.session_id, NEW.turn_id,
628
+ NEW.execution_generation, resource_row.resource_kind, resource_row.resource_id,
629
+ resource_row.resource_version_id, resource_row.selection_sources,
630
+ resource_row.action, resource_row.origin_workspace_id, member_row.id,
631
+ member_row.authorization_revision, authority.id, authority.generation,
632
+ NEW.workspace_id, session_row.visibility, session_row.authority_epoch,
633
+ grant_row.id, grant_row.generation, grant_row.mode, grant_row.context,
634
+ grant_row.session_id, grant_row.authority_epoch
635
+ FROM organization_user_resource_authorities authority
636
+ WHERE authority.id = resource_row.authority_id
637
+ AND authority.account_id = NEW.account_id;
638
+
639
+ IF grant_row.mode = 'once' THEN
640
+ UPDATE organization_user_resource_grants
641
+ SET status = 'consumed', updated_at = clock_timestamp()
642
+ WHERE id = grant_row.id
643
+ AND account_id = NEW.account_id
644
+ AND generation = grant_row.generation
645
+ AND status = 'active';
646
+ GET DIAGNOSTICS affected = ROW_COUNT;
647
+ IF affected <> 1 THEN
648
+ RAISE EXCEPTION 'once grant lost its first-use race'
649
+ USING ERRCODE = '40001';
650
+ END IF;
651
+ INSERT INTO personal_resource_once_consumption_receipts (
652
+ grant_id, account_id, attempt_id, workspace_id, session_id, turn_id,
653
+ execution_generation, authority_id, authority_generation, grant_generation
654
+ ) SELECT
655
+ grant_row.id, NEW.account_id, NEW.id, NEW.workspace_id, NEW.session_id,
656
+ NEW.turn_id, NEW.execution_generation, authority.id, authority.generation,
657
+ grant_row.generation
658
+ FROM organization_user_resource_authorities authority
659
+ WHERE authority.id = resource_row.authority_id
660
+ AND authority.account_id = NEW.account_id;
661
+ END IF;
662
+ snapshot_total := snapshot_total + 1;
663
+ END LOOP;
664
+
665
+ IF snapshot_total <> resource_total THEN
666
+ RAISE EXCEPTION 'personal-resource snapshot collection is not exact'
667
+ USING ERRCODE = '23514';
668
+ END IF;
669
+
670
+ DELETE FROM opengeni_private.personal_resource_delegation_capabilities
671
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
672
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
673
+ AND capability_kind = 'admit';
674
+ RETURN NEW;
675
+ EXCEPTION WHEN OTHERS THEN
676
+ DELETE FROM opengeni_private.personal_resource_delegation_capabilities
677
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
678
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
679
+ AND capability_kind = 'admit';
680
+ RAISE;
681
+ END;
682
+ $body$;
683
+ $ddl$, data_schema);
684
+
685
+ EXECUTE format($ddl$
686
+ CREATE OR REPLACE FUNCTION %1$I.resolve_session_attempt_personal_resources(
687
+ p_account_id uuid,
688
+ p_workspace_id uuid,
689
+ p_attempt_id uuid
690
+ ) RETURNS TABLE (
691
+ resource_kind text,
692
+ resource_id uuid,
693
+ resource_version_id uuid,
694
+ selection_sources text[],
695
+ action text,
696
+ authority_id uuid,
697
+ authority_generation bigint,
698
+ grant_id uuid,
699
+ grant_generation bigint,
700
+ grant_mode text
701
+ )
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_resource_admissions%%ROWTYPE;
708
+ invalid_count integer;
709
+ caller_subject text := coalesce(
710
+ nullif(pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), ''),
711
+ nullif(pg_catalog.current_setting('opengeni.subject_id', true), '')
712
+ );
713
+ BEGIN
714
+ IF p_account_id IS DISTINCT FROM nullif(
715
+ pg_catalog.current_setting('opengeni.account_id', true), ''
716
+ )::uuid
717
+ OR p_workspace_id IS DISTINCT FROM nullif(
718
+ pg_catalog.current_setting('opengeni.workspace_id', true), ''
719
+ )::uuid
720
+ THEN
721
+ RAISE EXCEPTION 'personal-resource resolve scope mismatch'
722
+ USING ERRCODE = '42501';
723
+ END IF;
724
+
725
+ INSERT INTO opengeni_private.personal_resource_delegation_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 STRICT admission_row
731
+ FROM session_attempt_personal_resource_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
+
736
+ IF caller_subject IS DISTINCT FROM admission_row.initiating_human_subject_id THEN
737
+ RAISE EXCEPTION 'personal-resource resolve initiating human mismatch'
738
+ USING ERRCODE = '42501';
739
+ END IF;
740
+
741
+ PERFORM 1
742
+ FROM sessions session_value
743
+ JOIN session_turns turn_value
744
+ ON turn_value.account_id = session_value.account_id
745
+ AND turn_value.workspace_id = session_value.workspace_id
746
+ AND turn_value.session_id = session_value.id
747
+ JOIN session_turn_attempts attempt
748
+ ON attempt.account_id = turn_value.account_id
749
+ AND attempt.workspace_id = turn_value.workspace_id
750
+ AND attempt.session_id = turn_value.session_id
751
+ AND attempt.turn_id = turn_value.id
752
+ WHERE session_value.id = admission_row.session_id
753
+ AND session_value.account_id = admission_row.account_id
754
+ AND session_value.workspace_id = admission_row.workspace_id
755
+ AND session_value.active_turn_id = admission_row.turn_id
756
+ AND turn_value.id = admission_row.turn_id
757
+ AND turn_value.active_attempt_id = admission_row.attempt_id
758
+ AND turn_value.execution_generation = admission_row.execution_generation
759
+ AND turn_value.status = 'running'
760
+ AND attempt.id = admission_row.attempt_id
761
+ AND attempt.execution_generation = admission_row.execution_generation
762
+ AND attempt.state IN ('claimed', 'running')
763
+ AND attempt.closed_at IS NULL
764
+ AND attempt.quiesced_at IS NULL
765
+ FOR SHARE OF session_value, turn_value
766
+ FOR UPDATE OF attempt;
767
+ IF NOT FOUND THEN
768
+ RAISE EXCEPTION 'personal-resource resolve requires the exact current uninterrupted attempt'
769
+ USING ERRCODE = '42501';
770
+ END IF;
771
+
772
+ IF EXISTS (
773
+ SELECT 1
774
+ FROM session_attempt_interruptions interruption
775
+ WHERE interruption.account_id = admission_row.account_id
776
+ AND interruption.workspace_id = admission_row.workspace_id
777
+ AND interruption.session_id = admission_row.session_id
778
+ AND interruption.attempt_id = admission_row.attempt_id
779
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
780
+ )
781
+ THEN
782
+ RAISE EXCEPTION 'personal-resource resolve requires the exact current uninterrupted attempt'
783
+ USING ERRCODE = '42501';
784
+ END IF;
785
+
786
+ SELECT count(*)::integer INTO invalid_count
787
+ FROM session_attempt_personal_resource_snapshots snapshot
788
+ WHERE snapshot.attempt_id = admission_row.attempt_id
789
+ AND NOT (
790
+ EXISTS (
791
+ SELECT 1
792
+ FROM session_turn_attempts attempt
793
+ JOIN sessions session_value
794
+ ON session_value.id = attempt.session_id
795
+ AND session_value.account_id = attempt.account_id
796
+ AND session_value.workspace_id = attempt.workspace_id
797
+ JOIN session_turns turn_value
798
+ ON turn_value.id = attempt.turn_id
799
+ AND turn_value.account_id = attempt.account_id
800
+ AND turn_value.workspace_id = attempt.workspace_id
801
+ AND turn_value.session_id = attempt.session_id
802
+ WHERE attempt.id = snapshot.attempt_id
803
+ AND attempt.account_id = snapshot.account_id
804
+ AND attempt.workspace_id = snapshot.workspace_id
805
+ AND attempt.session_id = snapshot.session_id
806
+ AND attempt.turn_id = snapshot.turn_id
807
+ AND attempt.execution_generation = snapshot.execution_generation
808
+ AND attempt.state IN ('claimed', 'running')
809
+ AND attempt.quiesced_at IS NULL
810
+ AND attempt.authority_visibility = snapshot.session_visibility
811
+ AND attempt.authority_epoch = snapshot.session_authority_epoch
812
+ AND session_value.visibility = snapshot.session_visibility
813
+ AND session_value.authority_epoch = snapshot.session_authority_epoch
814
+ AND coalesce(
815
+ nullif(btrim(turn_value.initiating_human_subject_id), ''),
816
+ CASE WHEN turn_value.initiator_kind = 'subject'
817
+ THEN nullif(btrim(turn_value.initiator_subject_id), '') END
818
+ ) = admission_row.initiating_human_subject_id
819
+ )
820
+ AND EXISTS (
821
+ SELECT 1
822
+ FROM organization_memberships membership
823
+ WHERE membership.id = snapshot.owner_organization_membership_id
824
+ AND membership.account_id = snapshot.account_id
825
+ AND membership.subject_id = admission_row.initiating_human_subject_id
826
+ AND membership.status = 'active'
827
+ AND membership.revoked_at IS NULL
828
+ AND membership.authorization_revision
829
+ = snapshot.membership_authorization_revision
830
+ AND (
831
+ membership.personal_workspace_id = snapshot.workspace_id
832
+ OR EXISTS (
833
+ SELECT 1
834
+ FROM workspace_memberships workspace_membership
835
+ WHERE workspace_membership.account_id = membership.account_id
836
+ AND workspace_membership.workspace_id = snapshot.workspace_id
837
+ AND workspace_membership.subject_id = membership.subject_id
838
+ )
839
+ )
840
+ )
841
+ AND EXISTS (
842
+ SELECT 1
843
+ FROM organization_user_resource_authorities authority
844
+ WHERE authority.id = snapshot.authority_id
845
+ AND authority.account_id = snapshot.account_id
846
+ AND authority.organization_membership_id
847
+ = snapshot.owner_organization_membership_id
848
+ AND authority.resource_kind = snapshot.resource_kind
849
+ AND authority.resource_id = snapshot.resource_id
850
+ AND authority.generation = snapshot.authority_generation
851
+ AND authority.status = 'active'
852
+ AND authority.revoked_at IS NULL
853
+ )
854
+ AND (
855
+ (
856
+ snapshot.resource_kind = 'variable_set'
857
+ AND EXISTS (
858
+ SELECT 1 FROM workspace_variable_sets variable_set
859
+ WHERE variable_set.id = snapshot.resource_id
860
+ AND variable_set.account_id = snapshot.account_id
861
+ AND variable_set.authority_scope = 'user'
862
+ AND variable_set.authority_id = snapshot.authority_id
863
+ AND variable_set.owner_organization_membership_id
864
+ = snapshot.owner_organization_membership_id
865
+ )
866
+ ) OR (
867
+ snapshot.resource_kind = 'rig'
868
+ AND EXISTS (
869
+ SELECT 1 FROM rigs rig
870
+ JOIN rig_versions rig_version
871
+ ON rig_version.id = snapshot.resource_version_id
872
+ AND rig_version.rig_id = rig.id
873
+ AND rig_version.account_id = rig.account_id
874
+ WHERE rig.id = snapshot.resource_id
875
+ AND rig.account_id = snapshot.account_id
876
+ AND rig.authority_scope = 'user'
877
+ AND rig.authority_id = snapshot.authority_id
878
+ AND rig.owner_organization_membership_id
879
+ = snapshot.owner_organization_membership_id
880
+ )
881
+ )
882
+ )
883
+ AND EXISTS (
884
+ SELECT 1
885
+ FROM organization_user_resource_grants grant_value
886
+ WHERE grant_value.id = snapshot.grant_id
887
+ AND grant_value.account_id = snapshot.account_id
888
+ AND grant_value.authority_id = snapshot.authority_id
889
+ AND grant_value.owner_organization_membership_id
890
+ = snapshot.owner_organization_membership_id
891
+ AND grant_value.workspace_id = snapshot.target_workspace_id
892
+ AND grant_value.action = snapshot.action
893
+ AND grant_value.mode = snapshot.grant_mode
894
+ AND grant_value.context = snapshot.grant_context
895
+ AND grant_value.generation = snapshot.grant_generation
896
+ AND grant_value.session_id IS NOT DISTINCT FROM snapshot.grant_session_id
897
+ AND grant_value.authority_epoch
898
+ IS NOT DISTINCT FROM snapshot.grant_authority_epoch
899
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
900
+ AND (
901
+ (
902
+ snapshot.grant_mode = 'once'
903
+ AND grant_value.status = 'consumed'
904
+ AND EXISTS (
905
+ SELECT 1 FROM personal_resource_once_consumption_receipts receipt
906
+ WHERE receipt.grant_id = snapshot.grant_id
907
+ AND receipt.account_id = snapshot.account_id
908
+ AND receipt.attempt_id = snapshot.attempt_id
909
+ AND receipt.authority_id = snapshot.authority_id
910
+ AND receipt.authority_generation = snapshot.authority_generation
911
+ AND receipt.grant_generation = snapshot.grant_generation
912
+ )
913
+ ) OR (
914
+ snapshot.grant_mode IN ('session', 'always')
915
+ AND grant_value.status = 'active'
916
+ )
917
+ )
918
+ )
919
+ );
920
+
921
+ IF invalid_count <> 0 THEN
922
+ RAISE EXCEPTION 'personal-resource authority snapshot is no longer live'
923
+ USING ERRCODE = '42501';
924
+ END IF;
925
+
926
+ SELECT count(*)::integer INTO invalid_count
927
+ FROM session_attempt_personal_resource_snapshots snapshot
928
+ WHERE snapshot.attempt_id = admission_row.attempt_id;
929
+ IF invalid_count <> admission_row.resource_count THEN
930
+ RAISE EXCEPTION 'personal-resource snapshot collection is incomplete'
931
+ USING ERRCODE = '42501';
932
+ END IF;
933
+
934
+ RETURN QUERY
935
+ SELECT snapshot.resource_kind, snapshot.resource_id,
936
+ snapshot.resource_version_id, snapshot.selection_sources, snapshot.action,
937
+ snapshot.authority_id, snapshot.authority_generation, snapshot.grant_id,
938
+ snapshot.grant_generation, snapshot.grant_mode
939
+ FROM session_attempt_personal_resource_snapshots snapshot
940
+ WHERE snapshot.attempt_id = admission_row.attempt_id
941
+ ORDER BY snapshot.resource_kind, snapshot.resource_id;
942
+
943
+ DELETE FROM opengeni_private.personal_resource_delegation_capabilities
944
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
945
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
946
+ AND capability_kind = 'resolve';
947
+ RETURN;
948
+ EXCEPTION WHEN OTHERS THEN
949
+ DELETE FROM opengeni_private.personal_resource_delegation_capabilities
950
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
951
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
952
+ AND capability_kind = 'resolve';
953
+ RAISE;
954
+ END;
955
+ $body$;
956
+ $ddl$, data_schema);
957
+
958
+ END
959
+ $personal_resource_delegation_authority_correction$;
960
+
961
+ CREATE OR REPLACE FUNCTION freeze_scheduled_task_personal_resources(
962
+ p_account_id uuid,
963
+ p_workspace_id uuid,
964
+ p_task_id uuid,
965
+ p_task_authority_revision bigint
966
+ )
967
+ RETURNS integer
968
+ LANGUAGE plpgsql
969
+ SECURITY DEFINER
970
+ SET search_path = pg_catalog, pg_temp
971
+ AS $freeze_scheduled_task_personal_resources$
972
+ DECLARE
973
+ task_row record;
974
+ member_row record;
975
+ session_row record;
976
+ resource_row record;
977
+ grant_row record;
978
+ initiating_subject text := coalesce(
979
+ nullif(btrim(current_setting('opengeni.initiating_human_subject_id', true)), ''),
980
+ nullif(btrim(current_setting('opengeni.subject_id', true)), '')
981
+ );
982
+ target_session uuid;
983
+ target_visibility text := 'workspace_shared';
984
+ target_epoch integer;
985
+ resource_total integer := 0;
986
+ BEGIN
987
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
988
+ OR p_workspace_id IS DISTINCT FROM nullif(
989
+ current_setting('opengeni.workspace_id', true), ''
990
+ )::uuid
991
+ THEN
992
+ RAISE EXCEPTION 'scheduled personal-resource task scope mismatch' USING ERRCODE = '42501';
993
+ END IF;
994
+
995
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
996
+ backend_pid, transaction_id, capability_kind
997
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'task_write')
998
+ ON CONFLICT DO NOTHING;
999
+
1000
+ SELECT task.* INTO STRICT task_row
1001
+ FROM scheduled_tasks task
1002
+ WHERE task.id = p_task_id
1003
+ AND task.account_id = p_account_id
1004
+ AND task.workspace_id = p_workspace_id
1005
+ AND task.authority_revision = p_task_authority_revision
1006
+ FOR UPDATE;
1007
+
1008
+ SELECT count(*)::integer INTO resource_total
1009
+ FROM (
1010
+ SELECT variable_set.id
1011
+ FROM workspace_variable_sets variable_set
1012
+ WHERE variable_set.id = task_row.variable_set_id
1013
+ AND variable_set.account_id = p_account_id
1014
+ AND variable_set.authority_scope = 'user'
1015
+ UNION
1016
+ SELECT rig.id
1017
+ FROM rigs rig
1018
+ WHERE rig.id = task_row.rig_id
1019
+ AND rig.account_id = p_account_id
1020
+ AND rig.authority_scope = 'user'
1021
+ UNION
1022
+ SELECT default_variable_set.id
1023
+ FROM rigs rig
1024
+ JOIN rig_versions rig_version
1025
+ ON rig_version.rig_id = rig.id
1026
+ AND rig_version.account_id = rig.account_id
1027
+ AND rig_version.active
1028
+ CROSS JOIN LATERAL jsonb_array_elements_text(
1029
+ rig_version.default_variable_set_ids
1030
+ ) default_id(value)
1031
+ JOIN workspace_variable_sets default_variable_set
1032
+ ON default_variable_set.id = default_id.value::uuid
1033
+ AND default_variable_set.account_id = p_account_id
1034
+ AND default_variable_set.authority_scope = 'user'
1035
+ WHERE rig.id = task_row.rig_id
1036
+ AND rig.account_id = p_account_id
1037
+ ) selected;
1038
+
1039
+ IF resource_total = 0 THEN
1040
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1041
+ WHERE backend_pid = pg_backend_pid()
1042
+ AND transaction_id = pg_current_xact_id_if_assigned()
1043
+ AND capability_kind = 'task_write';
1044
+ RETURN 0;
1045
+ END IF;
1046
+
1047
+ IF initiating_subject IS NULL THEN
1048
+ RAISE EXCEPTION 'scheduled personal resources require a causal human'
1049
+ USING ERRCODE = '42501';
1050
+ END IF;
1051
+
1052
+ SELECT membership.* INTO STRICT member_row
1053
+ FROM organization_memberships membership
1054
+ WHERE membership.account_id = p_account_id
1055
+ AND membership.subject_id = initiating_subject
1056
+ AND membership.status = 'active'
1057
+ AND membership.revoked_at IS NULL
1058
+ FOR SHARE;
1059
+
1060
+ IF member_row.personal_workspace_id IS DISTINCT FROM p_workspace_id THEN
1061
+ PERFORM 1 FROM workspace_memberships workspace_membership
1062
+ WHERE workspace_membership.account_id = p_account_id
1063
+ AND workspace_membership.workspace_id = p_workspace_id
1064
+ AND workspace_membership.subject_id = initiating_subject
1065
+ FOR KEY SHARE;
1066
+ IF NOT FOUND THEN
1067
+ RAISE EXCEPTION 'scheduled personal-resource owner lacks workspace access'
1068
+ USING ERRCODE = '42501';
1069
+ END IF;
1070
+ END IF;
1071
+
1072
+ IF task_row.run_mode = 'existing_session'
1073
+ OR (task_row.run_mode = 'reusable_session' AND task_row.reusable_session_id IS NOT NULL)
1074
+ THEN
1075
+ target_session := task_row.reusable_session_id;
1076
+ SELECT session_value.* INTO STRICT session_row
1077
+ FROM sessions session_value
1078
+ WHERE session_value.id = target_session
1079
+ AND session_value.account_id = p_account_id
1080
+ AND session_value.workspace_id = p_workspace_id
1081
+ AND session_value.status <> 'cancelled'
1082
+ FOR SHARE;
1083
+ target_visibility := session_row.visibility;
1084
+ target_epoch := session_row.authority_epoch;
1085
+ END IF;
1086
+
1087
+ INSERT INTO scheduled_task_personal_resource_authorities (
1088
+ task_id, task_authority_revision, account_id, workspace_id,
1089
+ initiating_human_subject_id, owner_organization_membership_id,
1090
+ membership_authorization_revision, target_session_id, session_visibility,
1091
+ session_authority_epoch, execution_digest, resource_count
1092
+ ) VALUES (
1093
+ p_task_id, p_task_authority_revision, p_account_id, p_workspace_id,
1094
+ initiating_subject, member_row.id, member_row.authorization_revision,
1095
+ target_session, target_visibility, target_epoch, task_row.execution_digest,
1096
+ resource_total
1097
+ );
1098
+
1099
+ FOR resource_row IN
1100
+ WITH selected AS (
1101
+ SELECT 'variable_set'::text AS resource_kind, variable_set.id AS resource_id,
1102
+ NULL::uuid AS resource_version_id, 'variable_set.use'::text AS action,
1103
+ 'session_variable_set'::text AS selection_source,
1104
+ variable_set.workspace_id AS resource_workspace_id, variable_set.authority_id,
1105
+ variable_set.owner_organization_membership_id, variable_set.origin_workspace_id
1106
+ FROM workspace_variable_sets variable_set
1107
+ WHERE variable_set.id = task_row.variable_set_id
1108
+ AND variable_set.account_id = p_account_id
1109
+ AND variable_set.authority_scope = 'user'
1110
+ UNION ALL
1111
+ SELECT 'rig'::text, rig.id, rig_version.id, 'rig.use'::text,
1112
+ 'session_rig'::text, rig.workspace_id, rig.authority_id,
1113
+ rig.owner_organization_membership_id, rig.origin_workspace_id
1114
+ FROM rigs rig
1115
+ JOIN rig_versions rig_version
1116
+ ON rig_version.rig_id = rig.id
1117
+ AND rig_version.account_id = rig.account_id
1118
+ AND rig_version.active
1119
+ WHERE rig.id = task_row.rig_id
1120
+ AND rig.account_id = p_account_id
1121
+ AND rig.authority_scope = 'user'
1122
+ UNION ALL
1123
+ SELECT 'variable_set'::text, default_variable_set.id, NULL::uuid,
1124
+ 'variable_set.use'::text,
1125
+ ('rig_default_variable_set:' || default_id.ordinality::text)::text,
1126
+ default_variable_set.workspace_id, default_variable_set.authority_id,
1127
+ default_variable_set.owner_organization_membership_id,
1128
+ default_variable_set.origin_workspace_id
1129
+ FROM rigs rig
1130
+ JOIN rig_versions rig_version
1131
+ ON rig_version.rig_id = rig.id
1132
+ AND rig_version.account_id = rig.account_id
1133
+ AND rig_version.active
1134
+ CROSS JOIN LATERAL jsonb_array_elements_text(
1135
+ rig_version.default_variable_set_ids
1136
+ ) WITH ORDINALITY default_id(value, ordinality)
1137
+ JOIN workspace_variable_sets default_variable_set
1138
+ ON default_variable_set.id = default_id.value::uuid
1139
+ AND default_variable_set.account_id = p_account_id
1140
+ AND default_variable_set.authority_scope = 'user'
1141
+ WHERE rig.id = task_row.rig_id
1142
+ AND rig.account_id = p_account_id
1143
+ )
1144
+ SELECT resource_kind, resource_id, min(resource_version_id::text)::uuid resource_version_id,
1145
+ action, array_agg(selection_source ORDER BY selection_source) selection_sources,
1146
+ min(resource_workspace_id::text)::uuid resource_workspace_id,
1147
+ min(authority_id::text)::uuid authority_id,
1148
+ min(owner_organization_membership_id::text)::uuid owner_organization_membership_id,
1149
+ min(origin_workspace_id::text)::uuid origin_workspace_id
1150
+ FROM selected
1151
+ GROUP BY resource_kind, resource_id, action
1152
+ ORDER BY resource_kind, resource_id
1153
+ LOOP
1154
+ IF resource_row.owner_organization_membership_id IS DISTINCT FROM member_row.id
1155
+ THEN
1156
+ RAISE EXCEPTION 'scheduled personal resource belongs to another human or organization'
1157
+ USING ERRCODE = '42501';
1158
+ END IF;
1159
+
1160
+ SELECT grant_value.* INTO grant_row
1161
+ FROM organization_user_resource_grants grant_value
1162
+ JOIN organization_user_resource_authorities authority
1163
+ ON authority.id = grant_value.authority_id
1164
+ AND authority.account_id = grant_value.account_id
1165
+ WHERE authority.id = resource_row.authority_id
1166
+ AND authority.account_id = p_account_id
1167
+ AND authority.organization_membership_id = member_row.id
1168
+ AND authority.resource_kind = resource_row.resource_kind
1169
+ AND authority.resource_id = resource_row.resource_id
1170
+ AND authority.status = 'active'
1171
+ AND authority.revoked_at IS NULL
1172
+ AND grant_value.owner_organization_membership_id = member_row.id
1173
+ AND grant_value.workspace_id = p_workspace_id
1174
+ AND grant_value.action = resource_row.action
1175
+ AND grant_value.context = target_visibility
1176
+ AND grant_value.status = 'active'
1177
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
1178
+ AND (
1179
+ (target_session IS NULL AND grant_value.mode = 'always'
1180
+ AND grant_value.session_id IS NULL AND grant_value.authority_epoch IS NULL)
1181
+ OR (target_session IS NOT NULL AND (
1182
+ (grant_value.mode IN ('once', 'session')
1183
+ AND grant_value.session_id = target_session
1184
+ AND grant_value.authority_epoch = target_epoch)
1185
+ OR (grant_value.mode = 'always'
1186
+ AND grant_value.session_id IS NULL AND grant_value.authority_epoch IS NULL)
1187
+ ))
1188
+ )
1189
+ ORDER BY CASE grant_value.mode WHEN 'once' THEN 1 WHEN 'session' THEN 2 ELSE 3 END,
1190
+ grant_value.generation DESC, grant_value.id
1191
+ LIMIT 1
1192
+ FOR SHARE OF authority, grant_value;
1193
+ IF NOT FOUND THEN
1194
+ RAISE EXCEPTION 'matching scheduled personal-resource grant required'
1195
+ USING ERRCODE = '42501';
1196
+ END IF;
1197
+
1198
+ INSERT INTO scheduled_task_personal_resource_snapshots (
1199
+ task_id, task_authority_revision, account_id, workspace_id,
1200
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
1201
+ origin_workspace_id, owner_organization_membership_id,
1202
+ membership_authorization_revision, authority_id, authority_generation,
1203
+ target_workspace_id, session_visibility, session_authority_epoch,
1204
+ grant_id, grant_generation, grant_mode, grant_context,
1205
+ grant_session_id, grant_authority_epoch
1206
+ )
1207
+ SELECT p_task_id, p_task_authority_revision, p_account_id, p_workspace_id,
1208
+ resource_row.resource_kind, resource_row.resource_id,
1209
+ resource_row.resource_version_id, resource_row.selection_sources, resource_row.action,
1210
+ resource_row.origin_workspace_id, member_row.id, member_row.authorization_revision,
1211
+ authority.id, authority.generation, p_workspace_id, target_visibility, target_epoch,
1212
+ grant_row.id, grant_row.generation, grant_row.mode, grant_row.context,
1213
+ grant_row.session_id, grant_row.authority_epoch
1214
+ FROM organization_user_resource_authorities authority
1215
+ WHERE authority.id = resource_row.authority_id
1216
+ AND authority.account_id = p_account_id;
1217
+ END LOOP;
1218
+
1219
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1220
+ WHERE backend_pid = pg_backend_pid()
1221
+ AND transaction_id = pg_current_xact_id_if_assigned()
1222
+ AND capability_kind = 'task_write';
1223
+ RETURN resource_total;
1224
+ EXCEPTION WHEN OTHERS THEN
1225
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1226
+ WHERE backend_pid = pg_backend_pid()
1227
+ AND transaction_id = pg_current_xact_id_if_assigned()
1228
+ AND capability_kind = 'task_write';
1229
+ RAISE;
1230
+ END
1231
+ $freeze_scheduled_task_personal_resources$;
1232
+ CREATE OR REPLACE FUNCTION admit_scheduled_task_run_personal_resources()
1233
+ RETURNS trigger
1234
+ LANGUAGE plpgsql
1235
+ SECURITY DEFINER
1236
+ SET search_path = pg_catalog, pg_temp
1237
+ AS $admit_scheduled_task_run_personal_resources$
1238
+ DECLARE
1239
+ task_row record;
1240
+ authority_row record;
1241
+ once_snapshot record;
1242
+ selected_personal_count integer;
1243
+ has_authority boolean := false;
1244
+ invalid_count integer;
1245
+ affected integer;
1246
+ BEGIN
1247
+ SELECT task.* INTO STRICT task_row
1248
+ FROM scheduled_tasks task
1249
+ WHERE task.id = NEW.task_id
1250
+ AND task.account_id = NEW.account_id
1251
+ AND task.workspace_id = NEW.workspace_id
1252
+ FOR UPDATE;
1253
+
1254
+ IF (NEW.task_authority_revision IS NULL) <> (NEW.task_execution_digest IS NULL) THEN
1255
+ RAISE EXCEPTION 'scheduled task run execution binding is incomplete'
1256
+ USING ERRCODE = '22023';
1257
+ END IF;
1258
+ IF NEW.task_authority_revision IS NOT NULL AND (
1259
+ NEW.task_authority_revision IS DISTINCT FROM task_row.authority_revision
1260
+ OR NEW.task_execution_digest IS DISTINCT FROM task_row.execution_digest
1261
+ ) THEN
1262
+ RAISE EXCEPTION 'scheduled task changed after worker read'
1263
+ USING ERRCODE = '40001';
1264
+ END IF;
1265
+
1266
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
1267
+ backend_pid, transaction_id, capability_kind
1268
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'run_admit')
1269
+ ON CONFLICT DO NOTHING;
1270
+
1271
+ SELECT authority.* INTO authority_row
1272
+ FROM scheduled_task_personal_resource_authorities authority
1273
+ WHERE authority.task_id = NEW.task_id
1274
+ AND authority.task_authority_revision = task_row.authority_revision;
1275
+ has_authority := FOUND;
1276
+ IF NOT has_authority THEN
1277
+ SELECT count(*)::integer INTO selected_personal_count
1278
+ FROM (
1279
+ SELECT variable_set.id
1280
+ FROM workspace_variable_sets variable_set
1281
+ WHERE variable_set.id = task_row.variable_set_id
1282
+ AND variable_set.account_id = task_row.account_id
1283
+ AND variable_set.authority_scope = 'user'
1284
+ UNION
1285
+ SELECT rig.id
1286
+ FROM rigs rig
1287
+ WHERE rig.id = task_row.rig_id
1288
+ AND rig.account_id = task_row.account_id
1289
+ AND rig.authority_scope = 'user'
1290
+ UNION
1291
+ SELECT default_variable_set.id
1292
+ FROM rigs rig
1293
+ JOIN rig_versions rig_version
1294
+ ON rig_version.rig_id = rig.id
1295
+ AND rig_version.account_id = rig.account_id
1296
+ AND rig_version.active
1297
+ CROSS JOIN LATERAL jsonb_array_elements_text(
1298
+ rig_version.default_variable_set_ids
1299
+ ) default_id(value)
1300
+ JOIN workspace_variable_sets default_variable_set
1301
+ ON default_variable_set.id = default_id.value::uuid
1302
+ AND default_variable_set.account_id = task_row.account_id
1303
+ AND default_variable_set.authority_scope = 'user'
1304
+ WHERE rig.id = task_row.rig_id
1305
+ AND rig.account_id = task_row.account_id
1306
+ ) selected;
1307
+ IF selected_personal_count = 0 THEN
1308
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1309
+ WHERE backend_pid = pg_backend_pid()
1310
+ AND transaction_id = pg_current_xact_id_if_assigned()
1311
+ AND capability_kind = 'run_admit';
1312
+ RETURN NEW;
1313
+ END IF;
1314
+ END IF;
1315
+
1316
+ -- Ordinary run-history rows remain writable for paused tasks. The active
1317
+ -- fence applies only when this insert would admit personal-resource
1318
+ -- authority, and is checked while holding the task row lock before any
1319
+ -- admission/snapshot/once-consumption write.
1320
+ IF task_row.status <> 'active' THEN
1321
+ RAISE EXCEPTION 'scheduled task is not active' USING ERRCODE = '55000';
1322
+ END IF;
1323
+
1324
+ IF NOT has_authority THEN
1325
+ RAISE EXCEPTION 'scheduled personal-resource task has no authority snapshot'
1326
+ USING ERRCODE = '42501';
1327
+ END IF;
1328
+
1329
+ IF NEW.task_authority_revision IS NULL THEN
1330
+ RAISE EXCEPTION 'scheduled personal-resource run requires an execution binding'
1331
+ USING ERRCODE = '42501';
1332
+ END IF;
1333
+ IF authority_row.execution_digest IS DISTINCT FROM task_row.execution_digest
1334
+ OR authority_row.task_authority_revision IS DISTINCT FROM NEW.task_authority_revision
1335
+ OR authority_row.execution_digest IS DISTINCT FROM NEW.task_execution_digest
1336
+ THEN
1337
+ RAISE EXCEPTION 'scheduled personal-resource execution binding mismatch'
1338
+ USING ERRCODE = '42501';
1339
+ END IF;
1340
+
1341
+ SELECT count(*)::integer INTO invalid_count
1342
+ FROM scheduled_task_personal_resource_snapshots snapshot
1343
+ WHERE snapshot.task_id = authority_row.task_id
1344
+ AND snapshot.task_authority_revision = authority_row.task_authority_revision
1345
+ AND NOT (
1346
+ EXISTS (
1347
+ SELECT 1 FROM organization_memberships membership
1348
+ WHERE membership.id = snapshot.owner_organization_membership_id
1349
+ AND membership.account_id = snapshot.account_id
1350
+ AND membership.subject_id = authority_row.initiating_human_subject_id
1351
+ AND membership.status = 'active'
1352
+ AND membership.revoked_at IS NULL
1353
+ AND membership.authorization_revision = snapshot.membership_authorization_revision
1354
+ AND (membership.personal_workspace_id = snapshot.workspace_id OR EXISTS (
1355
+ SELECT 1 FROM workspace_memberships workspace_membership
1356
+ WHERE workspace_membership.account_id = membership.account_id
1357
+ AND workspace_membership.workspace_id = snapshot.workspace_id
1358
+ AND workspace_membership.subject_id = membership.subject_id
1359
+ ))
1360
+ )
1361
+ AND EXISTS (
1362
+ SELECT 1 FROM organization_user_resource_authorities authority
1363
+ WHERE authority.id = snapshot.authority_id
1364
+ AND authority.account_id = snapshot.account_id
1365
+ AND authority.organization_membership_id = snapshot.owner_organization_membership_id
1366
+ AND authority.resource_kind = snapshot.resource_kind
1367
+ AND authority.resource_id = snapshot.resource_id
1368
+ AND authority.generation = snapshot.authority_generation
1369
+ AND authority.status = 'active'
1370
+ AND authority.revoked_at IS NULL
1371
+ )
1372
+ AND (
1373
+ (snapshot.resource_kind = 'variable_set' AND EXISTS (
1374
+ SELECT 1 FROM workspace_variable_sets variable_set
1375
+ WHERE variable_set.id = snapshot.resource_id
1376
+ AND variable_set.account_id = snapshot.account_id
1377
+ AND variable_set.authority_scope = 'user'
1378
+ AND variable_set.authority_id = snapshot.authority_id
1379
+ AND variable_set.owner_organization_membership_id =
1380
+ snapshot.owner_organization_membership_id
1381
+ )) OR (snapshot.resource_kind = 'rig' AND EXISTS (
1382
+ SELECT 1 FROM rigs rig
1383
+ JOIN rig_versions rig_version
1384
+ ON rig_version.id = snapshot.resource_version_id
1385
+ AND rig_version.rig_id = rig.id
1386
+ AND rig_version.account_id = rig.account_id
1387
+ WHERE rig.id = snapshot.resource_id
1388
+ AND rig.account_id = snapshot.account_id
1389
+ AND rig.authority_scope = 'user'
1390
+ AND rig.authority_id = snapshot.authority_id
1391
+ AND rig.owner_organization_membership_id =
1392
+ snapshot.owner_organization_membership_id
1393
+ ))
1394
+ )
1395
+ AND EXISTS (
1396
+ SELECT 1 FROM organization_user_resource_grants grant_value
1397
+ WHERE grant_value.id = snapshot.grant_id
1398
+ AND grant_value.account_id = snapshot.account_id
1399
+ AND grant_value.authority_id = snapshot.authority_id
1400
+ AND grant_value.owner_organization_membership_id =
1401
+ snapshot.owner_organization_membership_id
1402
+ AND grant_value.workspace_id = snapshot.target_workspace_id
1403
+ AND grant_value.action = snapshot.action
1404
+ AND grant_value.mode = snapshot.grant_mode
1405
+ AND grant_value.context = snapshot.grant_context
1406
+ AND grant_value.generation = snapshot.grant_generation
1407
+ AND grant_value.session_id IS NOT DISTINCT FROM snapshot.grant_session_id
1408
+ AND grant_value.authority_epoch IS NOT DISTINCT FROM snapshot.grant_authority_epoch
1409
+ AND grant_value.status = 'active'
1410
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
1411
+ )
1412
+ AND (
1413
+ authority_row.target_session_id IS NULL
1414
+ OR EXISTS (
1415
+ SELECT 1 FROM sessions session_value
1416
+ WHERE session_value.id = authority_row.target_session_id
1417
+ AND session_value.account_id = authority_row.account_id
1418
+ AND session_value.workspace_id = authority_row.workspace_id
1419
+ AND session_value.status <> 'cancelled'
1420
+ AND session_value.visibility = authority_row.session_visibility
1421
+ AND session_value.authority_epoch = authority_row.session_authority_epoch
1422
+ )
1423
+ )
1424
+ );
1425
+ IF invalid_count <> 0 THEN
1426
+ RAISE EXCEPTION 'scheduled personal-resource authority snapshot is no longer live'
1427
+ USING ERRCODE = '42501';
1428
+ END IF;
1429
+
1430
+ INSERT INTO scheduled_task_run_personal_resource_admissions (
1431
+ run_id, task_id, task_authority_revision, account_id, workspace_id,
1432
+ initiating_human_subject_id, owner_organization_membership_id,
1433
+ membership_authorization_revision, target_session_id, session_visibility,
1434
+ session_authority_epoch, execution_digest, resource_count
1435
+ ) VALUES (
1436
+ NEW.id, authority_row.task_id, authority_row.task_authority_revision,
1437
+ authority_row.account_id, authority_row.workspace_id,
1438
+ authority_row.initiating_human_subject_id,
1439
+ authority_row.owner_organization_membership_id,
1440
+ authority_row.membership_authorization_revision, authority_row.target_session_id,
1441
+ authority_row.session_visibility, authority_row.session_authority_epoch,
1442
+ authority_row.execution_digest, authority_row.resource_count
1443
+ );
1444
+
1445
+ INSERT INTO scheduled_task_run_personal_resource_snapshots (
1446
+ run_id, task_id, task_authority_revision, account_id, workspace_id,
1447
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
1448
+ origin_workspace_id, owner_organization_membership_id,
1449
+ membership_authorization_revision, authority_id, authority_generation,
1450
+ target_workspace_id, session_visibility, session_authority_epoch,
1451
+ grant_id, grant_generation, grant_mode, grant_context,
1452
+ grant_session_id, grant_authority_epoch
1453
+ )
1454
+ SELECT NEW.id, snapshot.task_id, snapshot.task_authority_revision,
1455
+ snapshot.account_id, snapshot.workspace_id, snapshot.resource_kind,
1456
+ snapshot.resource_id, snapshot.resource_version_id, snapshot.selection_sources,
1457
+ snapshot.action, snapshot.origin_workspace_id,
1458
+ snapshot.owner_organization_membership_id,
1459
+ snapshot.membership_authorization_revision, snapshot.authority_id,
1460
+ snapshot.authority_generation, snapshot.target_workspace_id,
1461
+ snapshot.session_visibility, snapshot.session_authority_epoch,
1462
+ snapshot.grant_id, snapshot.grant_generation, snapshot.grant_mode,
1463
+ snapshot.grant_context, snapshot.grant_session_id, snapshot.grant_authority_epoch
1464
+ FROM scheduled_task_personal_resource_snapshots snapshot
1465
+ WHERE snapshot.task_id = authority_row.task_id
1466
+ AND snapshot.task_authority_revision = authority_row.task_authority_revision;
1467
+
1468
+ FOR once_snapshot IN
1469
+ SELECT snapshot.*
1470
+ FROM scheduled_task_run_personal_resource_snapshots snapshot
1471
+ WHERE snapshot.run_id = NEW.id
1472
+ AND snapshot.grant_mode = 'once'
1473
+ ORDER BY snapshot.grant_id
1474
+ LOOP
1475
+ UPDATE organization_user_resource_grants grant_value
1476
+ SET status = 'consumed', updated_at = clock_timestamp()
1477
+ WHERE grant_value.id = once_snapshot.grant_id
1478
+ AND grant_value.account_id = once_snapshot.account_id
1479
+ AND grant_value.authority_id = once_snapshot.authority_id
1480
+ AND grant_value.generation = once_snapshot.grant_generation
1481
+ AND grant_value.status = 'active';
1482
+ GET DIAGNOSTICS affected = ROW_COUNT;
1483
+ IF affected <> 1 THEN
1484
+ RAISE EXCEPTION 'scheduled occurrence once grant lost its first-use race'
1485
+ USING ERRCODE = '40001';
1486
+ END IF;
1487
+ INSERT INTO scheduled_task_run_personal_resource_once_receipts (
1488
+ grant_id, run_id, account_id, workspace_id, authority_id,
1489
+ authority_generation, grant_generation
1490
+ ) VALUES (
1491
+ once_snapshot.grant_id, NEW.id, once_snapshot.account_id,
1492
+ once_snapshot.workspace_id, once_snapshot.authority_id,
1493
+ once_snapshot.authority_generation, once_snapshot.grant_generation
1494
+ );
1495
+ END LOOP;
1496
+
1497
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1498
+ WHERE backend_pid = pg_backend_pid()
1499
+ AND transaction_id = pg_current_xact_id_if_assigned()
1500
+ AND capability_kind = 'run_admit';
1501
+ RETURN NEW;
1502
+ EXCEPTION WHEN OTHERS THEN
1503
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1504
+ WHERE backend_pid = pg_backend_pid()
1505
+ AND transaction_id = pg_current_xact_id_if_assigned()
1506
+ AND capability_kind = 'run_admit';
1507
+ RAISE;
1508
+ END
1509
+ $admit_scheduled_task_run_personal_resources$;
1510
+
1511
+ DO $fixed_search_paths$
1512
+ DECLARE
1513
+ data_schema text := current_schema();
1514
+ BEGIN
1515
+ EXECUTE format(
1516
+ 'ALTER FUNCTION %1$I.list_self_user_resource_authorities(uuid) '
1517
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1518
+ data_schema
1519
+ );
1520
+ EXECUTE format(
1521
+ 'ALTER FUNCTION %1$I.issue_self_user_resource_grant('
1522
+ || 'uuid, uuid, uuid, text, text, text, uuid, boolean) '
1523
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1524
+ data_schema
1525
+ );
1526
+ EXECUTE format(
1527
+ 'ALTER FUNCTION %1$I.revoke_self_user_resource_grant(uuid, uuid) '
1528
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1529
+ data_schema
1530
+ );
1531
+ EXECUTE format(
1532
+ 'ALTER FUNCTION %1$I.authorize_session_attempt_personal_resource_reads(uuid, uuid, uuid) '
1533
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1534
+ data_schema
1535
+ );
1536
+ EXECUTE format(
1537
+ 'ALTER FUNCTION %1$I.freeze_scheduled_task_personal_resources(uuid, uuid, uuid, bigint) '
1538
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1539
+ data_schema
1540
+ );
1541
+ EXECUTE format(
1542
+ 'ALTER FUNCTION %1$I.admit_scheduled_task_run_personal_resources() '
1543
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1544
+ data_schema
1545
+ );
1546
+ END
1547
+ $fixed_search_paths$;
1548
+ REVOKE ALL ON FUNCTION freeze_scheduled_task_personal_resources(uuid, uuid, uuid, bigint) FROM PUBLIC;
1549
+ REVOKE ALL ON FUNCTION admit_scheduled_task_run_personal_resources() FROM PUBLIC;
1550
+ COMMENT ON FUNCTION list_self_user_resource_authorities(uuid) IS
1551
+ 'Owner-only opaque generic user-resource authority/grant lifecycle listing.';
1552
+ COMMENT ON FUNCTION issue_self_user_resource_grant(uuid, uuid, uuid, text, text, text, uuid, boolean) IS
1553
+ 'Owner-only idempotent user-resource grant issue with server-derived owner/session fences.';
1554
+ COMMENT ON FUNCTION revoke_self_user_resource_grant(uuid, uuid) IS
1555
+ 'Owner-only immediate idempotent user-resource grant revocation.';