@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,1867 @@
1
+ -- deployment-mode: rolling
2
+ -- Freeze personal Variable Set/Rig authority at scheduled-task write, admit the
3
+ -- exact immutable task snapshot for each occurrence, and bind that occurrence
4
+ -- to the causal scheduled turn before direct attempt admission. This migration
5
+ -- stores identifiers and authority generations only; it does not materialize
6
+ -- secrets, mutate Variable Set/Rig CRUD, or load worker credentials.
7
+
8
+ SET LOCAL lock_timeout = '5s';
9
+ SET LOCAL statement_timeout = '10min';
10
+
11
+ ALTER TABLE "scheduled_tasks"
12
+ ADD COLUMN "authority_revision" bigint NOT NULL DEFAULT 1;
13
+
14
+ ALTER TABLE "scheduled_tasks"
15
+ ADD COLUMN "execution_digest" text;
16
+
17
+ ALTER TABLE "scheduled_tasks"
18
+ ADD CONSTRAINT "scheduled_tasks_authority_revision_chk"
19
+ CHECK ("authority_revision" > 0),
20
+ ADD CONSTRAINT "scheduled_tasks_execution_digest_chk"
21
+ CHECK ("execution_digest" ~ '^[0-9a-f]{64}$') NOT VALID;
22
+
23
+ ALTER TABLE "scheduled_task_runs"
24
+ ADD COLUMN "task_authority_revision" bigint,
25
+ ADD COLUMN "task_execution_digest" text,
26
+ ADD CONSTRAINT "scheduled_task_runs_execution_binding_chk" CHECK (
27
+ ("task_authority_revision" IS NULL AND "task_execution_digest" IS NULL)
28
+ OR (
29
+ "task_authority_revision" > 0
30
+ AND "task_execution_digest" ~ '^[0-9a-f]{64}$'
31
+ )
32
+ );
33
+
34
+ ALTER TABLE "session_system_updates"
35
+ ADD COLUMN "scheduled_task_run_id" uuid;
36
+
37
+ CREATE TABLE opengeni_private.scheduled_personal_resource_capabilities (
38
+ "backend_pid" integer NOT NULL,
39
+ "transaction_id" xid8 NOT NULL,
40
+ "capability_kind" text NOT NULL,
41
+ "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
42
+ CONSTRAINT "scheduled_personal_resource_capabilities_kind_chk" CHECK (
43
+ "capability_kind" IN ('task_write', 'run_admit', 'attempt_match')
44
+ ),
45
+ CONSTRAINT "scheduled_personal_resource_capabilities_pk" PRIMARY KEY (
46
+ "backend_pid", "transaction_id", "capability_kind"
47
+ )
48
+ );
49
+ REVOKE ALL ON TABLE opengeni_private.scheduled_personal_resource_capabilities FROM PUBLIC;
50
+
51
+ DO $scheduled_capability_revoke$
52
+ BEGIN
53
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
54
+ REVOKE ALL ON TABLE opengeni_private.scheduled_personal_resource_capabilities
55
+ FROM opengeni_app;
56
+ END IF;
57
+ END
58
+ $scheduled_capability_revoke$;
59
+
60
+ CREATE OR REPLACE FUNCTION opengeni_private.scheduled_personal_resource_capability_active(
61
+ p_capability_kind text DEFAULT NULL
62
+ )
63
+ RETURNS boolean
64
+ LANGUAGE sql
65
+ STABLE
66
+ SECURITY DEFINER
67
+ SET search_path = pg_catalog
68
+ AS $scheduled_personal_resource_capability_active$
69
+ SELECT EXISTS (
70
+ SELECT 1
71
+ FROM opengeni_private.scheduled_personal_resource_capabilities capability
72
+ WHERE capability.backend_pid = pg_catalog.pg_backend_pid()
73
+ AND capability.transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
74
+ AND (
75
+ p_capability_kind IS NULL
76
+ OR capability.capability_kind = p_capability_kind
77
+ )
78
+ )
79
+ $scheduled_personal_resource_capability_active$;
80
+
81
+ REVOKE ALL ON FUNCTION
82
+ opengeni_private.scheduled_personal_resource_capability_active(text)
83
+ FROM PUBLIC;
84
+
85
+ CREATE OR REPLACE FUNCTION scheduled_task_execution_state(
86
+ p_task scheduled_tasks
87
+ )
88
+ RETURNS jsonb
89
+ LANGUAGE sql
90
+ IMMUTABLE
91
+ SET search_path = pg_catalog, pg_temp
92
+ AS $scheduled_task_execution_state$
93
+ -- Administrative presentation/lifecycle fields and the binding bookkeeping
94
+ -- itself are the only exclusions. Every current and future task column is
95
+ -- execution-affecting by default until a migration explicitly proves and
96
+ -- adds a narrow administrative exception here.
97
+ SELECT pg_catalog.to_jsonb(p_task) - ARRAY[
98
+ 'name',
99
+ 'status',
100
+ 'updated_at',
101
+ 'authority_revision',
102
+ 'execution_digest'
103
+ ]::text[]
104
+ $scheduled_task_execution_state$;
105
+
106
+ CREATE OR REPLACE FUNCTION scheduled_task_execution_digest(
107
+ p_task scheduled_tasks
108
+ )
109
+ RETURNS text
110
+ LANGUAGE sql
111
+ IMMUTABLE
112
+ SET search_path = pg_catalog, pg_temp
113
+ AS $scheduled_task_execution_digest$
114
+ SELECT pg_catalog.encode(
115
+ pg_catalog.sha256(pg_catalog.convert_to(
116
+ (
117
+ pg_catalog.to_jsonb(p_task) - ARRAY[
118
+ 'name',
119
+ 'status',
120
+ 'updated_at',
121
+ 'authority_revision',
122
+ 'execution_digest'
123
+ ]::text[]
124
+ )::text,
125
+ 'UTF8'
126
+ )),
127
+ 'hex'
128
+ )
129
+ $scheduled_task_execution_digest$;
130
+
131
+ CREATE OR REPLACE FUNCTION set_scheduled_task_execution_digest()
132
+ RETURNS trigger
133
+ LANGUAGE plpgsql
134
+ SECURITY DEFINER
135
+ SET search_path = pg_catalog, pg_temp
136
+ AS $set_scheduled_task_execution_digest$
137
+ BEGIN
138
+ NEW.execution_digest := pg_catalog.encode(
139
+ pg_catalog.sha256(pg_catalog.convert_to(
140
+ (
141
+ pg_catalog.to_jsonb(NEW) - ARRAY[
142
+ 'name',
143
+ 'status',
144
+ 'updated_at',
145
+ 'authority_revision',
146
+ 'execution_digest'
147
+ ]::text[]
148
+ )::text,
149
+ 'UTF8'
150
+ )),
151
+ 'hex'
152
+ );
153
+ RETURN NEW;
154
+ END
155
+ $set_scheduled_task_execution_digest$;
156
+
157
+ UPDATE scheduled_tasks task
158
+ SET execution_digest = scheduled_task_execution_digest(task);
159
+
160
+ ALTER TABLE "scheduled_tasks"
161
+ ALTER COLUMN "execution_digest" SET NOT NULL,
162
+ VALIDATE CONSTRAINT "scheduled_tasks_execution_digest_chk";
163
+
164
+ CREATE TABLE "scheduled_task_personal_resource_authorities" (
165
+ "task_id" uuid NOT NULL REFERENCES "scheduled_tasks"("id") ON DELETE CASCADE,
166
+ "task_authority_revision" bigint NOT NULL,
167
+ "account_id" uuid NOT NULL,
168
+ "workspace_id" uuid NOT NULL,
169
+ "initiating_human_subject_id" text NOT NULL,
170
+ "owner_organization_membership_id" uuid NOT NULL,
171
+ "membership_authorization_revision" bigint NOT NULL,
172
+ "target_session_id" uuid,
173
+ "session_visibility" text NOT NULL,
174
+ "session_authority_epoch" integer,
175
+ "execution_digest" text NOT NULL,
176
+ "resource_count" integer NOT NULL,
177
+ "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
178
+ CONSTRAINT "scheduled_task_personal_resource_authorities_pk" PRIMARY KEY (
179
+ "task_id", "task_authority_revision"
180
+ ),
181
+ CONSTRAINT "scheduled_task_personal_resource_authorities_owner_fk"
182
+ FOREIGN KEY ("owner_organization_membership_id", "account_id")
183
+ REFERENCES "organization_memberships"("id", "account_id") ON DELETE RESTRICT,
184
+ CONSTRAINT "scheduled_task_personal_resource_authorities_revision_chk" CHECK (
185
+ "task_authority_revision" > 0
186
+ AND "membership_authorization_revision" > 0
187
+ AND "resource_count" > 0
188
+ ),
189
+ CONSTRAINT "scheduled_task_personal_resource_authorities_visibility_chk" CHECK (
190
+ "session_visibility" IN ('user_private', 'workspace_shared')
191
+ ),
192
+ CONSTRAINT "scheduled_task_personal_resource_authorities_session_chk" CHECK (
193
+ ("target_session_id" IS NULL AND "session_authority_epoch" IS NULL)
194
+ OR ("target_session_id" IS NOT NULL AND "session_authority_epoch" > 0)
195
+ ),
196
+ CONSTRAINT "scheduled_task_personal_resource_authorities_subject_chk" CHECK (
197
+ length(btrim("initiating_human_subject_id")) BETWEEN 1 AND 1024
198
+ ),
199
+ CONSTRAINT "scheduled_task_personal_resource_authorities_digest_chk" CHECK (
200
+ "execution_digest" ~ '^[0-9a-f]{64}$'
201
+ )
202
+ );
203
+
204
+ CREATE TABLE "scheduled_task_personal_resource_snapshots" (
205
+ "task_id" uuid NOT NULL,
206
+ "task_authority_revision" bigint NOT NULL,
207
+ "account_id" uuid NOT NULL,
208
+ "workspace_id" uuid NOT NULL,
209
+ "resource_kind" text NOT NULL,
210
+ "resource_id" uuid NOT NULL,
211
+ "resource_version_id" uuid,
212
+ "selection_sources" text[] NOT NULL,
213
+ "action" text NOT NULL,
214
+ "origin_workspace_id" uuid NOT NULL,
215
+ "owner_organization_membership_id" uuid NOT NULL,
216
+ "membership_authorization_revision" bigint NOT NULL,
217
+ "authority_id" uuid NOT NULL,
218
+ "authority_generation" bigint NOT NULL,
219
+ "target_workspace_id" uuid NOT NULL,
220
+ "session_visibility" text NOT NULL,
221
+ "session_authority_epoch" integer,
222
+ "grant_id" uuid NOT NULL,
223
+ "grant_generation" bigint NOT NULL,
224
+ "grant_mode" text NOT NULL,
225
+ "grant_context" text NOT NULL,
226
+ "grant_session_id" uuid,
227
+ "grant_authority_epoch" integer,
228
+ "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
229
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_pk" PRIMARY KEY (
230
+ "task_id", "task_authority_revision", "resource_kind", "resource_id"
231
+ ),
232
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_authority_header_fk"
233
+ FOREIGN KEY ("task_id", "task_authority_revision")
234
+ REFERENCES "scheduled_task_personal_resource_authorities"(
235
+ "task_id", "task_authority_revision"
236
+ ) ON DELETE CASCADE,
237
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_authority_fk"
238
+ FOREIGN KEY ("authority_id", "account_id", "owner_organization_membership_id")
239
+ REFERENCES "organization_user_resource_authorities"(
240
+ "id", "account_id", "organization_membership_id"
241
+ ) ON DELETE RESTRICT,
242
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_grant_fk"
243
+ FOREIGN KEY ("grant_id", "account_id")
244
+ REFERENCES "organization_user_resource_grants"("id", "account_id") ON DELETE RESTRICT,
245
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_kind_chk" CHECK (
246
+ ("resource_kind" = 'variable_set' AND "resource_version_id" IS NULL)
247
+ OR ("resource_kind" = 'rig' AND "resource_version_id" IS NOT NULL)
248
+ ),
249
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_action_chk" CHECK (
250
+ ("resource_kind" = 'variable_set' AND "action" = 'variable_set.use')
251
+ OR ("resource_kind" = 'rig' AND "action" = 'rig.use')
252
+ ),
253
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_generation_chk" CHECK (
254
+ "task_authority_revision" > 0
255
+ AND "membership_authorization_revision" > 0
256
+ AND "authority_generation" > 0
257
+ AND "grant_generation" > 0
258
+ ),
259
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_sources_chk" CHECK (
260
+ cardinality("selection_sources") > 0
261
+ ),
262
+ CONSTRAINT "scheduled_task_personal_resource_snapshots_grant_fence_chk" CHECK (
263
+ (
264
+ "grant_mode" = 'always'
265
+ AND "grant_session_id" IS NULL
266
+ AND "grant_authority_epoch" IS NULL
267
+ AND "session_authority_epoch" IS NULL
268
+ ) OR (
269
+ "grant_mode" IN ('once', 'session')
270
+ AND "grant_session_id" IS NOT NULL
271
+ AND "grant_authority_epoch" = "session_authority_epoch"
272
+ )
273
+ )
274
+ );
275
+
276
+ CREATE TABLE "scheduled_task_run_personal_resource_admissions" (
277
+ "run_id" uuid PRIMARY KEY REFERENCES "scheduled_task_runs"("id") ON DELETE CASCADE,
278
+ "task_id" uuid NOT NULL,
279
+ "task_authority_revision" bigint NOT NULL,
280
+ "account_id" uuid NOT NULL,
281
+ "workspace_id" uuid NOT NULL,
282
+ "initiating_human_subject_id" text NOT NULL,
283
+ "owner_organization_membership_id" uuid NOT NULL,
284
+ "membership_authorization_revision" bigint NOT NULL,
285
+ "target_session_id" uuid,
286
+ "session_visibility" text NOT NULL,
287
+ "session_authority_epoch" integer,
288
+ "execution_digest" text NOT NULL,
289
+ "resource_count" integer NOT NULL,
290
+ "admitted_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
291
+ CONSTRAINT "scheduled_task_run_personal_resource_admissions_task_fk"
292
+ FOREIGN KEY ("task_id", "task_authority_revision")
293
+ REFERENCES "scheduled_task_personal_resource_authorities"(
294
+ "task_id", "task_authority_revision"
295
+ ) ON DELETE RESTRICT,
296
+ CONSTRAINT "scheduled_task_run_personal_resource_admissions_owner_fk"
297
+ FOREIGN KEY ("owner_organization_membership_id", "account_id")
298
+ REFERENCES "organization_memberships"("id", "account_id") ON DELETE RESTRICT,
299
+ CONSTRAINT "scheduled_task_run_personal_resource_admissions_revision_chk" CHECK (
300
+ "task_authority_revision" > 0
301
+ AND "membership_authorization_revision" > 0
302
+ AND "resource_count" > 0
303
+ ),
304
+ CONSTRAINT "scheduled_task_run_personal_resource_admissions_digest_chk" CHECK (
305
+ "execution_digest" ~ '^[0-9a-f]{64}$'
306
+ )
307
+ );
308
+
309
+ CREATE TABLE "scheduled_task_run_personal_resource_snapshots" (
310
+ "run_id" uuid NOT NULL,
311
+ "task_id" uuid NOT NULL,
312
+ "task_authority_revision" bigint NOT NULL,
313
+ "account_id" uuid NOT NULL,
314
+ "workspace_id" uuid NOT NULL,
315
+ "resource_kind" text NOT NULL,
316
+ "resource_id" uuid NOT NULL,
317
+ "resource_version_id" uuid,
318
+ "selection_sources" text[] NOT NULL,
319
+ "action" text NOT NULL,
320
+ "origin_workspace_id" uuid NOT NULL,
321
+ "owner_organization_membership_id" uuid NOT NULL,
322
+ "membership_authorization_revision" bigint NOT NULL,
323
+ "authority_id" uuid NOT NULL,
324
+ "authority_generation" bigint NOT NULL,
325
+ "target_workspace_id" uuid NOT NULL,
326
+ "session_visibility" text NOT NULL,
327
+ "session_authority_epoch" integer,
328
+ "grant_id" uuid NOT NULL,
329
+ "grant_generation" bigint NOT NULL,
330
+ "grant_mode" text NOT NULL,
331
+ "grant_context" text NOT NULL,
332
+ "grant_session_id" uuid,
333
+ "grant_authority_epoch" integer,
334
+ "created_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
335
+ CONSTRAINT "scheduled_task_run_personal_resource_snapshots_pk" PRIMARY KEY (
336
+ "run_id", "resource_kind", "resource_id"
337
+ ),
338
+ CONSTRAINT "scheduled_task_run_personal_resource_snapshots_admission_fk"
339
+ FOREIGN KEY ("run_id")
340
+ REFERENCES "scheduled_task_run_personal_resource_admissions"("run_id") ON DELETE CASCADE,
341
+ CONSTRAINT "scheduled_task_run_personal_resource_snapshots_task_fk"
342
+ FOREIGN KEY (
343
+ "task_id", "task_authority_revision", "resource_kind", "resource_id"
344
+ ) REFERENCES "scheduled_task_personal_resource_snapshots"(
345
+ "task_id", "task_authority_revision", "resource_kind", "resource_id"
346
+ ) ON DELETE RESTRICT
347
+ );
348
+
349
+ CREATE TABLE "scheduled_task_run_personal_resource_once_receipts" (
350
+ "grant_id" uuid PRIMARY KEY,
351
+ "run_id" uuid NOT NULL,
352
+ "account_id" uuid NOT NULL,
353
+ "workspace_id" uuid NOT NULL,
354
+ "authority_id" uuid NOT NULL,
355
+ "authority_generation" bigint NOT NULL,
356
+ "grant_generation" bigint NOT NULL,
357
+ "consumed_at" timestamptz NOT NULL DEFAULT clock_timestamp(),
358
+ CONSTRAINT "scheduled_task_run_personal_resource_once_receipts_run_fk"
359
+ FOREIGN KEY ("run_id")
360
+ REFERENCES "scheduled_task_run_personal_resource_admissions"("run_id") ON DELETE CASCADE,
361
+ CONSTRAINT "scheduled_task_run_personal_resource_once_receipts_grant_fk"
362
+ FOREIGN KEY ("grant_id", "account_id")
363
+ REFERENCES "organization_user_resource_grants"("id", "account_id") ON DELETE RESTRICT,
364
+ CONSTRAINT "scheduled_run_once_receipts_generation_chk" CHECK (
365
+ "authority_generation" > 0 AND "grant_generation" > 0
366
+ )
367
+ );
368
+
369
+ -- Existing tasks were accepted by writers that could not create this migration's
370
+ -- authority ledger. Pause every such personal-resource task deterministically;
371
+ -- a current writer's explicit resume creates a new, fully frozen revision.
372
+ UPDATE "scheduled_tasks" task
373
+ SET "status" = 'paused', "updated_at" = clock_timestamp()
374
+ WHERE task."status" = 'active'
375
+ AND NOT EXISTS (
376
+ SELECT 1
377
+ FROM "scheduled_task_personal_resource_authorities" authority
378
+ WHERE authority."task_id" = task."id"
379
+ AND authority."task_authority_revision" = task."authority_revision"
380
+ )
381
+ AND (
382
+ EXISTS (
383
+ SELECT 1
384
+ FROM "workspace_variable_sets" variable_set
385
+ WHERE variable_set."id" = task."variable_set_id"
386
+ AND variable_set."account_id" = task."account_id"
387
+ AND variable_set."authority_scope" = 'user'
388
+ )
389
+ OR EXISTS (
390
+ SELECT 1
391
+ FROM "rigs" rig
392
+ WHERE rig."id" = task."rig_id"
393
+ AND rig."account_id" = task."account_id"
394
+ AND rig."authority_scope" = 'user'
395
+ )
396
+ OR EXISTS (
397
+ SELECT 1
398
+ FROM "rigs" rig
399
+ JOIN "rig_versions" rig_version
400
+ ON rig_version."rig_id" = rig."id"
401
+ AND rig_version."account_id" = rig."account_id"
402
+ AND rig_version."active"
403
+ CROSS JOIN LATERAL jsonb_array_elements_text(
404
+ rig_version."default_variable_set_ids"
405
+ ) default_id(value)
406
+ JOIN "workspace_variable_sets" default_variable_set
407
+ ON default_variable_set."id" = default_id.value::uuid
408
+ AND default_variable_set."account_id" = task."account_id"
409
+ AND default_variable_set."authority_scope" = 'user'
410
+ WHERE rig."id" = task."rig_id"
411
+ AND rig."account_id" = task."account_id"
412
+ )
413
+ );
414
+
415
+ CREATE INDEX "scheduled_task_personal_resource_snapshots_authority_idx"
416
+ ON "scheduled_task_personal_resource_snapshots"(
417
+ "account_id", "authority_id", "authority_generation"
418
+ );
419
+ CREATE INDEX "scheduled_task_personal_resource_snapshots_grant_idx"
420
+ ON "scheduled_task_personal_resource_snapshots"(
421
+ "account_id", "grant_id", "grant_generation"
422
+ );
423
+ CREATE INDEX "scheduled_task_run_personal_resource_snapshots_authority_idx"
424
+ ON "scheduled_task_run_personal_resource_snapshots"(
425
+ "account_id", "authority_id", "authority_generation"
426
+ );
427
+
428
+ ALTER TABLE "scheduled_task_personal_resource_authorities" ENABLE ROW LEVEL SECURITY;
429
+ ALTER TABLE "scheduled_task_personal_resource_authorities" FORCE ROW LEVEL SECURITY;
430
+ ALTER TABLE "scheduled_task_personal_resource_snapshots" ENABLE ROW LEVEL SECURITY;
431
+ ALTER TABLE "scheduled_task_personal_resource_snapshots" FORCE ROW LEVEL SECURITY;
432
+ ALTER TABLE "scheduled_task_run_personal_resource_admissions" ENABLE ROW LEVEL SECURITY;
433
+ ALTER TABLE "scheduled_task_run_personal_resource_admissions" FORCE ROW LEVEL SECURITY;
434
+ ALTER TABLE "scheduled_task_run_personal_resource_snapshots" ENABLE ROW LEVEL SECURITY;
435
+ ALTER TABLE "scheduled_task_run_personal_resource_snapshots" FORCE ROW LEVEL SECURITY;
436
+ ALTER TABLE "scheduled_task_run_personal_resource_once_receipts" ENABLE ROW LEVEL SECURITY;
437
+ ALTER TABLE "scheduled_task_run_personal_resource_once_receipts" FORCE ROW LEVEL SECURITY;
438
+
439
+ DO $scheduled_personal_resource_policies$
440
+ DECLARE
441
+ data_schema text := current_schema();
442
+ migration_owner text := current_user;
443
+ table_name text;
444
+ BEGIN
445
+ FOREACH table_name IN ARRAY ARRAY[
446
+ 'scheduled_task_personal_resource_authorities',
447
+ 'scheduled_task_personal_resource_snapshots',
448
+ 'scheduled_task_run_personal_resource_admissions',
449
+ 'scheduled_task_run_personal_resource_snapshots',
450
+ 'scheduled_task_run_personal_resource_once_receipts'
451
+ ] LOOP
452
+ EXECUTE format(
453
+ 'CREATE POLICY scheduled_personal_resource_capability_read ON %I.%I '
454
+ || 'FOR SELECT USING (current_user = %L AND '
455
+ || 'opengeni_private.scheduled_personal_resource_capability_active())',
456
+ data_schema, table_name, migration_owner
457
+ );
458
+ EXECUTE format(
459
+ 'CREATE POLICY scheduled_personal_resource_capability_insert ON %I.%I '
460
+ || 'FOR INSERT WITH CHECK (current_user = %L AND '
461
+ || 'opengeni_private.scheduled_personal_resource_capability_active())',
462
+ data_schema, table_name, migration_owner
463
+ );
464
+ END LOOP;
465
+
466
+ FOREACH table_name IN ARRAY ARRAY[
467
+ 'scheduled_task_run_personal_resource_admissions',
468
+ 'scheduled_task_run_personal_resource_snapshots'
469
+ ] LOOP
470
+ EXECUTE format(
471
+ 'CREATE POLICY scheduled_personal_resource_run_rebind_update ON %I.%I '
472
+ || 'FOR UPDATE USING (current_user = %L AND '
473
+ || 'opengeni_private.scheduled_personal_resource_capability_active(''run_admit'')) '
474
+ || 'WITH CHECK (current_user = %L AND '
475
+ || 'opengeni_private.scheduled_personal_resource_capability_active(''run_admit''))',
476
+ data_schema, table_name, migration_owner, migration_owner
477
+ );
478
+ END LOOP;
479
+
480
+ FOREACH table_name IN ARRAY ARRAY[
481
+ 'organization_memberships',
482
+ 'organization_user_resource_authorities',
483
+ 'organization_user_resource_grants',
484
+ 'workspace_variable_sets',
485
+ 'rigs',
486
+ 'rig_versions'
487
+ ] LOOP
488
+ EXECUTE format(
489
+ 'CREATE POLICY scheduled_personal_resource_read ON %I.%I '
490
+ || 'FOR SELECT USING (current_user = %L AND '
491
+ || 'opengeni_private.scheduled_personal_resource_capability_active())',
492
+ data_schema, table_name, migration_owner
493
+ );
494
+ END LOOP;
495
+
496
+ EXECUTE format(
497
+ 'CREATE POLICY scheduled_personal_resource_grant_update '
498
+ || 'ON %I.organization_user_resource_grants FOR UPDATE USING ('
499
+ || 'current_user = %L AND '
500
+ || 'opengeni_private.scheduled_personal_resource_capability_active()) '
501
+ || 'WITH CHECK (current_user = %L AND '
502
+ || 'opengeni_private.scheduled_personal_resource_capability_active())',
503
+ data_schema, migration_owner, migration_owner
504
+ );
505
+ EXECUTE format(
506
+ 'CREATE POLICY scheduled_personal_resource_attempt_receipt_read '
507
+ || 'ON %I.personal_resource_once_consumption_receipts FOR SELECT USING ('
508
+ || 'current_user = %L AND '
509
+ || 'opengeni_private.scheduled_personal_resource_capability_active(''attempt_match''))',
510
+ data_schema, migration_owner
511
+ );
512
+ EXECUTE format(
513
+ 'CREATE POLICY scheduled_personal_resource_attempt_receipt_delete '
514
+ || 'ON %I.personal_resource_once_consumption_receipts FOR DELETE USING ('
515
+ || 'current_user = %L AND '
516
+ || 'opengeni_private.scheduled_personal_resource_capability_active(''attempt_match''))',
517
+ data_schema, migration_owner
518
+ );
519
+ END
520
+ $scheduled_personal_resource_policies$;
521
+
522
+ CREATE OR REPLACE FUNCTION freeze_scheduled_task_personal_resources(
523
+ p_account_id uuid,
524
+ p_workspace_id uuid,
525
+ p_task_id uuid,
526
+ p_task_authority_revision bigint
527
+ )
528
+ RETURNS integer
529
+ LANGUAGE plpgsql
530
+ SECURITY DEFINER
531
+ SET search_path = pg_catalog, pg_temp
532
+ AS $freeze_scheduled_task_personal_resources$
533
+ DECLARE
534
+ task_row record;
535
+ member_row record;
536
+ session_row record;
537
+ resource_row record;
538
+ grant_row record;
539
+ initiating_subject text := coalesce(
540
+ nullif(btrim(current_setting('opengeni.initiating_human_subject_id', true)), ''),
541
+ nullif(btrim(current_setting('opengeni.subject_id', true)), '')
542
+ );
543
+ target_session uuid;
544
+ target_visibility text := 'workspace_shared';
545
+ target_epoch integer;
546
+ resource_total integer := 0;
547
+ BEGIN
548
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
549
+ OR p_workspace_id IS DISTINCT FROM nullif(
550
+ current_setting('opengeni.workspace_id', true), ''
551
+ )::uuid
552
+ THEN
553
+ RAISE EXCEPTION 'scheduled personal-resource task scope mismatch' USING ERRCODE = '42501';
554
+ END IF;
555
+
556
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
557
+ backend_pid, transaction_id, capability_kind
558
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'task_write')
559
+ ON CONFLICT DO NOTHING;
560
+
561
+ SELECT task.* INTO STRICT task_row
562
+ FROM scheduled_tasks task
563
+ WHERE task.id = p_task_id
564
+ AND task.account_id = p_account_id
565
+ AND task.workspace_id = p_workspace_id
566
+ AND task.authority_revision = p_task_authority_revision
567
+ FOR UPDATE;
568
+
569
+ SELECT count(*)::integer INTO resource_total
570
+ FROM (
571
+ SELECT variable_set.id
572
+ FROM workspace_variable_sets variable_set
573
+ WHERE variable_set.id = task_row.variable_set_id
574
+ AND variable_set.account_id = p_account_id
575
+ AND variable_set.authority_scope = 'user'
576
+ UNION
577
+ SELECT rig.id
578
+ FROM rigs rig
579
+ WHERE rig.id = task_row.rig_id
580
+ AND rig.account_id = p_account_id
581
+ AND rig.authority_scope = 'user'
582
+ UNION
583
+ SELECT default_variable_set.id
584
+ FROM rigs rig
585
+ JOIN rig_versions rig_version
586
+ ON rig_version.rig_id = rig.id
587
+ AND rig_version.account_id = rig.account_id
588
+ AND rig_version.active
589
+ CROSS JOIN LATERAL jsonb_array_elements_text(
590
+ rig_version.default_variable_set_ids
591
+ ) default_id(value)
592
+ JOIN workspace_variable_sets default_variable_set
593
+ ON default_variable_set.id = default_id.value::uuid
594
+ AND default_variable_set.account_id = p_account_id
595
+ AND default_variable_set.authority_scope = 'user'
596
+ WHERE rig.id = task_row.rig_id
597
+ AND rig.account_id = p_account_id
598
+ ) selected;
599
+
600
+ IF resource_total = 0 THEN
601
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
602
+ WHERE backend_pid = pg_backend_pid()
603
+ AND transaction_id = pg_current_xact_id_if_assigned()
604
+ AND capability_kind = 'task_write';
605
+ RETURN 0;
606
+ END IF;
607
+
608
+ IF initiating_subject IS NULL THEN
609
+ RAISE EXCEPTION 'scheduled personal resources require a causal human'
610
+ USING ERRCODE = '42501';
611
+ END IF;
612
+
613
+ SELECT membership.* INTO STRICT member_row
614
+ FROM organization_memberships membership
615
+ WHERE membership.account_id = p_account_id
616
+ AND membership.subject_id = initiating_subject
617
+ AND membership.status = 'active'
618
+ AND membership.revoked_at IS NULL
619
+ AND membership.personal_workspace_id IS NOT NULL
620
+ FOR SHARE;
621
+
622
+ IF member_row.personal_workspace_id IS DISTINCT FROM p_workspace_id THEN
623
+ PERFORM 1 FROM workspace_memberships workspace_membership
624
+ WHERE workspace_membership.account_id = p_account_id
625
+ AND workspace_membership.workspace_id = p_workspace_id
626
+ AND workspace_membership.subject_id = initiating_subject
627
+ FOR KEY SHARE;
628
+ IF NOT FOUND THEN
629
+ RAISE EXCEPTION 'scheduled personal-resource owner lacks workspace access'
630
+ USING ERRCODE = '42501';
631
+ END IF;
632
+ END IF;
633
+
634
+ IF task_row.run_mode = 'existing_session'
635
+ OR (task_row.run_mode = 'reusable_session' AND task_row.reusable_session_id IS NOT NULL)
636
+ THEN
637
+ target_session := task_row.reusable_session_id;
638
+ SELECT session_value.* INTO STRICT session_row
639
+ FROM sessions session_value
640
+ WHERE session_value.id = target_session
641
+ AND session_value.account_id = p_account_id
642
+ AND session_value.workspace_id = p_workspace_id
643
+ AND session_value.status <> 'cancelled'
644
+ FOR SHARE;
645
+ target_visibility := session_row.visibility;
646
+ target_epoch := session_row.authority_epoch;
647
+ END IF;
648
+
649
+ INSERT INTO scheduled_task_personal_resource_authorities (
650
+ task_id, task_authority_revision, account_id, workspace_id,
651
+ initiating_human_subject_id, owner_organization_membership_id,
652
+ membership_authorization_revision, target_session_id, session_visibility,
653
+ session_authority_epoch, execution_digest, resource_count
654
+ ) VALUES (
655
+ p_task_id, p_task_authority_revision, p_account_id, p_workspace_id,
656
+ initiating_subject, member_row.id, member_row.authorization_revision,
657
+ target_session, target_visibility, target_epoch, task_row.execution_digest,
658
+ resource_total
659
+ );
660
+
661
+ FOR resource_row IN
662
+ WITH selected AS (
663
+ SELECT 'variable_set'::text AS resource_kind, variable_set.id AS resource_id,
664
+ NULL::uuid AS resource_version_id, 'variable_set.use'::text AS action,
665
+ 'session_variable_set'::text AS selection_source,
666
+ variable_set.workspace_id AS resource_workspace_id, variable_set.authority_id,
667
+ variable_set.owner_organization_membership_id, variable_set.origin_workspace_id
668
+ FROM workspace_variable_sets variable_set
669
+ WHERE variable_set.id = task_row.variable_set_id
670
+ AND variable_set.account_id = p_account_id
671
+ AND variable_set.authority_scope = 'user'
672
+ UNION ALL
673
+ SELECT 'rig'::text, rig.id, rig_version.id, 'rig.use'::text,
674
+ 'session_rig'::text, rig.workspace_id, rig.authority_id,
675
+ rig.owner_organization_membership_id, rig.origin_workspace_id
676
+ FROM rigs rig
677
+ JOIN rig_versions rig_version
678
+ ON rig_version.rig_id = rig.id
679
+ AND rig_version.account_id = rig.account_id
680
+ AND rig_version.active
681
+ WHERE rig.id = task_row.rig_id
682
+ AND rig.account_id = p_account_id
683
+ AND rig.authority_scope = 'user'
684
+ UNION ALL
685
+ SELECT 'variable_set'::text, default_variable_set.id, NULL::uuid,
686
+ 'variable_set.use'::text,
687
+ ('rig_default_variable_set:' || default_id.ordinality::text)::text,
688
+ default_variable_set.workspace_id, default_variable_set.authority_id,
689
+ default_variable_set.owner_organization_membership_id,
690
+ default_variable_set.origin_workspace_id
691
+ FROM rigs rig
692
+ JOIN rig_versions rig_version
693
+ ON rig_version.rig_id = rig.id
694
+ AND rig_version.account_id = rig.account_id
695
+ AND rig_version.active
696
+ CROSS JOIN LATERAL jsonb_array_elements_text(
697
+ rig_version.default_variable_set_ids
698
+ ) WITH ORDINALITY default_id(value, ordinality)
699
+ JOIN workspace_variable_sets default_variable_set
700
+ ON default_variable_set.id = default_id.value::uuid
701
+ AND default_variable_set.account_id = p_account_id
702
+ AND default_variable_set.authority_scope = 'user'
703
+ WHERE rig.id = task_row.rig_id
704
+ AND rig.account_id = p_account_id
705
+ )
706
+ SELECT resource_kind, resource_id, min(resource_version_id::text)::uuid resource_version_id,
707
+ action, array_agg(selection_source ORDER BY selection_source) selection_sources,
708
+ min(resource_workspace_id::text)::uuid resource_workspace_id,
709
+ min(authority_id::text)::uuid authority_id,
710
+ min(owner_organization_membership_id::text)::uuid owner_organization_membership_id,
711
+ min(origin_workspace_id::text)::uuid origin_workspace_id
712
+ FROM selected
713
+ GROUP BY resource_kind, resource_id, action
714
+ ORDER BY resource_kind, resource_id
715
+ LOOP
716
+ IF resource_row.owner_organization_membership_id IS DISTINCT FROM member_row.id
717
+ OR resource_row.resource_workspace_id IS DISTINCT FROM member_row.personal_workspace_id
718
+ OR resource_row.origin_workspace_id IS DISTINCT FROM member_row.personal_workspace_id
719
+ THEN
720
+ RAISE EXCEPTION 'scheduled personal resource belongs to another human or organization'
721
+ USING ERRCODE = '42501';
722
+ END IF;
723
+
724
+ SELECT grant_value.* INTO grant_row
725
+ FROM organization_user_resource_grants grant_value
726
+ JOIN organization_user_resource_authorities authority
727
+ ON authority.id = grant_value.authority_id
728
+ AND authority.account_id = grant_value.account_id
729
+ WHERE authority.id = resource_row.authority_id
730
+ AND authority.account_id = p_account_id
731
+ AND authority.organization_membership_id = member_row.id
732
+ AND authority.resource_kind = resource_row.resource_kind
733
+ AND authority.resource_id = resource_row.resource_id
734
+ AND authority.origin_workspace_id = member_row.personal_workspace_id
735
+ AND authority.status = 'active'
736
+ AND authority.revoked_at IS NULL
737
+ AND grant_value.owner_organization_membership_id = member_row.id
738
+ AND grant_value.workspace_id = p_workspace_id
739
+ AND grant_value.action = resource_row.action
740
+ AND grant_value.context = target_visibility
741
+ AND grant_value.status = 'active'
742
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
743
+ AND (
744
+ (target_session IS NULL AND grant_value.mode = 'always'
745
+ AND grant_value.session_id IS NULL AND grant_value.authority_epoch IS NULL)
746
+ OR (target_session IS NOT NULL AND (
747
+ (grant_value.mode IN ('once', 'session')
748
+ AND grant_value.session_id = target_session
749
+ AND grant_value.authority_epoch = target_epoch)
750
+ OR (grant_value.mode = 'always'
751
+ AND grant_value.session_id IS NULL AND grant_value.authority_epoch IS NULL)
752
+ ))
753
+ )
754
+ ORDER BY CASE grant_value.mode WHEN 'once' THEN 1 WHEN 'session' THEN 2 ELSE 3 END,
755
+ grant_value.generation DESC, grant_value.id
756
+ LIMIT 1
757
+ FOR SHARE OF authority, grant_value;
758
+ IF NOT FOUND THEN
759
+ RAISE EXCEPTION 'matching scheduled personal-resource grant required'
760
+ USING ERRCODE = '42501';
761
+ END IF;
762
+
763
+ INSERT INTO scheduled_task_personal_resource_snapshots (
764
+ task_id, task_authority_revision, account_id, workspace_id,
765
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
766
+ origin_workspace_id, owner_organization_membership_id,
767
+ membership_authorization_revision, authority_id, authority_generation,
768
+ target_workspace_id, session_visibility, session_authority_epoch,
769
+ grant_id, grant_generation, grant_mode, grant_context,
770
+ grant_session_id, grant_authority_epoch
771
+ )
772
+ SELECT p_task_id, p_task_authority_revision, p_account_id, p_workspace_id,
773
+ resource_row.resource_kind, resource_row.resource_id,
774
+ resource_row.resource_version_id, resource_row.selection_sources, resource_row.action,
775
+ member_row.personal_workspace_id, member_row.id, member_row.authorization_revision,
776
+ authority.id, authority.generation, p_workspace_id, target_visibility, target_epoch,
777
+ grant_row.id, grant_row.generation, grant_row.mode, grant_row.context,
778
+ grant_row.session_id, grant_row.authority_epoch
779
+ FROM organization_user_resource_authorities authority
780
+ WHERE authority.id = resource_row.authority_id
781
+ AND authority.account_id = p_account_id;
782
+ END LOOP;
783
+
784
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
785
+ WHERE backend_pid = pg_backend_pid()
786
+ AND transaction_id = pg_current_xact_id_if_assigned()
787
+ AND capability_kind = 'task_write';
788
+ RETURN resource_total;
789
+ EXCEPTION WHEN OTHERS THEN
790
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
791
+ WHERE backend_pid = pg_backend_pid()
792
+ AND transaction_id = pg_current_xact_id_if_assigned()
793
+ AND capability_kind = 'task_write';
794
+ RAISE;
795
+ END
796
+ $freeze_scheduled_task_personal_resources$;
797
+
798
+ CREATE OR REPLACE FUNCTION clone_scheduled_task_personal_resource_authority(
799
+ p_account_id uuid,
800
+ p_workspace_id uuid,
801
+ p_task_id uuid,
802
+ p_source_task_authority_revision bigint,
803
+ p_target_task_authority_revision bigint
804
+ )
805
+ RETURNS integer
806
+ LANGUAGE plpgsql
807
+ SECURITY DEFINER
808
+ SET search_path = pg_catalog, pg_temp
809
+ AS $clone_scheduled_task_personal_resource_authority$
810
+ DECLARE
811
+ source_authority record;
812
+ target_execution_digest text;
813
+ copied_count integer;
814
+ BEGIN
815
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
816
+ OR p_workspace_id IS DISTINCT FROM nullif(
817
+ current_setting('opengeni.workspace_id', true), ''
818
+ )::uuid
819
+ THEN
820
+ RAISE EXCEPTION 'scheduled personal-resource clone scope mismatch'
821
+ USING ERRCODE = '42501';
822
+ END IF;
823
+
824
+ IF p_source_task_authority_revision <= 0
825
+ OR p_target_task_authority_revision <= p_source_task_authority_revision
826
+ THEN
827
+ RAISE EXCEPTION 'scheduled personal-resource authority clone revision is invalid'
828
+ USING ERRCODE = '22023';
829
+ END IF;
830
+
831
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
832
+ backend_pid, transaction_id, capability_kind
833
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'task_write')
834
+ ON CONFLICT DO NOTHING;
835
+
836
+ SELECT task.execution_digest INTO target_execution_digest
837
+ FROM scheduled_tasks task
838
+ WHERE task.id = p_task_id
839
+ AND task.account_id = p_account_id
840
+ AND task.workspace_id = p_workspace_id
841
+ AND task.authority_revision = p_target_task_authority_revision
842
+ FOR UPDATE;
843
+ IF NOT FOUND THEN
844
+ RAISE EXCEPTION 'scheduled personal-resource clone target is not current'
845
+ USING ERRCODE = '40001';
846
+ END IF;
847
+
848
+ SELECT authority.* INTO source_authority
849
+ FROM scheduled_task_personal_resource_authorities authority
850
+ WHERE authority.task_id = p_task_id
851
+ AND authority.task_authority_revision = p_source_task_authority_revision
852
+ AND authority.account_id = p_account_id
853
+ AND authority.workspace_id = p_workspace_id;
854
+ IF NOT FOUND THEN
855
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
856
+ WHERE backend_pid = pg_backend_pid()
857
+ AND transaction_id = pg_current_xact_id_if_assigned()
858
+ AND capability_kind = 'task_write';
859
+ RETURN 0;
860
+ END IF;
861
+
862
+ INSERT INTO scheduled_task_personal_resource_authorities (
863
+ task_id, task_authority_revision, account_id, workspace_id,
864
+ initiating_human_subject_id, owner_organization_membership_id,
865
+ membership_authorization_revision, target_session_id, session_visibility,
866
+ session_authority_epoch, execution_digest, resource_count
867
+ ) VALUES (
868
+ source_authority.task_id, p_target_task_authority_revision,
869
+ source_authority.account_id, source_authority.workspace_id,
870
+ source_authority.initiating_human_subject_id,
871
+ source_authority.owner_organization_membership_id,
872
+ source_authority.membership_authorization_revision,
873
+ source_authority.target_session_id, source_authority.session_visibility,
874
+ source_authority.session_authority_epoch, target_execution_digest,
875
+ source_authority.resource_count
876
+ );
877
+
878
+ INSERT INTO scheduled_task_personal_resource_snapshots (
879
+ task_id, task_authority_revision, account_id, workspace_id,
880
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
881
+ origin_workspace_id, owner_organization_membership_id,
882
+ membership_authorization_revision, authority_id, authority_generation,
883
+ target_workspace_id, session_visibility, session_authority_epoch,
884
+ grant_id, grant_generation, grant_mode, grant_context,
885
+ grant_session_id, grant_authority_epoch
886
+ )
887
+ SELECT snapshot.task_id, p_target_task_authority_revision,
888
+ snapshot.account_id, snapshot.workspace_id, snapshot.resource_kind,
889
+ snapshot.resource_id, snapshot.resource_version_id, snapshot.selection_sources,
890
+ snapshot.action, snapshot.origin_workspace_id,
891
+ snapshot.owner_organization_membership_id,
892
+ snapshot.membership_authorization_revision, snapshot.authority_id,
893
+ snapshot.authority_generation, snapshot.target_workspace_id,
894
+ snapshot.session_visibility, snapshot.session_authority_epoch,
895
+ snapshot.grant_id, snapshot.grant_generation, snapshot.grant_mode,
896
+ snapshot.grant_context, snapshot.grant_session_id, snapshot.grant_authority_epoch
897
+ FROM scheduled_task_personal_resource_snapshots snapshot
898
+ WHERE snapshot.task_id = p_task_id
899
+ AND snapshot.task_authority_revision = p_source_task_authority_revision;
900
+ GET DIAGNOSTICS copied_count = ROW_COUNT;
901
+ IF copied_count <> source_authority.resource_count THEN
902
+ RAISE EXCEPTION 'scheduled personal-resource authority clone is incomplete'
903
+ USING ERRCODE = '42501';
904
+ END IF;
905
+
906
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
907
+ WHERE backend_pid = pg_backend_pid()
908
+ AND transaction_id = pg_current_xact_id_if_assigned()
909
+ AND capability_kind = 'task_write';
910
+ RETURN copied_count;
911
+ EXCEPTION WHEN OTHERS THEN
912
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
913
+ WHERE backend_pid = pg_backend_pid()
914
+ AND transaction_id = pg_current_xact_id_if_assigned()
915
+ AND capability_kind = 'task_write';
916
+ RAISE;
917
+ END
918
+ $clone_scheduled_task_personal_resource_authority$;
919
+
920
+ CREATE OR REPLACE FUNCTION materialize_scheduled_task_reusable_session_from_run(
921
+ p_account_id uuid,
922
+ p_workspace_id uuid,
923
+ p_task_id uuid,
924
+ p_run_id uuid,
925
+ p_session_id uuid,
926
+ p_source_task_authority_revision bigint,
927
+ p_source_execution_digest text
928
+ )
929
+ RETURNS bigint
930
+ LANGUAGE plpgsql
931
+ SECURITY DEFINER
932
+ SET search_path = pg_catalog, pg_temp
933
+ AS $materialize_scheduled_task_reusable_session_from_run$
934
+ DECLARE
935
+ task_row record;
936
+ admission_row record;
937
+ target_revision bigint;
938
+ target_execution_digest text;
939
+ copied_count integer;
940
+ affected integer;
941
+ BEGIN
942
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
943
+ OR p_workspace_id IS DISTINCT FROM nullif(
944
+ current_setting('opengeni.workspace_id', true), ''
945
+ )::uuid
946
+ THEN
947
+ RAISE EXCEPTION 'scheduled reusable-session materialization scope mismatch'
948
+ USING ERRCODE = '42501';
949
+ END IF;
950
+ IF p_source_task_authority_revision <= 0
951
+ OR p_source_execution_digest !~ '^[0-9a-f]{64}$'
952
+ THEN
953
+ RAISE EXCEPTION 'scheduled reusable-session source binding is invalid'
954
+ USING ERRCODE = '22023';
955
+ END IF;
956
+
957
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
958
+ backend_pid, transaction_id, capability_kind
959
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'run_admit')
960
+ ON CONFLICT DO NOTHING;
961
+
962
+ SELECT admission.* INTO admission_row
963
+ FROM scheduled_task_run_personal_resource_admissions admission
964
+ WHERE admission.run_id = p_run_id
965
+ AND admission.task_id = p_task_id
966
+ AND admission.account_id = p_account_id
967
+ AND admission.workspace_id = p_workspace_id
968
+ AND admission.task_authority_revision = p_source_task_authority_revision
969
+ AND admission.execution_digest = p_source_execution_digest
970
+ FOR UPDATE;
971
+ IF NOT FOUND THEN
972
+ RAISE EXCEPTION 'scheduled reusable-session admission binding changed'
973
+ USING ERRCODE = '40001';
974
+ END IF;
975
+
976
+ PERFORM 1
977
+ FROM scheduled_task_runs run
978
+ WHERE run.id = p_run_id
979
+ AND run.task_id = p_task_id
980
+ AND run.account_id = p_account_id
981
+ AND run.workspace_id = p_workspace_id
982
+ AND run.status = 'queued'
983
+ AND run.task_authority_revision = p_source_task_authority_revision
984
+ AND run.task_execution_digest = p_source_execution_digest
985
+ FOR UPDATE;
986
+ IF NOT FOUND THEN
987
+ RAISE EXCEPTION 'scheduled reusable-session run binding changed'
988
+ USING ERRCODE = '40001';
989
+ END IF;
990
+
991
+ PERFORM 1
992
+ FROM sessions session_value
993
+ WHERE session_value.id = p_session_id
994
+ AND session_value.account_id = p_account_id
995
+ AND session_value.workspace_id = p_workspace_id
996
+ AND session_value.status <> 'cancelled'
997
+ FOR SHARE;
998
+ IF NOT FOUND THEN
999
+ RAISE EXCEPTION 'scheduled reusable-session target is unavailable'
1000
+ USING ERRCODE = '42501';
1001
+ END IF;
1002
+
1003
+ SELECT task.* INTO task_row
1004
+ FROM scheduled_tasks task
1005
+ WHERE task.id = p_task_id
1006
+ AND task.account_id = p_account_id
1007
+ AND task.workspace_id = p_workspace_id
1008
+ FOR UPDATE;
1009
+ IF NOT FOUND THEN
1010
+ RAISE EXCEPTION 'scheduled reusable-session task is unavailable'
1011
+ USING ERRCODE = '40001';
1012
+ END IF;
1013
+ IF task_row.status <> 'active'
1014
+ OR task_row.run_mode <> 'reusable_session'
1015
+ OR task_row.reusable_session_id IS NOT NULL
1016
+ OR task_row.authority_revision IS DISTINCT FROM p_source_task_authority_revision
1017
+ OR task_row.execution_digest IS DISTINCT FROM p_source_execution_digest
1018
+ THEN
1019
+ RAISE EXCEPTION 'scheduled reusable-session task changed before materialization'
1020
+ USING ERRCODE = '40001';
1021
+ END IF;
1022
+
1023
+ UPDATE scheduled_tasks task
1024
+ SET reusable_session_id = p_session_id,
1025
+ authority_revision = task.authority_revision + 1,
1026
+ updated_at = clock_timestamp()
1027
+ WHERE task.id = p_task_id
1028
+ AND task.account_id = p_account_id
1029
+ AND task.workspace_id = p_workspace_id
1030
+ RETURNING task.authority_revision, task.execution_digest
1031
+ INTO STRICT target_revision, target_execution_digest;
1032
+
1033
+ SELECT clone_scheduled_task_personal_resource_authority(
1034
+ p_account_id,
1035
+ p_workspace_id,
1036
+ p_task_id,
1037
+ p_source_task_authority_revision,
1038
+ target_revision
1039
+ ) INTO copied_count;
1040
+ IF copied_count <> admission_row.resource_count THEN
1041
+ RAISE EXCEPTION 'scheduled reusable-session authority clone is incomplete'
1042
+ USING ERRCODE = '42501';
1043
+ END IF;
1044
+
1045
+ UPDATE scheduled_task_run_personal_resource_snapshots snapshot
1046
+ SET task_authority_revision = target_revision
1047
+ WHERE snapshot.run_id = p_run_id
1048
+ AND snapshot.task_id = p_task_id
1049
+ AND snapshot.task_authority_revision = p_source_task_authority_revision;
1050
+ GET DIAGNOSTICS affected = ROW_COUNT;
1051
+ IF affected <> admission_row.resource_count THEN
1052
+ RAISE EXCEPTION 'scheduled reusable-session run snapshot rebind is incomplete'
1053
+ USING ERRCODE = '42501';
1054
+ END IF;
1055
+
1056
+ UPDATE scheduled_task_run_personal_resource_admissions admission
1057
+ SET task_authority_revision = target_revision,
1058
+ execution_digest = target_execution_digest
1059
+ WHERE admission.run_id = p_run_id
1060
+ AND admission.task_authority_revision = p_source_task_authority_revision
1061
+ AND admission.execution_digest = p_source_execution_digest;
1062
+ GET DIAGNOSTICS affected = ROW_COUNT;
1063
+ IF affected <> 1 THEN
1064
+ RAISE EXCEPTION 'scheduled reusable-session run admission rebind failed'
1065
+ USING ERRCODE = '42501';
1066
+ END IF;
1067
+
1068
+ UPDATE scheduled_task_runs run
1069
+ SET task_authority_revision = target_revision,
1070
+ task_execution_digest = target_execution_digest,
1071
+ updated_at = clock_timestamp()
1072
+ WHERE run.id = p_run_id
1073
+ AND run.task_authority_revision = p_source_task_authority_revision
1074
+ AND run.task_execution_digest = p_source_execution_digest;
1075
+ GET DIAGNOSTICS affected = ROW_COUNT;
1076
+ IF affected <> 1 THEN
1077
+ RAISE EXCEPTION 'scheduled reusable-session run row rebind failed'
1078
+ USING ERRCODE = '42501';
1079
+ END IF;
1080
+
1081
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1082
+ WHERE backend_pid = pg_backend_pid()
1083
+ AND transaction_id = pg_current_xact_id_if_assigned()
1084
+ AND capability_kind = 'run_admit';
1085
+ RETURN target_revision;
1086
+ EXCEPTION WHEN OTHERS THEN
1087
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1088
+ WHERE backend_pid = pg_backend_pid()
1089
+ AND transaction_id = pg_current_xact_id_if_assigned()
1090
+ AND capability_kind = 'run_admit';
1091
+ RAISE;
1092
+ END
1093
+ $materialize_scheduled_task_reusable_session_from_run$;
1094
+
1095
+ CREATE OR REPLACE FUNCTION fence_scheduled_task_personal_resource_execution_update()
1096
+ RETURNS trigger
1097
+ LANGUAGE plpgsql
1098
+ SECURITY DEFINER
1099
+ SET search_path = pg_catalog, pg_temp
1100
+ AS $fence_scheduled_task_personal_resource_execution_update$
1101
+ DECLARE
1102
+ execution_changed boolean;
1103
+ has_personal_authority boolean := false;
1104
+ capability_inserted boolean := false;
1105
+ affected integer;
1106
+ BEGIN
1107
+ IF NEW.authority_revision IS DISTINCT FROM OLD.authority_revision
1108
+ AND NEW.authority_revision <> OLD.authority_revision + 1
1109
+ THEN
1110
+ RAISE EXCEPTION 'scheduled personal-resource authority revision update is invalid'
1111
+ USING ERRCODE = '22023';
1112
+ END IF;
1113
+
1114
+ execution_changed := (
1115
+ pg_catalog.to_jsonb(NEW) - ARRAY[
1116
+ 'name',
1117
+ 'status',
1118
+ 'updated_at',
1119
+ 'authority_revision',
1120
+ 'execution_digest'
1121
+ ]::text[]
1122
+ ) IS DISTINCT FROM (
1123
+ pg_catalog.to_jsonb(OLD) - ARRAY[
1124
+ 'name',
1125
+ 'status',
1126
+ 'updated_at',
1127
+ 'authority_revision',
1128
+ 'execution_digest'
1129
+ ]::text[]
1130
+ );
1131
+
1132
+ IF NOT execution_changed THEN
1133
+ RETURN NEW;
1134
+ END IF;
1135
+
1136
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
1137
+ backend_pid, transaction_id, capability_kind
1138
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'task_write')
1139
+ ON CONFLICT DO NOTHING;
1140
+ GET DIAGNOSTICS affected = ROW_COUNT;
1141
+ capability_inserted := affected = 1;
1142
+
1143
+ SELECT EXISTS (
1144
+ SELECT 1
1145
+ FROM scheduled_task_personal_resource_authorities authority
1146
+ WHERE authority.task_id = OLD.id
1147
+ ) INTO has_personal_authority;
1148
+
1149
+ IF capability_inserted THEN
1150
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1151
+ WHERE backend_pid = pg_backend_pid()
1152
+ AND transaction_id = pg_current_xact_id_if_assigned()
1153
+ AND capability_kind = 'task_write';
1154
+ capability_inserted := false;
1155
+ END IF;
1156
+
1157
+ IF has_personal_authority THEN
1158
+ -- A current writer already supplies OLD + 1 and freezes/clones that exact
1159
+ -- revision in the same transaction. A rolling legacy writer supplies no
1160
+ -- revision, so advance it here and leave no matching snapshot: run
1161
+ -- admission then fails closed instead of reusing stale personal authority.
1162
+ NEW.authority_revision := OLD.authority_revision + 1;
1163
+ END IF;
1164
+ RETURN NEW;
1165
+ EXCEPTION WHEN OTHERS THEN
1166
+ IF capability_inserted THEN
1167
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1168
+ WHERE backend_pid = pg_backend_pid()
1169
+ AND transaction_id = pg_current_xact_id_if_assigned()
1170
+ AND capability_kind = 'task_write';
1171
+ END IF;
1172
+ RAISE;
1173
+ END
1174
+ $fence_scheduled_task_personal_resource_execution_update$;
1175
+
1176
+ CREATE OR REPLACE FUNCTION admit_scheduled_task_run_personal_resources()
1177
+ RETURNS trigger
1178
+ LANGUAGE plpgsql
1179
+ SECURITY DEFINER
1180
+ SET search_path = pg_catalog, pg_temp
1181
+ AS $admit_scheduled_task_run_personal_resources$
1182
+ DECLARE
1183
+ task_row record;
1184
+ authority_row record;
1185
+ once_snapshot record;
1186
+ selected_personal_count integer;
1187
+ has_authority boolean := false;
1188
+ invalid_count integer;
1189
+ affected integer;
1190
+ BEGIN
1191
+ SELECT task.* INTO STRICT task_row
1192
+ FROM scheduled_tasks task
1193
+ WHERE task.id = NEW.task_id
1194
+ AND task.account_id = NEW.account_id
1195
+ AND task.workspace_id = NEW.workspace_id
1196
+ FOR UPDATE;
1197
+
1198
+ IF (NEW.task_authority_revision IS NULL) <> (NEW.task_execution_digest IS NULL) THEN
1199
+ RAISE EXCEPTION 'scheduled task run execution binding is incomplete'
1200
+ USING ERRCODE = '22023';
1201
+ END IF;
1202
+ IF NEW.task_authority_revision IS NOT NULL AND (
1203
+ NEW.task_authority_revision IS DISTINCT FROM task_row.authority_revision
1204
+ OR NEW.task_execution_digest IS DISTINCT FROM task_row.execution_digest
1205
+ ) THEN
1206
+ RAISE EXCEPTION 'scheduled task changed after worker read'
1207
+ USING ERRCODE = '40001';
1208
+ END IF;
1209
+
1210
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
1211
+ backend_pid, transaction_id, capability_kind
1212
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'run_admit')
1213
+ ON CONFLICT DO NOTHING;
1214
+
1215
+ SELECT authority.* INTO authority_row
1216
+ FROM scheduled_task_personal_resource_authorities authority
1217
+ WHERE authority.task_id = NEW.task_id
1218
+ AND authority.task_authority_revision = task_row.authority_revision;
1219
+ has_authority := FOUND;
1220
+ IF NOT has_authority THEN
1221
+ SELECT count(*)::integer INTO selected_personal_count
1222
+ FROM (
1223
+ SELECT variable_set.id
1224
+ FROM workspace_variable_sets variable_set
1225
+ WHERE variable_set.id = task_row.variable_set_id
1226
+ AND variable_set.account_id = task_row.account_id
1227
+ AND variable_set.authority_scope = 'user'
1228
+ UNION
1229
+ SELECT rig.id
1230
+ FROM rigs rig
1231
+ WHERE rig.id = task_row.rig_id
1232
+ AND rig.account_id = task_row.account_id
1233
+ AND rig.authority_scope = 'user'
1234
+ UNION
1235
+ SELECT default_variable_set.id
1236
+ FROM rigs rig
1237
+ JOIN rig_versions rig_version
1238
+ ON rig_version.rig_id = rig.id
1239
+ AND rig_version.account_id = rig.account_id
1240
+ AND rig_version.active
1241
+ CROSS JOIN LATERAL jsonb_array_elements_text(
1242
+ rig_version.default_variable_set_ids
1243
+ ) default_id(value)
1244
+ JOIN workspace_variable_sets default_variable_set
1245
+ ON default_variable_set.id = default_id.value::uuid
1246
+ AND default_variable_set.account_id = task_row.account_id
1247
+ AND default_variable_set.authority_scope = 'user'
1248
+ WHERE rig.id = task_row.rig_id
1249
+ AND rig.account_id = task_row.account_id
1250
+ ) selected;
1251
+ IF selected_personal_count = 0 THEN
1252
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1253
+ WHERE backend_pid = pg_backend_pid()
1254
+ AND transaction_id = pg_current_xact_id_if_assigned()
1255
+ AND capability_kind = 'run_admit';
1256
+ RETURN NEW;
1257
+ END IF;
1258
+ END IF;
1259
+
1260
+ -- Ordinary run-history rows remain writable for paused tasks. The active
1261
+ -- fence applies only when this insert would admit personal-resource
1262
+ -- authority, and is checked while holding the task row lock before any
1263
+ -- admission/snapshot/once-consumption write.
1264
+ IF task_row.status <> 'active' THEN
1265
+ RAISE EXCEPTION 'scheduled task is not active' USING ERRCODE = '55000';
1266
+ END IF;
1267
+
1268
+ IF NOT has_authority THEN
1269
+ RAISE EXCEPTION 'scheduled personal-resource task has no authority snapshot'
1270
+ USING ERRCODE = '42501';
1271
+ END IF;
1272
+
1273
+ IF NEW.task_authority_revision IS NULL THEN
1274
+ RAISE EXCEPTION 'scheduled personal-resource run requires an execution binding'
1275
+ USING ERRCODE = '42501';
1276
+ END IF;
1277
+ IF authority_row.execution_digest IS DISTINCT FROM task_row.execution_digest
1278
+ OR authority_row.task_authority_revision IS DISTINCT FROM NEW.task_authority_revision
1279
+ OR authority_row.execution_digest IS DISTINCT FROM NEW.task_execution_digest
1280
+ THEN
1281
+ RAISE EXCEPTION 'scheduled personal-resource execution binding mismatch'
1282
+ USING ERRCODE = '42501';
1283
+ END IF;
1284
+
1285
+ SELECT count(*)::integer INTO invalid_count
1286
+ FROM scheduled_task_personal_resource_snapshots snapshot
1287
+ WHERE snapshot.task_id = authority_row.task_id
1288
+ AND snapshot.task_authority_revision = authority_row.task_authority_revision
1289
+ AND NOT (
1290
+ EXISTS (
1291
+ SELECT 1 FROM organization_memberships membership
1292
+ WHERE membership.id = snapshot.owner_organization_membership_id
1293
+ AND membership.account_id = snapshot.account_id
1294
+ AND membership.subject_id = authority_row.initiating_human_subject_id
1295
+ AND membership.status = 'active'
1296
+ AND membership.revoked_at IS NULL
1297
+ AND membership.personal_workspace_id = snapshot.origin_workspace_id
1298
+ AND membership.authorization_revision = snapshot.membership_authorization_revision
1299
+ AND (membership.personal_workspace_id = snapshot.workspace_id OR EXISTS (
1300
+ SELECT 1 FROM workspace_memberships workspace_membership
1301
+ WHERE workspace_membership.account_id = membership.account_id
1302
+ AND workspace_membership.workspace_id = snapshot.workspace_id
1303
+ AND workspace_membership.subject_id = membership.subject_id
1304
+ ))
1305
+ )
1306
+ AND EXISTS (
1307
+ SELECT 1 FROM organization_user_resource_authorities authority
1308
+ WHERE authority.id = snapshot.authority_id
1309
+ AND authority.account_id = snapshot.account_id
1310
+ AND authority.organization_membership_id = snapshot.owner_organization_membership_id
1311
+ AND authority.resource_kind = snapshot.resource_kind
1312
+ AND authority.resource_id = snapshot.resource_id
1313
+ AND authority.origin_workspace_id = snapshot.origin_workspace_id
1314
+ AND authority.generation = snapshot.authority_generation
1315
+ AND authority.status = 'active'
1316
+ AND authority.revoked_at IS NULL
1317
+ )
1318
+ AND (
1319
+ (snapshot.resource_kind = 'variable_set' AND EXISTS (
1320
+ SELECT 1 FROM workspace_variable_sets variable_set
1321
+ WHERE variable_set.id = snapshot.resource_id
1322
+ AND variable_set.account_id = snapshot.account_id
1323
+ AND variable_set.workspace_id = snapshot.origin_workspace_id
1324
+ AND variable_set.authority_scope = 'user'
1325
+ AND variable_set.authority_id = snapshot.authority_id
1326
+ AND variable_set.owner_organization_membership_id =
1327
+ snapshot.owner_organization_membership_id
1328
+ AND variable_set.origin_workspace_id = snapshot.origin_workspace_id
1329
+ )) OR (snapshot.resource_kind = 'rig' AND EXISTS (
1330
+ SELECT 1 FROM rigs rig
1331
+ JOIN rig_versions rig_version
1332
+ ON rig_version.id = snapshot.resource_version_id
1333
+ AND rig_version.rig_id = rig.id
1334
+ AND rig_version.account_id = rig.account_id
1335
+ AND rig_version.workspace_id = snapshot.origin_workspace_id
1336
+ WHERE rig.id = snapshot.resource_id
1337
+ AND rig.account_id = snapshot.account_id
1338
+ AND rig.workspace_id = snapshot.origin_workspace_id
1339
+ AND rig.authority_scope = 'user'
1340
+ AND rig.authority_id = snapshot.authority_id
1341
+ AND rig.owner_organization_membership_id =
1342
+ snapshot.owner_organization_membership_id
1343
+ AND rig.origin_workspace_id = snapshot.origin_workspace_id
1344
+ ))
1345
+ )
1346
+ AND EXISTS (
1347
+ SELECT 1 FROM organization_user_resource_grants grant_value
1348
+ WHERE grant_value.id = snapshot.grant_id
1349
+ AND grant_value.account_id = snapshot.account_id
1350
+ AND grant_value.authority_id = snapshot.authority_id
1351
+ AND grant_value.owner_organization_membership_id =
1352
+ snapshot.owner_organization_membership_id
1353
+ AND grant_value.workspace_id = snapshot.target_workspace_id
1354
+ AND grant_value.action = snapshot.action
1355
+ AND grant_value.mode = snapshot.grant_mode
1356
+ AND grant_value.context = snapshot.grant_context
1357
+ AND grant_value.generation = snapshot.grant_generation
1358
+ AND grant_value.session_id IS NOT DISTINCT FROM snapshot.grant_session_id
1359
+ AND grant_value.authority_epoch IS NOT DISTINCT FROM snapshot.grant_authority_epoch
1360
+ AND grant_value.status = 'active'
1361
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
1362
+ )
1363
+ AND (
1364
+ authority_row.target_session_id IS NULL
1365
+ OR EXISTS (
1366
+ SELECT 1 FROM sessions session_value
1367
+ WHERE session_value.id = authority_row.target_session_id
1368
+ AND session_value.account_id = authority_row.account_id
1369
+ AND session_value.workspace_id = authority_row.workspace_id
1370
+ AND session_value.status <> 'cancelled'
1371
+ AND session_value.visibility = authority_row.session_visibility
1372
+ AND session_value.authority_epoch = authority_row.session_authority_epoch
1373
+ )
1374
+ )
1375
+ );
1376
+ IF invalid_count <> 0 THEN
1377
+ RAISE EXCEPTION 'scheduled personal-resource authority snapshot is no longer live'
1378
+ USING ERRCODE = '42501';
1379
+ END IF;
1380
+
1381
+ INSERT INTO scheduled_task_run_personal_resource_admissions (
1382
+ run_id, task_id, task_authority_revision, account_id, workspace_id,
1383
+ initiating_human_subject_id, owner_organization_membership_id,
1384
+ membership_authorization_revision, target_session_id, session_visibility,
1385
+ session_authority_epoch, execution_digest, resource_count
1386
+ ) VALUES (
1387
+ NEW.id, authority_row.task_id, authority_row.task_authority_revision,
1388
+ authority_row.account_id, authority_row.workspace_id,
1389
+ authority_row.initiating_human_subject_id,
1390
+ authority_row.owner_organization_membership_id,
1391
+ authority_row.membership_authorization_revision, authority_row.target_session_id,
1392
+ authority_row.session_visibility, authority_row.session_authority_epoch,
1393
+ authority_row.execution_digest, authority_row.resource_count
1394
+ );
1395
+
1396
+ INSERT INTO scheduled_task_run_personal_resource_snapshots (
1397
+ run_id, task_id, task_authority_revision, account_id, workspace_id,
1398
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
1399
+ origin_workspace_id, owner_organization_membership_id,
1400
+ membership_authorization_revision, authority_id, authority_generation,
1401
+ target_workspace_id, session_visibility, session_authority_epoch,
1402
+ grant_id, grant_generation, grant_mode, grant_context,
1403
+ grant_session_id, grant_authority_epoch
1404
+ )
1405
+ SELECT NEW.id, snapshot.task_id, snapshot.task_authority_revision,
1406
+ snapshot.account_id, snapshot.workspace_id, snapshot.resource_kind,
1407
+ snapshot.resource_id, snapshot.resource_version_id, snapshot.selection_sources,
1408
+ snapshot.action, snapshot.origin_workspace_id,
1409
+ snapshot.owner_organization_membership_id,
1410
+ snapshot.membership_authorization_revision, snapshot.authority_id,
1411
+ snapshot.authority_generation, snapshot.target_workspace_id,
1412
+ snapshot.session_visibility, snapshot.session_authority_epoch,
1413
+ snapshot.grant_id, snapshot.grant_generation, snapshot.grant_mode,
1414
+ snapshot.grant_context, snapshot.grant_session_id, snapshot.grant_authority_epoch
1415
+ FROM scheduled_task_personal_resource_snapshots snapshot
1416
+ WHERE snapshot.task_id = authority_row.task_id
1417
+ AND snapshot.task_authority_revision = authority_row.task_authority_revision;
1418
+
1419
+ FOR once_snapshot IN
1420
+ SELECT snapshot.*
1421
+ FROM scheduled_task_run_personal_resource_snapshots snapshot
1422
+ WHERE snapshot.run_id = NEW.id
1423
+ AND snapshot.grant_mode = 'once'
1424
+ ORDER BY snapshot.grant_id
1425
+ LOOP
1426
+ UPDATE organization_user_resource_grants grant_value
1427
+ SET status = 'consumed', updated_at = clock_timestamp()
1428
+ WHERE grant_value.id = once_snapshot.grant_id
1429
+ AND grant_value.account_id = once_snapshot.account_id
1430
+ AND grant_value.authority_id = once_snapshot.authority_id
1431
+ AND grant_value.generation = once_snapshot.grant_generation
1432
+ AND grant_value.status = 'active';
1433
+ GET DIAGNOSTICS affected = ROW_COUNT;
1434
+ IF affected <> 1 THEN
1435
+ RAISE EXCEPTION 'scheduled occurrence once grant lost its first-use race'
1436
+ USING ERRCODE = '40001';
1437
+ END IF;
1438
+ INSERT INTO scheduled_task_run_personal_resource_once_receipts (
1439
+ grant_id, run_id, account_id, workspace_id, authority_id,
1440
+ authority_generation, grant_generation
1441
+ ) VALUES (
1442
+ once_snapshot.grant_id, NEW.id, once_snapshot.account_id,
1443
+ once_snapshot.workspace_id, once_snapshot.authority_id,
1444
+ once_snapshot.authority_generation, once_snapshot.grant_generation
1445
+ );
1446
+ END LOOP;
1447
+
1448
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1449
+ WHERE backend_pid = pg_backend_pid()
1450
+ AND transaction_id = pg_current_xact_id_if_assigned()
1451
+ AND capability_kind = 'run_admit';
1452
+ RETURN NEW;
1453
+ EXCEPTION WHEN OTHERS THEN
1454
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1455
+ WHERE backend_pid = pg_backend_pid()
1456
+ AND transaction_id = pg_current_xact_id_if_assigned()
1457
+ AND capability_kind = 'run_admit';
1458
+ RAISE;
1459
+ END
1460
+ $admit_scheduled_task_run_personal_resources$;
1461
+
1462
+ CREATE OR REPLACE FUNCTION prepare_scheduled_task_attempt_once_grants()
1463
+ RETURNS trigger
1464
+ LANGUAGE plpgsql
1465
+ SECURITY DEFINER
1466
+ SET search_path = pg_catalog, pg_temp
1467
+ AS $prepare_scheduled_task_attempt_once_grants$
1468
+ DECLARE
1469
+ scheduled_run_id uuid;
1470
+ run_count integer;
1471
+ receipt_row record;
1472
+ prior_attempt_run_id uuid;
1473
+ affected integer;
1474
+ BEGIN
1475
+ SELECT count(DISTINCT update_row.scheduled_task_run_id)::integer,
1476
+ min(update_row.scheduled_task_run_id::text)::uuid
1477
+ INTO run_count, scheduled_run_id
1478
+ FROM session_system_updates update_row
1479
+ WHERE update_row.workspace_id = NEW.workspace_id
1480
+ AND update_row.session_id = NEW.session_id
1481
+ AND update_row.delivered_turn_id = NEW.turn_id
1482
+ AND update_row.kind = 'scheduled_occurrence'
1483
+ AND update_row.scheduled_task_run_id IS NOT NULL;
1484
+ IF run_count = 0 THEN RETURN NEW; END IF;
1485
+ IF run_count <> 1 THEN
1486
+ RAISE EXCEPTION 'scheduled once-grant attempt requires one occurrence snapshot'
1487
+ USING ERRCODE = '42501';
1488
+ END IF;
1489
+
1490
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
1491
+ backend_pid, transaction_id, capability_kind
1492
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'attempt_match')
1493
+ ON CONFLICT DO NOTHING;
1494
+
1495
+ FOR receipt_row IN
1496
+ SELECT receipt.*
1497
+ FROM scheduled_task_run_personal_resource_once_receipts receipt
1498
+ WHERE receipt.run_id = scheduled_run_id
1499
+ ORDER BY receipt.grant_id
1500
+ FOR UPDATE
1501
+ LOOP
1502
+ SELECT update_row.scheduled_task_run_id INTO prior_attempt_run_id
1503
+ FROM personal_resource_once_consumption_receipts attempt_receipt
1504
+ JOIN session_system_updates update_row
1505
+ ON update_row.workspace_id = attempt_receipt.workspace_id
1506
+ AND update_row.session_id = attempt_receipt.session_id
1507
+ AND update_row.delivered_turn_id = attempt_receipt.turn_id
1508
+ AND update_row.kind = 'scheduled_occurrence'
1509
+ AND update_row.scheduled_task_run_id IS NOT NULL
1510
+ WHERE attempt_receipt.grant_id = receipt_row.grant_id
1511
+ AND attempt_receipt.account_id = receipt_row.account_id
1512
+ LIMIT 1;
1513
+ IF FOUND AND prior_attempt_run_id IS DISTINCT FROM scheduled_run_id THEN
1514
+ RAISE EXCEPTION 'once grant is owned by another scheduled occurrence'
1515
+ USING ERRCODE = '42501';
1516
+ END IF;
1517
+ IF FOUND THEN
1518
+ DELETE FROM personal_resource_once_consumption_receipts
1519
+ WHERE grant_id = receipt_row.grant_id
1520
+ AND account_id = receipt_row.account_id;
1521
+ END IF;
1522
+
1523
+ UPDATE organization_user_resource_grants grant_value
1524
+ SET status = 'active', updated_at = clock_timestamp()
1525
+ WHERE grant_value.id = receipt_row.grant_id
1526
+ AND grant_value.account_id = receipt_row.account_id
1527
+ AND grant_value.authority_id = receipt_row.authority_id
1528
+ AND grant_value.generation = receipt_row.grant_generation
1529
+ AND grant_value.status = 'consumed';
1530
+ GET DIAGNOSTICS affected = ROW_COUNT;
1531
+ IF affected <> 1 THEN
1532
+ RAISE EXCEPTION 'scheduled occurrence once grant is no longer owned'
1533
+ USING ERRCODE = '42501';
1534
+ END IF;
1535
+ END LOOP;
1536
+
1537
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1538
+ WHERE backend_pid = pg_backend_pid()
1539
+ AND transaction_id = pg_current_xact_id_if_assigned()
1540
+ AND capability_kind = 'attempt_match';
1541
+ RETURN NEW;
1542
+ EXCEPTION WHEN OTHERS THEN
1543
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1544
+ WHERE backend_pid = pg_backend_pid()
1545
+ AND transaction_id = pg_current_xact_id_if_assigned()
1546
+ AND capability_kind = 'attempt_match';
1547
+ RAISE;
1548
+ END
1549
+ $prepare_scheduled_task_attempt_once_grants$;
1550
+
1551
+ CREATE OR REPLACE FUNCTION validate_scheduled_task_attempt_personal_resources()
1552
+ RETURNS trigger
1553
+ LANGUAGE plpgsql
1554
+ SECURITY DEFINER
1555
+ SET search_path = pg_catalog, pg_temp
1556
+ AS $validate_scheduled_task_attempt_personal_resources$
1557
+ DECLARE
1558
+ scheduled_run_id uuid;
1559
+ run_count integer;
1560
+ admission_row record;
1561
+ turn_subject text;
1562
+ mismatch_count integer;
1563
+ BEGIN
1564
+ SELECT count(DISTINCT update_row.scheduled_task_run_id)::integer,
1565
+ min(update_row.scheduled_task_run_id::text)::uuid
1566
+ INTO run_count, scheduled_run_id
1567
+ FROM session_system_updates update_row
1568
+ WHERE update_row.workspace_id = NEW.workspace_id
1569
+ AND update_row.session_id = NEW.session_id
1570
+ AND update_row.delivered_turn_id = NEW.turn_id
1571
+ AND update_row.kind = 'scheduled_occurrence'
1572
+ AND update_row.scheduled_task_run_id IS NOT NULL;
1573
+ IF run_count = 0 THEN RETURN NEW; END IF;
1574
+ IF run_count <> 1 THEN
1575
+ RAISE EXCEPTION 'scheduled personal-resource attempt requires one occurrence snapshot'
1576
+ USING ERRCODE = '42501';
1577
+ END IF;
1578
+
1579
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
1580
+ backend_pid, transaction_id, capability_kind
1581
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'attempt_match')
1582
+ ON CONFLICT DO NOTHING;
1583
+
1584
+ SELECT admission.* INTO admission_row
1585
+ FROM scheduled_task_run_personal_resource_admissions admission
1586
+ WHERE admission.run_id = scheduled_run_id;
1587
+ IF NOT FOUND THEN
1588
+ IF EXISTS (
1589
+ SELECT 1 FROM session_attempt_personal_resource_snapshots snapshot
1590
+ WHERE snapshot.attempt_id = NEW.id
1591
+ ) THEN
1592
+ RAISE EXCEPTION 'scheduled occurrence has no admitted personal-resource snapshot'
1593
+ USING ERRCODE = '42501';
1594
+ END IF;
1595
+ RETURN NEW;
1596
+ END IF;
1597
+
1598
+ SELECT coalesce(
1599
+ nullif(btrim(turn_value.initiating_human_subject_id), ''),
1600
+ CASE WHEN turn_value.initiator_kind = 'subject'
1601
+ THEN nullif(btrim(turn_value.initiator_subject_id), '') END
1602
+ ) INTO turn_subject
1603
+ FROM session_turns turn_value
1604
+ WHERE turn_value.id = NEW.turn_id
1605
+ AND turn_value.account_id = NEW.account_id
1606
+ AND turn_value.workspace_id = NEW.workspace_id
1607
+ AND turn_value.session_id = NEW.session_id;
1608
+ IF turn_subject IS DISTINCT FROM admission_row.initiating_human_subject_id THEN
1609
+ RAISE EXCEPTION 'scheduled occurrence causal human mismatch' USING ERRCODE = '42501';
1610
+ END IF;
1611
+
1612
+ PERFORM 1 FROM scheduled_tasks task
1613
+ WHERE task.id = admission_row.task_id
1614
+ AND task.account_id = admission_row.account_id
1615
+ AND task.workspace_id = admission_row.workspace_id
1616
+ AND task.status = 'active'
1617
+ AND task.authority_revision = admission_row.task_authority_revision
1618
+ AND task.execution_digest = admission_row.execution_digest
1619
+ FOR SHARE;
1620
+ IF NOT FOUND THEN
1621
+ RAISE EXCEPTION 'scheduled task authority revision changed before attempt admission'
1622
+ USING ERRCODE = '42501';
1623
+ END IF;
1624
+
1625
+ SELECT count(*)::integer INTO mismatch_count
1626
+ FROM scheduled_task_run_personal_resource_snapshots run_snapshot
1627
+ FULL JOIN session_attempt_personal_resource_snapshots attempt_snapshot
1628
+ ON attempt_snapshot.attempt_id = NEW.id
1629
+ AND attempt_snapshot.resource_kind = run_snapshot.resource_kind
1630
+ AND attempt_snapshot.resource_id = run_snapshot.resource_id
1631
+ WHERE run_snapshot.run_id = scheduled_run_id
1632
+ AND (
1633
+ attempt_snapshot.attempt_id IS NULL
1634
+ OR attempt_snapshot.resource_version_id IS DISTINCT FROM run_snapshot.resource_version_id
1635
+ OR attempt_snapshot.selection_sources IS DISTINCT FROM run_snapshot.selection_sources
1636
+ OR attempt_snapshot.authority_id IS DISTINCT FROM run_snapshot.authority_id
1637
+ OR attempt_snapshot.authority_generation IS DISTINCT FROM run_snapshot.authority_generation
1638
+ OR attempt_snapshot.grant_id IS DISTINCT FROM run_snapshot.grant_id
1639
+ OR attempt_snapshot.grant_generation IS DISTINCT FROM run_snapshot.grant_generation
1640
+ OR attempt_snapshot.grant_mode IS DISTINCT FROM run_snapshot.grant_mode
1641
+ );
1642
+ IF mismatch_count <> 0 OR (
1643
+ SELECT count(*) FROM session_attempt_personal_resource_snapshots snapshot
1644
+ WHERE snapshot.attempt_id = NEW.id
1645
+ ) <> admission_row.resource_count THEN
1646
+ RAISE EXCEPTION 'scheduled occurrence personal-resource snapshot widened or changed'
1647
+ USING ERRCODE = '42501';
1648
+ END IF;
1649
+
1650
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1651
+ WHERE backend_pid = pg_backend_pid()
1652
+ AND transaction_id = pg_current_xact_id_if_assigned()
1653
+ AND capability_kind = 'attempt_match';
1654
+ RETURN NEW;
1655
+ EXCEPTION WHEN OTHERS THEN
1656
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1657
+ WHERE backend_pid = pg_backend_pid()
1658
+ AND transaction_id = pg_current_xact_id_if_assigned()
1659
+ AND capability_kind = 'attempt_match';
1660
+ RAISE;
1661
+ END
1662
+ $validate_scheduled_task_attempt_personal_resources$;
1663
+
1664
+ CREATE TRIGGER scheduled_task_personal_resource_execution_revision
1665
+ BEFORE UPDATE ON "scheduled_tasks"
1666
+ FOR EACH ROW EXECUTE FUNCTION fence_scheduled_task_personal_resource_execution_update();
1667
+
1668
+ CREATE TRIGGER zz_scheduled_task_execution_digest
1669
+ BEFORE INSERT OR UPDATE ON "scheduled_tasks"
1670
+ FOR EACH ROW EXECUTE FUNCTION set_scheduled_task_execution_digest();
1671
+
1672
+ CREATE TRIGGER scheduled_task_run_personal_resource_admission
1673
+ AFTER INSERT ON "scheduled_task_runs"
1674
+ FOR EACH ROW EXECUTE FUNCTION admit_scheduled_task_run_personal_resources();
1675
+
1676
+ CREATE TRIGGER scheduled_task_attempt_once_retry_prepare
1677
+ BEFORE INSERT ON "session_turn_attempts"
1678
+ FOR EACH ROW EXECUTE FUNCTION prepare_scheduled_task_attempt_once_grants();
1679
+
1680
+ CREATE TRIGGER zz_scheduled_task_attempt_personal_resource_match
1681
+ AFTER INSERT ON "session_turn_attempts"
1682
+ FOR EACH ROW EXECUTE FUNCTION validate_scheduled_task_attempt_personal_resources();
1683
+
1684
+ CREATE OR REPLACE FUNCTION scheduled_task_run_personal_resource_authority(
1685
+ p_account_id uuid,
1686
+ p_workspace_id uuid,
1687
+ p_run_id uuid
1688
+ )
1689
+ RETURNS TABLE (
1690
+ task_id uuid,
1691
+ task_authority_revision bigint,
1692
+ execution_digest text,
1693
+ initiating_human_subject_id text,
1694
+ resource_kind text,
1695
+ resource_id uuid,
1696
+ resource_version_id uuid,
1697
+ selection_sources text[],
1698
+ authority_id uuid,
1699
+ authority_generation bigint,
1700
+ grant_id uuid,
1701
+ grant_generation bigint,
1702
+ grant_mode text
1703
+ )
1704
+ LANGUAGE plpgsql
1705
+ SECURITY DEFINER
1706
+ SET search_path = pg_catalog, pg_temp
1707
+ AS $scheduled_task_run_personal_resource_authority$
1708
+ BEGIN
1709
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
1710
+ OR p_workspace_id IS DISTINCT FROM nullif(
1711
+ current_setting('opengeni.workspace_id', true), ''
1712
+ )::uuid
1713
+ THEN
1714
+ RAISE EXCEPTION 'scheduled run personal-resource scope mismatch' USING ERRCODE = '42501';
1715
+ END IF;
1716
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
1717
+ backend_pid, transaction_id, capability_kind
1718
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'run_admit')
1719
+ ON CONFLICT DO NOTHING;
1720
+ RETURN QUERY
1721
+ SELECT admission.task_id, admission.task_authority_revision,
1722
+ admission.execution_digest, admission.initiating_human_subject_id, snapshot.resource_kind,
1723
+ snapshot.resource_id, snapshot.resource_version_id, snapshot.selection_sources,
1724
+ snapshot.authority_id, snapshot.authority_generation, snapshot.grant_id,
1725
+ snapshot.grant_generation, snapshot.grant_mode
1726
+ FROM scheduled_task_run_personal_resource_admissions admission
1727
+ JOIN scheduled_task_run_personal_resource_snapshots snapshot
1728
+ ON snapshot.run_id = admission.run_id
1729
+ WHERE admission.run_id = p_run_id
1730
+ AND admission.account_id = p_account_id
1731
+ AND admission.workspace_id = p_workspace_id
1732
+ ORDER BY snapshot.resource_kind, snapshot.resource_id;
1733
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
1734
+ WHERE backend_pid = pg_backend_pid()
1735
+ AND transaction_id = pg_current_xact_id_if_assigned()
1736
+ AND capability_kind = 'run_admit';
1737
+ END
1738
+ $scheduled_task_run_personal_resource_authority$;
1739
+
1740
+ -- The migration target may be public or a dedicated embed schema. Bind that
1741
+ -- trusted schema explicitly into every privileged routine and put pg_temp last
1742
+ -- so caller-created temporary relations can never shadow durable authority.
1743
+ DO $scheduled_personal_resource_search_path$
1744
+ DECLARE
1745
+ data_schema text := current_schema();
1746
+ BEGIN
1747
+ EXECUTE format(
1748
+ 'ALTER FUNCTION %1$I.scheduled_task_execution_state(%1$I.scheduled_tasks) '
1749
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1750
+ data_schema
1751
+ );
1752
+ EXECUTE format(
1753
+ 'ALTER FUNCTION %1$I.scheduled_task_execution_digest(%1$I.scheduled_tasks) '
1754
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1755
+ data_schema
1756
+ );
1757
+ EXECUTE format(
1758
+ 'ALTER FUNCTION %1$I.set_scheduled_task_execution_digest() '
1759
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1760
+ data_schema
1761
+ );
1762
+ EXECUTE format(
1763
+ 'ALTER FUNCTION %1$I.freeze_scheduled_task_personal_resources(uuid, uuid, uuid, bigint) '
1764
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1765
+ data_schema
1766
+ );
1767
+ EXECUTE format(
1768
+ 'ALTER FUNCTION %1$I.clone_scheduled_task_personal_resource_authority('
1769
+ || 'uuid, uuid, uuid, bigint, bigint) SET search_path = pg_catalog, %1$I, pg_temp',
1770
+ data_schema
1771
+ );
1772
+ EXECUTE format(
1773
+ 'ALTER FUNCTION %1$I.materialize_scheduled_task_reusable_session_from_run('
1774
+ || 'uuid, uuid, uuid, uuid, uuid, bigint, text) '
1775
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1776
+ data_schema
1777
+ );
1778
+ EXECUTE format(
1779
+ 'ALTER FUNCTION %1$I.fence_scheduled_task_personal_resource_execution_update() '
1780
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1781
+ data_schema
1782
+ );
1783
+ EXECUTE format(
1784
+ 'ALTER FUNCTION %1$I.admit_scheduled_task_run_personal_resources() '
1785
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1786
+ data_schema
1787
+ );
1788
+ EXECUTE format(
1789
+ 'ALTER FUNCTION %1$I.prepare_scheduled_task_attempt_once_grants() '
1790
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1791
+ data_schema
1792
+ );
1793
+ EXECUTE format(
1794
+ 'ALTER FUNCTION %1$I.validate_scheduled_task_attempt_personal_resources() '
1795
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1796
+ data_schema
1797
+ );
1798
+ EXECUTE format(
1799
+ 'ALTER FUNCTION %1$I.scheduled_task_run_personal_resource_authority(uuid, uuid, uuid) '
1800
+ || 'SET search_path = pg_catalog, %1$I, pg_temp',
1801
+ data_schema
1802
+ );
1803
+ END
1804
+ $scheduled_personal_resource_search_path$;
1805
+
1806
+ REVOKE ALL ON FUNCTION scheduled_task_execution_state(scheduled_tasks) FROM PUBLIC;
1807
+ REVOKE ALL ON FUNCTION scheduled_task_execution_digest(scheduled_tasks) FROM PUBLIC;
1808
+ REVOKE ALL ON FUNCTION set_scheduled_task_execution_digest() FROM PUBLIC;
1809
+ REVOKE ALL ON FUNCTION freeze_scheduled_task_personal_resources(uuid, uuid, uuid, bigint)
1810
+ FROM PUBLIC;
1811
+ REVOKE ALL ON FUNCTION clone_scheduled_task_personal_resource_authority(
1812
+ uuid, uuid, uuid, bigint, bigint
1813
+ ) FROM PUBLIC;
1814
+ REVOKE ALL ON FUNCTION materialize_scheduled_task_reusable_session_from_run(
1815
+ uuid, uuid, uuid, uuid, uuid, bigint, text
1816
+ ) FROM PUBLIC;
1817
+ REVOKE ALL ON FUNCTION fence_scheduled_task_personal_resource_execution_update() FROM PUBLIC;
1818
+ REVOKE ALL ON FUNCTION scheduled_task_run_personal_resource_authority(uuid, uuid, uuid)
1819
+ FROM PUBLIC;
1820
+ REVOKE ALL ON FUNCTION admit_scheduled_task_run_personal_resources() FROM PUBLIC;
1821
+ REVOKE ALL ON FUNCTION prepare_scheduled_task_attempt_once_grants() FROM PUBLIC;
1822
+ REVOKE ALL ON FUNCTION validate_scheduled_task_attempt_personal_resources() FROM PUBLIC;
1823
+
1824
+ DO $scheduled_personal_resource_runtime_grants$
1825
+ DECLARE
1826
+ table_name text;
1827
+ BEGIN
1828
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1829
+ GRANT EXECUTE ON FUNCTION
1830
+ opengeni_private.scheduled_personal_resource_capability_active(text)
1831
+ TO opengeni_app;
1832
+ GRANT EXECUTE ON FUNCTION
1833
+ freeze_scheduled_task_personal_resources(uuid, uuid, uuid, bigint)
1834
+ TO opengeni_app;
1835
+ GRANT EXECUTE ON FUNCTION
1836
+ clone_scheduled_task_personal_resource_authority(uuid, uuid, uuid, bigint, bigint)
1837
+ TO opengeni_app;
1838
+ GRANT EXECUTE ON FUNCTION
1839
+ materialize_scheduled_task_reusable_session_from_run(
1840
+ uuid, uuid, uuid, uuid, uuid, bigint, text
1841
+ ) TO opengeni_app;
1842
+ GRANT EXECUTE ON FUNCTION
1843
+ scheduled_task_run_personal_resource_authority(uuid, uuid, uuid)
1844
+ TO opengeni_app;
1845
+ FOREACH table_name IN ARRAY ARRAY[
1846
+ 'scheduled_task_personal_resource_authorities',
1847
+ 'scheduled_task_personal_resource_snapshots',
1848
+ 'scheduled_task_run_personal_resource_admissions',
1849
+ 'scheduled_task_run_personal_resource_snapshots',
1850
+ 'scheduled_task_run_personal_resource_once_receipts'
1851
+ ] LOOP
1852
+ EXECUTE format('REVOKE ALL ON TABLE %I FROM opengeni_app', table_name);
1853
+ END LOOP;
1854
+ END IF;
1855
+ END
1856
+ $scheduled_personal_resource_runtime_grants$;
1857
+
1858
+ COMMENT ON TABLE "scheduled_task_personal_resource_authorities" IS
1859
+ 'Immutable causal-human and membership header for one scheduled-task authority revision.';
1860
+ COMMENT ON TABLE "scheduled_task_personal_resource_snapshots" IS
1861
+ 'Server-derived exact personal Variable Set/Rig authority frozen at scheduled-task write.';
1862
+ COMMENT ON TABLE "scheduled_task_run_personal_resource_admissions" IS
1863
+ 'Accepted occurrence binding to one immutable task authority revision before dispatch.';
1864
+ COMMENT ON TABLE "scheduled_task_run_personal_resource_snapshots" IS
1865
+ 'Exact occurrence copy used to fence queued/recovering scheduled work against widening.';
1866
+ COMMENT ON TABLE "scheduled_task_run_personal_resource_once_receipts" IS
1867
+ 'Unique once-grant ownership for one admitted scheduled occurrence across attempt retries.';