@opengeni/db 5.0.1 → 6.0.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 (212) hide show
  1. package/dist/automations.d.ts +0 -2
  2. package/dist/canonical-human-identities.d.ts +4 -0
  3. package/dist/canonical-human-identities.js +3 -3
  4. package/dist/capability-components.d.ts +5 -13
  5. package/dist/capability-integrations.d.ts +23 -8
  6. package/dist/capability-reconciliation.d.ts +5 -0
  7. package/dist/capability-reconciliation.js +21 -0
  8. package/dist/capability-reconciliation.js.map +1 -0
  9. package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
  10. package/dist/chunk-4UJCXBR2.js.map +1 -0
  11. package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
  12. package/dist/chunk-AJA2ZIUW.js +219 -0
  13. package/dist/chunk-AJA2ZIUW.js.map +1 -0
  14. package/dist/chunk-CDAT6EKW.js +247 -0
  15. package/dist/chunk-CDAT6EKW.js.map +1 -0
  16. package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
  17. package/dist/chunk-CJI6G254.js.map +1 -0
  18. package/dist/chunk-G32NVMRJ.js +276 -0
  19. package/dist/chunk-G32NVMRJ.js.map +1 -0
  20. package/dist/chunk-GABXCG3N.js +5811 -0
  21. package/dist/chunk-GABXCG3N.js.map +1 -0
  22. package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
  23. package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
  24. package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
  25. package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
  26. package/dist/chunk-MN5PZMEK.js.map +1 -0
  27. package/dist/chunk-PJI4MUZ7.js +131 -0
  28. package/dist/chunk-PJI4MUZ7.js.map +1 -0
  29. package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
  30. package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
  31. package/dist/chunk-RL2MUXM3.js.map +1 -0
  32. package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
  33. package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
  34. package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
  35. package/dist/chunk-XIXVVBW2.js.map +1 -0
  36. package/dist/chunk-ZPJ6V2VG.js +156 -0
  37. package/dist/chunk-ZPJ6V2VG.js.map +1 -0
  38. package/dist/codex-capacity-recovery.d.ts +12 -0
  39. package/dist/codex-failure-eligibility.d.ts +9 -0
  40. package/dist/company-profile-schema.d.ts +4 -2
  41. package/dist/connect-attempts.d.ts +15 -0
  42. package/dist/connect-attempts.js +25 -0
  43. package/dist/connect-attempts.js.map +1 -0
  44. package/dist/connection-authority.d.ts +7 -63
  45. package/dist/connection-refresh-lock.d.ts +4 -0
  46. package/dist/connection-token-resolver.d.ts +8 -43
  47. package/dist/database-timing.d.ts +10 -0
  48. package/dist/database.d.ts +4 -0
  49. package/dist/editable-artifact-durable-export.js +2 -2
  50. package/dist/editable-artifacts.d.ts +9 -0
  51. package/dist/editable-artifacts.js +7 -5
  52. package/dist/index.d.ts +447 -78
  53. package/dist/index.js +12705 -12872
  54. package/dist/index.js.map +1 -1
  55. package/dist/insights-usage-bundle.d.ts +3 -0
  56. package/dist/integration-bindings.d.ts +1 -1
  57. package/dist/integration-facets.d.ts +18 -1
  58. package/dist/knowledge-entries-schema.d.ts +2 -0
  59. package/dist/knowledge-entries.d.ts +1 -0
  60. package/dist/knowledge-indexing.d.ts +1 -0
  61. package/dist/live-session-attempt.d.ts +35 -137
  62. package/dist/managed-auth-session-sets.js +3 -3
  63. package/dist/mcp-account-bindings.d.ts +4 -0
  64. package/dist/mcp-oauth.d.ts +11 -0
  65. package/dist/mcp-operations.js +2 -2
  66. package/dist/migrate.js +1 -1
  67. package/dist/organization-integration-policy.d.ts +51 -0
  68. package/dist/organization-integration-policy.js +18 -0
  69. package/dist/organization-integration-policy.js.map +1 -0
  70. package/dist/organization-usage.d.ts +20 -0
  71. package/dist/plugin-packages.d.ts +9 -12
  72. package/dist/pr-review.d.ts +0 -11
  73. package/dist/preference-registry-schema.d.ts +17 -0
  74. package/dist/provision-roles.js +1 -1
  75. package/dist/retained-provider-commands.d.ts +124 -6
  76. package/dist/retained-provider-commands.js +29 -97
  77. package/dist/retained-provider-commands.js.map +1 -1
  78. package/dist/retired-session-create-metadata.d.ts +4 -0
  79. package/dist/runtime-posture.d.ts +7 -5
  80. package/dist/schema.d.ts +876 -656
  81. package/dist/schema.js +5 -7
  82. package/dist/session-admission-block.d.ts +32 -0
  83. package/dist/session-attempt-writers.d.ts +4 -0
  84. package/dist/session-background-commands.js +5 -4
  85. package/dist/session-command-output.js +8 -126
  86. package/dist/session-command-output.js.map +1 -1
  87. package/dist/session-control.d.ts +5 -1
  88. package/dist/session-event-slices.d.ts +10 -0
  89. package/dist/session-event-slices.js +8 -222
  90. package/dist/session-event-slices.js.map +1 -1
  91. package/dist/session-file-attachments.d.ts +28 -0
  92. package/dist/session-goal-reports.d.ts +43 -0
  93. package/dist/session-mcp-approval.d.ts +4 -0
  94. package/dist/session-mcp-credential-rotation.js +5 -4
  95. package/dist/session-mcp-credential-rotation.js.map +1 -1
  96. package/dist/session-message-search.d.ts +22 -0
  97. package/dist/session-queue-commands.d.ts +2 -6
  98. package/dist/session-retry.d.ts +36 -0
  99. package/dist/session-tenancy.js +3 -3
  100. package/dist/skill-source-release-impact.d.ts +18 -0
  101. package/dist/skill-source-release.d.ts +9 -0
  102. package/dist/skills.d.ts +7 -1
  103. package/dist/user-resource-authority.d.ts +0 -8
  104. package/dist/video-generation.js +3 -3
  105. package/dist/workspace-tool-defaults.js +5 -4
  106. package/dist/workspace-tool-defaults.js.map +1 -1
  107. package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
  108. package/drizzle/0469_knowledge_source_discovery.sql +85 -0
  109. package/drizzle/0470_organization_integration_policy.sql +151 -0
  110. package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
  111. package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
  112. package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
  113. package/drizzle/0474_goal_report_requirements.sql +156 -0
  114. package/drizzle/0475_session_event_history_statistics.sql +15 -0
  115. package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
  116. package/drizzle/0477_managed_sign_in_methods.sql +303 -0
  117. package/drizzle/0478_sender_owned_connections.sql +2772 -0
  118. package/drizzle/0479_connection_create_idempotency.sql +16 -0
  119. package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
  120. package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
  121. package/drizzle/0482_remove_packs.sql +417 -0
  122. package/drizzle/0483_preclaim_admission_block.sql +85 -0
  123. package/drizzle/0484_insights_usage_projection.sql +211 -0
  124. package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
  125. package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
  126. package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
  127. package/drizzle/0488_permanent_skill_removal.sql +207 -0
  128. package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
  129. package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
  130. package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
  131. package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
  132. package/drizzle/0493_stopping_command_error_containment.sql +58 -0
  133. package/drizzle/0494_mcp_account_bindings.sql +334 -0
  134. package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
  135. package/drizzle/0496_supervised_command_settlement.sql +386 -0
  136. package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
  137. package/drizzle/0498_enrollment_membership_fence.sql +87 -0
  138. package/drizzle/0499_session_attachment_access.sql +224 -0
  139. package/drizzle/0500_session_execution_epoch.sql +37 -0
  140. package/drizzle/0501_session_sharing_execution.sql +591 -0
  141. package/drizzle/0502_active_message_boundary_forks.sql +30 -0
  142. package/package.json +17 -5
  143. package/src/automations.ts +17 -73
  144. package/src/canonical-human-identities.ts +17 -5
  145. package/src/capability-components.ts +7 -24
  146. package/src/capability-integrations.ts +301 -21
  147. package/src/capability-reconciliation.ts +19 -0
  148. package/src/codex-capacity-recovery.ts +47 -0
  149. package/src/codex-failure-eligibility.ts +93 -0
  150. package/src/connect-attempts.ts +49 -18
  151. package/src/connection-authority.ts +12 -158
  152. package/src/connection-refresh-lock.ts +22 -0
  153. package/src/connection-token-resolver.ts +58 -530
  154. package/src/database-timing.ts +43 -0
  155. package/src/database.ts +61 -26
  156. package/src/editable-artifacts.ts +1 -1
  157. package/src/index.ts +3578 -1273
  158. package/src/insights-model-bundle.ts +19 -7
  159. package/src/insights-usage-bundle.ts +16 -19
  160. package/src/integration-bindings.ts +2 -7
  161. package/src/integration-facets.ts +73 -6
  162. package/src/knowledge-entries.ts +8 -1
  163. package/src/live-session-attempt.ts +0 -22
  164. package/src/mcp-account-bindings.ts +12 -0
  165. package/src/mcp-oauth.ts +26 -0
  166. package/src/organization-integration-policy.ts +235 -0
  167. package/src/organization-usage.ts +83 -0
  168. package/src/plugin-packages.ts +226 -42
  169. package/src/pr-review.ts +7 -69
  170. package/src/preference-registry-schema.ts +1 -0
  171. package/src/provision-roles.ts +50 -37
  172. package/src/retained-provider-commands.ts +286 -9
  173. package/src/retired-session-create-metadata.ts +16 -0
  174. package/src/runtime-posture.ts +217 -13
  175. package/src/schema.ts +104 -120
  176. package/src/session-admission-block.ts +29 -0
  177. package/src/session-attempt-writers.ts +48 -0
  178. package/src/session-control.ts +21 -5
  179. package/src/session-event-slices.ts +76 -26
  180. package/src/session-file-attachments.ts +66 -0
  181. package/src/session-goal-reports.ts +255 -0
  182. package/src/session-mcp-approval.ts +30 -0
  183. package/src/session-message-search.ts +348 -0
  184. package/src/session-queue-commands.ts +60 -36
  185. package/src/session-retry.ts +319 -0
  186. package/src/skill-config-migration.ts +11 -2
  187. package/src/skill-source-release-impact.ts +33 -0
  188. package/src/skill-source-release.ts +67 -26
  189. package/src/skills.ts +44 -6
  190. package/src/user-resource-authority.ts +0 -56
  191. package/dist/chunk-6T2GGYWE.js.map +0 -1
  192. package/dist/chunk-7HMK3B45.js.map +0 -1
  193. package/dist/chunk-DPONPYJD.js.map +0 -1
  194. package/dist/chunk-F73UKJY6.js.map +0 -1
  195. package/dist/chunk-T6YLHJLS.js.map +0 -1
  196. package/dist/host-mcp-binding-match.d.ts +0 -5
  197. package/dist/host-mcp-bindings.d.ts +0 -79
  198. package/dist/host-mcp-resolvers.d.ts +0 -47
  199. package/dist/host-mcp-task-authority.d.ts +0 -43
  200. package/dist/host-selection-identity.d.ts +0 -6
  201. package/dist/pack-components.d.ts +0 -83
  202. package/dist/pack-installations.d.ts +0 -86
  203. package/src/host-mcp-binding-match.ts +0 -34
  204. package/src/host-mcp-bindings.ts +0 -777
  205. package/src/host-mcp-resolvers.ts +0 -266
  206. package/src/host-mcp-task-authority.ts +0 -384
  207. package/src/host-selection-identity.ts +0 -29
  208. package/src/pack-components.ts +0 -980
  209. package/src/pack-installations.ts +0 -950
  210. /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
  211. /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
  212. /package/dist/{chunk-T6BWIGWJ.js.map → chunk-RIYHP2WU.js.map} +0 -0
@@ -0,0 +1,2772 @@
1
+ -- deployment-mode: maintenance
2
+ -- Sender-owned connection execution. Stop all old API and worker processes.
3
+ -- Historical grant receipts remain audit evidence, never execution authority.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '5min';
6
+ DO $drain$
7
+ DECLARE roles jsonb := nullif(current_setting('opengeni.migration_application_roles', true), '')::jsonb;
8
+ BEGIN
9
+ IF roles IS NULL OR jsonb_typeof(roles) <> 'array' OR jsonb_array_length(roles) NOT BETWEEN 1 AND 16
10
+ OR EXISTS (SELECT 1 FROM jsonb_array_elements(roles) item WHERE jsonb_typeof(item) <> 'string'
11
+ OR btrim(item #>> '{}') = '' OR item #>> '{}' <> btrim(item #>> '{}') OR octet_length(item #>> '{}') > 63)
12
+ OR EXISTS (SELECT 1 FROM pg_stat_activity a JOIN jsonb_array_elements_text(roles) r ON r = a.usename
13
+ WHERE a.datname = current_database() AND a.pid <> pg_backend_pid()) THEN
14
+ RAISE EXCEPTION 'sender-owned connection migration requires stopped application roles' USING ERRCODE = '55000';
15
+ END IF;
16
+ END $drain$;
17
+
18
+ -- Replace ownership restrictions with setup preferences. Existing installations
19
+ -- and credentials retain their selected ownership; this changes catalog setup only.
20
+ ALTER TABLE capability_catalog_items NO FORCE ROW LEVEL SECURITY;
21
+ UPDATE capability_catalog_items
22
+ SET metadata = (metadata - 'connectionOwnership') || jsonb_build_object(
23
+ 'defaultConnectionOwnership', CASE
24
+ WHEN endpoint_url = 'https://gmailmcp.googleapis.com/mcp/v1' THEN 'personal'
25
+ WHEN endpoint_url = 'https://mcp.slack.com/mcp' THEN 'workspace'
26
+ ELSE coalesce(metadata ->> 'defaultConnectionOwnership', 'workspace') END)
27
+ WHERE metadata ? 'connectionOwnership';
28
+ UPDATE capability_catalog_items
29
+ SET metadata = jsonb_set(metadata, '{oauthProfile}',
30
+ ((metadata -> 'oauthProfile') - 'allowedOwnership') || jsonb_build_object(
31
+ 'defaultOwnership', CASE
32
+ WHEN metadata #> '{oauthProfile,allowedOwnership}' = '["personal"]'::jsonb THEN 'personal'
33
+ ELSE 'workspace' END))
34
+ WHERE jsonb_typeof(metadata -> 'oauthProfile') = 'object'
35
+ AND metadata -> 'oauthProfile' ? 'allowedOwnership';
36
+ ALTER TABLE capability_catalog_items FORCE ROW LEVEL SECURITY;
37
+
38
+ -- Execution identity is immutable, separate from the mutable task configuration
39
+ -- digest. Preserve existing accepted-run evidence during this maintenance step.
40
+ ALTER TABLE scheduled_tasks NO FORCE ROW LEVEL SECURITY;
41
+ CREATE TEMP TABLE sender_schedule_bindings_before ON COMMIT DROP AS
42
+ SELECT id, authority_revision, execution_digest FROM scheduled_tasks;
43
+ ALTER TABLE scheduled_tasks ADD COLUMN owner_subject_id text
44
+ CHECK (owner_subject_id IS NULL OR length(btrim(owner_subject_id)) BETWEEN 1 AND 1024);
45
+ DO $owner_digest$
46
+ DECLARE signature text; definition text;
47
+ BEGIN
48
+ FOREACH signature IN ARRAY ARRAY[
49
+ 'scheduled_task_execution_state(scheduled_tasks)',
50
+ 'scheduled_task_execution_digest(scheduled_tasks)',
51
+ 'set_scheduled_task_execution_digest()',
52
+ 'fence_scheduled_task_personal_resource_execution_update()',
53
+ 'fence_scheduled_task_connection_authority_execution_update()'
54
+ ] LOOP
55
+ definition := pg_get_functiondef(signature::regprocedure);
56
+ IF strpos(definition, '''execution_digest''') = 0 THEN
57
+ RAISE EXCEPTION 'scheduled task digest definition changed: %', signature;
58
+ END IF;
59
+ EXECUTE replace(definition, '''execution_digest''', '''execution_digest'', ''owner_subject_id''');
60
+ END LOOP;
61
+ END
62
+ $owner_digest$;
63
+ ALTER TABLE scheduled_task_revision_authorities NO FORCE ROW LEVEL SECURITY;
64
+ -- Backfills obey the same workspace mutation fence as application writers.
65
+ -- The owner posture above makes every affected workspace visible under RLS.
66
+ DO $schedule_fences$
67
+ DECLARE workspace_value uuid;
68
+ BEGIN
69
+ FOR workspace_value IN SELECT DISTINCT workspace_id FROM scheduled_tasks ORDER BY workspace_id LOOP
70
+ PERFORM pg_advisory_xact_lock(hashtextextended('session-tenancy:' || workspace_value::text, 0));
71
+ END LOOP;
72
+ END
73
+ $schedule_fences$;
74
+ -- Only non-executable ownership/status fields change here. Retain the exact
75
+ -- stored digest even when an older additive migration left new nullable keys.
76
+ ALTER TABLE scheduled_tasks DISABLE TRIGGER zz_scheduled_task_execution_digest;
77
+ UPDATE scheduled_tasks task SET owner_subject_id = coalesce(
78
+ (SELECT authority.subject_id FROM scheduled_task_revision_authorities authority
79
+ WHERE authority.task_id = task.id AND authority.account_id = task.account_id
80
+ AND authority.workspace_id = task.workspace_id
81
+ AND authority.task_authority_revision = task.authority_revision),
82
+ CASE WHEN task.created_by_kind = 'subject'
83
+ AND NOT (task.created_by_context ?| ARRAY['via','viaTruncated','provenanceError','backfill'])
84
+ THEN task.created_by_subject_id END
85
+ ) WHERE task.action ->> 'kind' = 'agent_turn';
86
+ ALTER TABLE scheduled_task_revision_authorities FORCE ROW LEVEL SECURITY;
87
+ -- Do not keep firing old personal work when its human owner cannot be proven.
88
+ -- Preserve its instructions and historical selections for an owner to recreate.
89
+ UPDATE scheduled_tasks SET status = 'paused'
90
+ WHERE action ->> 'kind' = 'agent_turn' AND owner_subject_id IS NULL
91
+ AND status = 'active' AND personal_connection_delegations <> '[]'::jsonb;
92
+ ALTER TABLE scheduled_tasks ENABLE TRIGGER zz_scheduled_task_execution_digest;
93
+ DO $owner_binding_check$
94
+ BEGIN
95
+ IF EXISTS (SELECT 1 FROM scheduled_tasks task JOIN sender_schedule_bindings_before prior ON prior.id = task.id
96
+ WHERE task.authority_revision IS DISTINCT FROM prior.authority_revision
97
+ OR task.execution_digest IS DISTINCT FROM prior.execution_digest) THEN
98
+ RAISE EXCEPTION 'schedule ownership backfill changed accepted execution bindings';
99
+ END IF;
100
+ END
101
+ $owner_binding_check$;
102
+ ALTER TABLE scheduled_tasks FORCE ROW LEVEL SECURITY;
103
+ CREATE FUNCTION opengeni_private.guard_scheduled_task_owner()
104
+ RETURNS trigger LANGUAGE plpgsql SET search_path = pg_catalog AS $body$
105
+ DECLARE expected_owner text;
106
+ BEGIN
107
+ IF TG_OP = 'UPDATE' THEN
108
+ IF NEW.owner_subject_id IS DISTINCT FROM OLD.owner_subject_id THEN
109
+ RAISE EXCEPTION 'schedule owner is immutable; copy the schedule to use another account'
110
+ USING ERRCODE = '42501';
111
+ END IF;
112
+ ELSE
113
+ expected_owner := CASE WHEN NEW.action ->> 'kind' = 'agent_turn'
114
+ THEN nullif(current_setting('opengeni.initiating_human_subject_id', true), '') END;
115
+ IF NEW.owner_subject_id IS DISTINCT FROM expected_owner THEN
116
+ RAISE EXCEPTION 'schedule owner differs from verified creator' USING ERRCODE = '42501';
117
+ END IF;
118
+ END IF;
119
+ RETURN NEW;
120
+ END
121
+ $body$;
122
+ REVOKE ALL ON FUNCTION opengeni_private.guard_scheduled_task_owner() FROM PUBLIC;
123
+ CREATE TRIGGER scheduled_task_owner_immutable BEFORE INSERT OR UPDATE ON scheduled_tasks
124
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_scheduled_task_owner();
125
+
126
+ CREATE FUNCTION opengeni_private.guard_scheduled_run_owner()
127
+ RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT AS $body$
128
+ DECLARE expected_owner text;
129
+ BEGIN
130
+ IF NEW.action_kind <> 'agent_turn' THEN RETURN NEW; END IF;
131
+ SELECT task.owner_subject_id INTO STRICT expected_owner FROM scheduled_tasks task
132
+ WHERE task.id = NEW.task_id AND task.account_id = NEW.account_id
133
+ AND task.workspace_id = NEW.workspace_id FOR SHARE;
134
+ IF NEW.accepted_execution_snapshot ->> 'causalHumanSubjectId' IS DISTINCT FROM expected_owner
135
+ OR NEW.accepted_execution_snapshot #>> '{task,ownerSubjectId}' IS DISTINCT FROM expected_owner
136
+ THEN RAISE EXCEPTION 'scheduled run differs from immutable execution owner' USING ERRCODE = '42501'; END IF;
137
+ RETURN NEW;
138
+ END
139
+ $body$;
140
+ REVOKE ALL ON FUNCTION opengeni_private.guard_scheduled_run_owner() FROM PUBLIC;
141
+ CREATE TRIGGER scheduled_run_owner_matches BEFORE INSERT ON scheduled_task_runs
142
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.guard_scheduled_run_owner();
143
+
144
+ -- Only migration-owned capture/resolution routines can call this helper.
145
+ -- Restore the caller context after the scoped read; exceptions roll back the
146
+ -- PL/pgSQL block, including its transaction-local context changes.
147
+ CREATE FUNCTION opengeni_private.read_sender_connection(
148
+ p_account_id uuid, p_origin_workspace_id uuid, p_connection_id uuid, p_owner_subject_id text
149
+ ) RETURNS SETOF connections
150
+ LANGUAGE plpgsql SECURITY INVOKER SET search_path FROM CURRENT
151
+ AS $body$
152
+ DECLARE
153
+ prior_workspace text := current_setting('opengeni.workspace_id', true);
154
+ prior_subject text := current_setting('opengeni.subject_id', true);
155
+ BEGIN
156
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
157
+ OR p_origin_workspace_id IS NULL
158
+ THEN RAISE EXCEPTION 'connection account scope mismatch' USING ERRCODE = '42501'; END IF;
159
+ PERFORM set_config('opengeni.workspace_id', p_origin_workspace_id::text, true);
160
+ PERFORM set_config('opengeni.subject_id', coalesce(p_owner_subject_id, ''), true);
161
+ RETURN QUERY SELECT c.* FROM connections c
162
+ WHERE c.id = p_connection_id AND c.account_id = p_account_id
163
+ AND c.workspace_id = p_origin_workspace_id
164
+ AND c.origin_workspace_id = p_origin_workspace_id
165
+ AND c.subject_id IS NOT DISTINCT FROM p_owner_subject_id
166
+ FOR SHARE;
167
+ PERFORM set_config('opengeni.workspace_id', coalesce(prior_workspace, ''), true);
168
+ PERFORM set_config('opengeni.subject_id', coalesce(prior_subject, ''), true);
169
+ EXCEPTION WHEN OTHERS THEN RAISE;
170
+ END
171
+ $body$;
172
+ REVOKE ALL ON FUNCTION opengeni_private.read_sender_connection(uuid,uuid,uuid,text) FROM PUBLIC;
173
+
174
+
175
+ -- Older snapshots remain readable audit facts. The resolver below refuses
176
+ -- their provenance; only new sender snapshots authorize execution.
177
+ ALTER TABLE turn_connection_authority_snapshots
178
+ DROP CONSTRAINT turn_connection_authority_grant_check,
179
+ ADD CONSTRAINT "turn_connection_authority_grant_check" CHECK (
180
+ ("authority_scope" = 'user'
181
+ AND "authority_source" = 'user_delegation'
182
+ AND "owner_subject_id" IS NOT NULL
183
+ AND "owner_organization_membership_id" IS NOT NULL
184
+ AND "membership_authorization_revision" > 0
185
+ AND "authority_id" IS NOT NULL AND "authority_generation" > 0
186
+ AND "grant_id" IS NOT NULL AND "grant_generation" > 0
187
+ AND "grant_mode" IN ('once', 'session', 'always')
188
+ AND "grant_context" IN ('user_private', 'workspace_shared')
189
+ AND (("grant_mode" = 'always' AND "grant_session_id" IS NULL
190
+ AND "grant_authority_epoch" IS NULL)
191
+ OR ("grant_mode" IN ('once', 'session') AND "grant_session_id" = "session_id"
192
+ AND "grant_authority_epoch" > 0)))
193
+ OR ("authority_scope" = 'workspace'
194
+ AND "authority_source" IN ('explicit_workspace', 'legacy_workspace_omission')
195
+ AND "owner_subject_id" IS NULL
196
+ AND "owner_organization_membership_id" IS NULL
197
+ AND "membership_authorization_revision" IS NULL
198
+ AND "authority_id" IS NULL AND "authority_generation" IS NULL
199
+ AND "grant_id" IS NULL AND "grant_generation" IS NULL
200
+ AND "grant_mode" IS NULL AND "grant_context" IS NULL
201
+ AND "grant_session_id" IS NULL AND "grant_authority_epoch" IS NULL)
202
+ OR ("authority_scope" = 'legacy_user'
203
+ AND "authority_source" = 'legacy_user_compatibility'
204
+ AND "owner_subject_id" IS NOT NULL
205
+ AND "owner_organization_membership_id" IS NULL
206
+ AND "membership_authorization_revision" IS NULL
207
+ AND "authority_id" IS NULL AND "authority_generation" IS NULL
208
+ AND "grant_id" IS NULL AND "grant_generation" IS NULL
209
+ AND "grant_mode" IS NULL AND "grant_context" IS NULL
210
+ AND "grant_session_id" IS NULL AND "grant_authority_epoch" IS NULL)
211
+
212
+ OR (authority_scope = 'user' AND authority_source = 'sender'
213
+ AND owner_subject_id IS NOT NULL AND owner_organization_membership_id IS NOT NULL
214
+ AND membership_authorization_revision IS NOT NULL AND membership_authorization_revision > 0
215
+ AND authority_id IS NOT NULL AND authority_generation IS NOT NULL AND authority_generation > 0
216
+ AND grant_id IS NULL AND grant_generation IS NULL AND grant_mode IS NULL
217
+ AND grant_context IS NULL AND grant_session_id IS NULL AND grant_authority_epoch IS NULL)
218
+ );
219
+
220
+ DO $sender_capture$
221
+ BEGIN
222
+ EXECUTE format($definition$
223
+ CREATE OR REPLACE FUNCTION opengeni_private.capture_accepted_turn_connection_authorities()
224
+ RETURNS trigger
225
+ LANGUAGE plpgsql
226
+ SECURITY DEFINER
227
+ SET search_path = %1$I, pg_catalog, public, pg_temp
228
+ AS $body$
229
+ DECLARE
230
+ sender_prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
231
+ session_row sessions%%ROWTYPE;
232
+ item jsonb;
233
+ connection_row connections%%ROWTYPE;
234
+ membership_row organization_memberships%%ROWTYPE;
235
+ authority_row organization_user_resource_authorities%%ROWTYPE;
236
+ canonical jsonb;
237
+ existing_digest bytea;
238
+ existing_snapshot jsonb;
239
+ initiating_subject text;
240
+ verified_causal_subject text := coalesce(
241
+ nullif(current_setting('opengeni.initiating_human_subject_id', true), ''),
242
+ nullif(current_setting('opengeni.subject_id', true), '')
243
+ );
244
+ BEGIN
245
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
246
+ IF TG_OP = 'UPDATE' THEN
247
+ IF NEW.personal_connection_delegations IS NOT DISTINCT FROM OLD.personal_connection_delegations
248
+ THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NEW;
249
+ END IF;
250
+ IF NEW.status <> 'queued' OR EXISTS (
251
+ SELECT 1 FROM session_turn_attempts attempt
252
+ WHERE attempt.turn_id = NEW.id AND attempt.workspace_id = NEW.workspace_id
253
+ ) THEN
254
+ RAISE EXCEPTION 'accepted connection authority cannot change after claim'
255
+ USING ERRCODE = '42501';
256
+ END IF;
257
+ DELETE FROM turn_connection_authority_snapshots snapshot
258
+ WHERE snapshot.turn_id = NEW.id AND snapshot.workspace_id = NEW.workspace_id;
259
+ END IF;
260
+ SELECT session_value.* INTO STRICT session_row
261
+ FROM sessions session_value
262
+ WHERE session_value.id = NEW.session_id
263
+ AND session_value.account_id = NEW.account_id
264
+ AND session_value.workspace_id = NEW.workspace_id
265
+ FOR SHARE;
266
+ initiating_subject := coalesce(
267
+ nullif(btrim(NEW.initiating_human_subject_id), ''),
268
+ CASE WHEN NEW.initiator_kind = 'subject'
269
+ THEN nullif(btrim(NEW.initiator_subject_id), '') END
270
+ );
271
+
272
+ FOR item IN
273
+ SELECT value FROM jsonb_array_elements(NEW.personal_connection_delegations)
274
+ LOOP
275
+ IF item ? 'userDelegation' THEN
276
+ RAISE EXCEPTION 'connection grants are retired; refresh the client' USING ERRCODE = '22023';
277
+ END IF;
278
+ -- Social uses a distinct store. It is not activated here and must never
279
+ -- smuggle the common connection-authority envelope past this boundary.
280
+ IF item ->> 'connectionType' = 'social' THEN
281
+ CONTINUE;
282
+ END IF;
283
+ IF nullif(item ->> 'serverId', '') IS NULL
284
+ OR octet_length(item ->> 'serverId') > 256
285
+ OR nullif(item ->> 'connectionId', '') IS NULL
286
+ THEN
287
+ RAISE EXCEPTION 'invalid accepted connection selection' USING ERRCODE = '22023';
288
+ END IF;
289
+ IF initiating_subject IS NULL OR verified_causal_subject IS DISTINCT FROM initiating_subject THEN
290
+ RAISE EXCEPTION 'accepted connection authority causal human is unverified' USING ERRCODE = '42501';
291
+ END IF;
292
+ SELECT connection_value.* INTO connection_row
293
+ FROM opengeni_private.read_sender_connection(
294
+ NEW.account_id, nullif(item ->> 'originWorkspaceId', '')::uuid,
295
+ (item ->> 'connectionId')::uuid, initiating_subject
296
+ ) connection_value;
297
+
298
+ IF NOT FOUND THEN
299
+ RAISE EXCEPTION 'accepted connection selection is unavailable'
300
+ USING ERRCODE = '42501';
301
+ END IF;
302
+
303
+ IF connection_row.authority_scope <> 'user' THEN
304
+ RAISE EXCEPTION 'accepted personal connection scope is unavailable'
305
+ USING ERRCODE = '42501';
306
+ END IF;
307
+ IF initiating_subject IS NULL OR connection_row.subject_id IS DISTINCT FROM initiating_subject
308
+ OR connection_row.status <> 'active'
309
+ OR lower(connection_row.provider_domain) IS DISTINCT FROM lower(item ->> 'providerDomain')
310
+ OR (item ? 'kind' AND connection_row.kind IS DISTINCT FROM item ->> 'kind')
311
+ THEN
312
+ RAISE EXCEPTION 'accepted personal connection identity is unavailable'
313
+ USING ERRCODE = '42501';
314
+ END IF;
315
+ IF verified_causal_subject IS DISTINCT FROM initiating_subject THEN
316
+ RAISE EXCEPTION 'accepted connection authority causal human is unverified'
317
+ USING ERRCODE = '42501';
318
+ END IF;
319
+
320
+ SELECT membership.* INTO STRICT membership_row
321
+ FROM organization_memberships membership
322
+ WHERE membership.id = connection_row.owner_organization_membership_id
323
+ AND membership.account_id = NEW.account_id
324
+ AND membership.subject_id = initiating_subject
325
+ AND membership.status = 'active'
326
+ AND membership.revoked_at IS NULL
327
+ FOR SHARE;
328
+ IF membership_row.personal_workspace_id IS DISTINCT FROM NEW.workspace_id
329
+ AND NOT EXISTS (
330
+ SELECT 1 FROM workspace_memberships workspace_membership
331
+ WHERE workspace_membership.account_id = NEW.account_id
332
+ AND workspace_membership.workspace_id = NEW.workspace_id
333
+ AND workspace_membership.subject_id = initiating_subject
334
+ )
335
+ THEN
336
+ RAISE EXCEPTION 'personal connection owner lacks target workspace access'
337
+ USING ERRCODE = '42501';
338
+ END IF;
339
+
340
+ SELECT authority.* INTO STRICT authority_row
341
+ FROM organization_user_resource_authorities authority
342
+ WHERE authority.id = connection_row.authority_id
343
+ AND authority.account_id = NEW.account_id
344
+ AND authority.organization_membership_id = membership_row.id
345
+ AND authority.resource_kind = 'connection'
346
+ AND authority.resource_id = connection_row.id
347
+ AND authority.origin_workspace_id = connection_row.origin_workspace_id
348
+ AND authority.status = 'active'
349
+ AND authority.revoked_at IS NULL
350
+ FOR SHARE;
351
+
352
+ IF nullif(item ->> 'originWorkspaceId', '')::uuid
353
+ IS DISTINCT FROM connection_row.origin_workspace_id
354
+ THEN
355
+ RAISE EXCEPTION 'accepted connection origin is not server-resolved'
356
+ USING ERRCODE = '42501';
357
+ END IF;
358
+
359
+ canonical := jsonb_build_object(
360
+ 'organizationId', NEW.account_id,
361
+ 'originWorkspaceId', connection_row.origin_workspace_id,
362
+ 'targetWorkspaceId', NEW.workspace_id,
363
+ 'targetSessionId', NEW.session_id,
364
+ 'targetSessionVisibility', session_row.visibility,
365
+ 'targetSessionAuthorityEpoch', session_row.authority_epoch,
366
+ 'acceptedWork', jsonb_build_object('kind', 'turn', 'turnId', NEW.id),
367
+ 'connectionId', connection_row.id,
368
+ 'connectionGeneration', connection_row.authority_generation,
369
+ 'connectionStatus', 'active',
370
+ 'providerDomain', lower(connection_row.provider_domain),
371
+ 'connectionKind', connection_row.kind,
372
+ 'scope', 'user',
373
+ 'ownerSubjectId', initiating_subject,
374
+ 'ownerOrganizationMembershipId', membership_row.id,
375
+ 'ownerMembershipAuthorizationRevision', membership_row.authorization_revision,
376
+ 'authoritySource', 'sender',
377
+ 'selectionSources', jsonb_build_array('mcp:' || (item ->> 'serverId')),
378
+ 'userDelegation', NULL
379
+ );
380
+
381
+ SELECT snapshot.snapshot_digest, snapshot.canonical_snapshot
382
+ INTO existing_digest, existing_snapshot
383
+ FROM turn_connection_authority_snapshots snapshot
384
+ WHERE snapshot.turn_id = NEW.id AND snapshot.server_id = item ->> 'serverId';
385
+ IF FOUND THEN
386
+ IF existing_snapshot IS DISTINCT FROM canonical
387
+ OR existing_digest IS DISTINCT FROM digest(convert_to(canonical::text, 'UTF8'), 'sha256')
388
+ THEN
389
+ RAISE EXCEPTION 'accepted connection authority changed across recovery'
390
+ USING ERRCODE = '42501';
391
+ END IF;
392
+ CONTINUE;
393
+ END IF;
394
+
395
+ INSERT INTO turn_connection_authority_snapshots (
396
+ account_id, workspace_id, session_id, turn_id, server_id,
397
+ connection_id, connection_generation, origin_workspace_id,
398
+ provider_domain, connection_kind, authority_scope, authority_source, owner_subject_id,
399
+ owner_organization_membership_id, membership_authorization_revision,
400
+ authority_id, authority_generation, grant_id, grant_generation,
401
+ grant_mode, grant_context, grant_session_id, grant_authority_epoch,
402
+ session_visibility, session_authority_epoch, canonical_snapshot, snapshot_digest
403
+ ) VALUES (
404
+ NEW.account_id, NEW.workspace_id, NEW.session_id, NEW.id, item ->> 'serverId',
405
+ connection_row.id, connection_row.authority_generation,
406
+ connection_row.origin_workspace_id, lower(connection_row.provider_domain),
407
+ connection_row.kind, 'user', 'sender', initiating_subject, membership_row.id,
408
+ membership_row.authorization_revision, authority_row.id, authority_row.generation,
409
+ NULL, NULL, NULL, NULL, NULL, NULL,
410
+ session_row.visibility, session_row.authority_epoch, canonical,
411
+ digest(convert_to(canonical::text, 'UTF8'), 'sha256')
412
+ );
413
+ END LOOP;
414
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NEW;
415
+ EXCEPTION WHEN OTHERS THEN RAISE;
416
+ END
417
+ $body$
418
+ $definition$, current_schema());
419
+ END
420
+ $sender_capture$;
421
+ REVOKE ALL ON FUNCTION opengeni_private.capture_accepted_turn_connection_authorities() FROM PUBLIC;
422
+
423
+ DROP TRIGGER accepted_turn_connection_authority_capture ON session_turns;
424
+ CREATE TRIGGER accepted_turn_connection_authority_capture
425
+ AFTER INSERT OR UPDATE OF personal_connection_delegations ON session_turns
426
+ FOR EACH ROW WHEN (NEW.scheduled_task_run_id IS NULL)
427
+ EXECUTE FUNCTION opengeni_private.capture_accepted_turn_connection_authorities();
428
+ DROP FUNCTION opengeni_private.capture_accepted_turn_connection_authorities_0264();
429
+
430
+ CREATE OR REPLACE FUNCTION resolve_accepted_connection_use(
431
+ p_account_id uuid, p_workspace_id uuid, p_session_id uuid, p_turn_id uuid,
432
+ p_attempt_id uuid, p_execution_generation integer, p_physical_request_id uuid,
433
+ p_use_phase text, p_server_id text, p_connection_id uuid,
434
+ p_provider_domain text, p_connection_kind text, p_subject_scope text,
435
+ p_owner_subject_id text DEFAULT NULL
436
+ ) RETURNS TABLE (
437
+ authorization_status text, denial_reason text, resolved_connection_id uuid,
438
+ connection_generation bigint, origin_workspace_id uuid,
439
+ resolved_connection_kind text, authority_scope text, owner_subject_id text,
440
+ authority_id uuid, grant_id uuid
441
+ )
442
+ LANGUAGE plpgsql SECURITY DEFINER
443
+ SET search_path FROM CURRENT
444
+ AS $body$
445
+ #variable_conflict use_column
446
+ DECLARE
447
+ sender_prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
448
+ prior record;
449
+ connection_row record;
450
+ snapshot record;
451
+ session_row record;
452
+ turn_row record;
453
+ audit_initiator_kind text;
454
+ audit_initiator_subject text;
455
+ audit_initiating_human text;
456
+ audit_authority_epoch integer;
457
+ audit_authority_visibility text;
458
+ audit_owner_membership uuid;
459
+ request_digest bytea;
460
+ has_prior boolean := false;
461
+ reason text;
462
+ scheduled_run_id uuid;
463
+ BEGIN
464
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
465
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
466
+ OR p_workspace_id IS DISTINCT FROM nullif(current_setting('opengeni.workspace_id', true), '')::uuid
467
+ OR p_execution_generation <= 0
468
+ OR p_use_phase NOT IN ('credential_resolution', 'provider_request')
469
+ OR nullif(btrim(p_server_id), '') IS NULL OR octet_length(p_server_id) > 256
470
+ THEN RAISE EXCEPTION 'connection use scope mismatch' USING ERRCODE = '42501';
471
+ END IF;
472
+ request_digest := digest(convert_to(jsonb_build_object(
473
+ 'accountId', p_account_id, 'workspaceId', p_workspace_id,
474
+ 'sessionId', p_session_id, 'turnId', p_turn_id, 'attemptId', p_attempt_id,
475
+ 'executionGeneration', p_execution_generation, 'usePhase', p_use_phase,
476
+ 'serverId', p_server_id, 'connectionId', p_connection_id,
477
+ 'providerDomain', lower(p_provider_domain), 'connectionKind', p_connection_kind,
478
+ 'subjectScope', p_subject_scope, 'ownerSubjectId', p_owner_subject_id
479
+ )::text, 'UTF8'), 'sha256');
480
+
481
+ -- Canonical lifecycle lock prefix: control -> workspace -> session -> turn -> attempt.
482
+ PERFORM 1 FROM workspace_inference_controls control_row
483
+ WHERE control_row.account_id = p_account_id AND control_row.workspace_id = p_workspace_id
484
+ FOR SHARE;
485
+ IF NOT FOUND THEN reason := 'session_identity_changed'; END IF;
486
+ IF reason IS NULL THEN
487
+ PERFORM 1 FROM workspaces workspace_value
488
+ WHERE workspace_value.account_id = p_account_id AND workspace_value.id = p_workspace_id
489
+ FOR KEY SHARE;
490
+ IF NOT FOUND THEN reason := 'session_identity_changed'; END IF;
491
+ END IF;
492
+ IF reason IS NULL THEN
493
+ SELECT session_value.* INTO session_row FROM sessions session_value
494
+ WHERE session_value.id = p_session_id AND session_value.account_id = p_account_id
495
+ AND session_value.workspace_id = p_workspace_id
496
+ FOR NO KEY UPDATE;
497
+ IF NOT FOUND OR session_row.active_turn_id IS DISTINCT FROM p_turn_id
498
+ OR session_row.status = 'cancelled'
499
+ THEN reason := 'session_identity_changed'; END IF;
500
+ -- Attribution evidence (0280): captured from the row this same
501
+ -- transaction locked, NULL when the fence never loaded it.
502
+ audit_authority_epoch := session_row.authority_epoch;
503
+ audit_authority_visibility := session_row.visibility;
504
+ audit_owner_membership := session_row.owner_organization_membership_id;
505
+ END IF;
506
+ IF reason IS NULL THEN
507
+ SELECT turn_value.* INTO turn_row FROM session_turns turn_value
508
+ WHERE turn_value.id = p_turn_id AND turn_value.account_id = p_account_id
509
+ AND turn_value.workspace_id = p_workspace_id AND turn_value.session_id = p_session_id
510
+ AND turn_value.active_attempt_id = p_attempt_id
511
+ AND turn_value.execution_generation = p_execution_generation
512
+ AND turn_value.status = 'running'
513
+ FOR UPDATE;
514
+ IF NOT FOUND THEN reason := 'session_identity_changed'; END IF;
515
+ -- Causal-initiator evidence (0280): the frozen turn identity, recorded
516
+ -- verbatim (a pre-0096 sentinel stays the sentinel), NULL when the turn
517
+ -- fence never matched a row.
518
+ audit_initiator_kind := turn_row.initiator_kind;
519
+ audit_initiator_subject := turn_row.initiator_subject_id;
520
+ audit_initiating_human := turn_row.initiating_human_subject_id;
521
+ END IF;
522
+ IF reason IS NULL THEN
523
+ PERFORM 1 FROM session_turn_attempts attempt
524
+ WHERE attempt.id = p_attempt_id AND attempt.account_id = p_account_id
525
+ AND attempt.workspace_id = p_workspace_id AND attempt.session_id = p_session_id
526
+ AND attempt.turn_id = p_turn_id
527
+ AND attempt.execution_generation = p_execution_generation
528
+ AND attempt.state IN ('claimed', 'running')
529
+ AND attempt.closed_at IS NULL AND attempt.quiesced_at IS NULL
530
+ AND attempt.authority_visibility = session_row.visibility
531
+ AND attempt.authority_epoch = session_row.authority_epoch
532
+ AND attempt.authority_owner_organization_membership_id
533
+ IS NOT DISTINCT FROM session_row.owner_organization_membership_id
534
+ FOR UPDATE;
535
+ IF NOT FOUND OR EXISTS (
536
+ SELECT 1 FROM session_attempt_interruptions interruption
537
+ WHERE interruption.account_id = p_account_id
538
+ AND interruption.workspace_id = p_workspace_id
539
+ AND interruption.session_id = p_session_id
540
+ AND interruption.attempt_id = p_attempt_id
541
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
542
+ ) THEN reason := 'session_identity_changed'; END IF;
543
+ END IF;
544
+
545
+ PERFORM pg_advisory_xact_lock(hashtextextended(p_physical_request_id::text, 0));
546
+ SELECT EXISTS (SELECT 1 FROM connection_use_audit_facts audit
547
+ WHERE audit.physical_request_id = p_physical_request_id) INTO has_prior;
548
+ -- Only dereference `prior` inside the guarded branch: PL/pgSQL evaluates
549
+ -- both operands of `has_prior AND prior.<field>`, and an unassigned record
550
+ -- raises instead of yielding NULL.
551
+ IF has_prior THEN
552
+ SELECT audit.* INTO STRICT prior FROM connection_use_audit_facts audit
553
+ WHERE audit.physical_request_id = p_physical_request_id;
554
+ IF prior.request_digest IS DISTINCT FROM request_digest THEN
555
+ RAISE EXCEPTION 'physical connection request id was reused for different work'
556
+ USING ERRCODE = '23505';
557
+ END IF;
558
+ IF reason IS NULL AND prior.outcome = 'denied' THEN
559
+ authorization_status := prior.outcome; denial_reason := prior.denial_reason;
560
+ RETURN NEXT; PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN;
561
+ END IF;
562
+ END IF;
563
+
564
+ IF reason IS NULL THEN
565
+ SELECT authority_snapshot.* INTO snapshot
566
+ FROM turn_connection_authority_snapshots authority_snapshot
567
+ WHERE authority_snapshot.turn_id = p_turn_id
568
+ AND authority_snapshot.server_id = p_server_id
569
+ AND authority_snapshot.account_id = p_account_id
570
+ AND authority_snapshot.workspace_id = p_workspace_id
571
+ AND authority_snapshot.session_id = p_session_id;
572
+ IF FOUND THEN
573
+ scheduled_run_id := CASE
574
+ WHEN snapshot.canonical_snapshot -> 'acceptedWork' ->> 'kind' = 'scheduled_task'
575
+ THEN nullif(snapshot.canonical_snapshot -> 'acceptedWork' ->> 'runId', '')::uuid
576
+ ELSE NULL END;
577
+ SELECT connection_value.* INTO connection_row
578
+ FROM opengeni_private.read_sender_connection(
579
+ p_account_id, snapshot.origin_workspace_id, snapshot.connection_id, snapshot.owner_subject_id
580
+ ) connection_value;
581
+ IF snapshot.snapshot_digest IS DISTINCT FROM digest(
582
+ convert_to(snapshot.canonical_snapshot::text, 'UTF8'), 'sha256'
583
+ ) THEN reason := 'accepted_snapshot_digest_changed';
584
+ ELSIF NOT FOUND THEN reason := 'connection_missing';
585
+ ELSIF snapshot.authority_source IS DISTINCT FROM 'sender'
586
+ OR snapshot.authority_scope IS DISTINCT FROM 'user'
587
+ OR p_subject_scope IS DISTINCT FROM 'subject'
588
+ OR p_connection_id IS DISTINCT FROM snapshot.connection_id
589
+ OR lower(p_provider_domain) IS DISTINCT FROM snapshot.provider_domain
590
+ OR (p_connection_kind IS NOT NULL
591
+ AND p_connection_kind IS DISTINCT FROM snapshot.connection_kind)
592
+ THEN reason := 'connection_identity_changed';
593
+ ELSIF connection_row.status <> 'active' THEN reason := 'connection_status_inactive';
594
+ ELSIF connection_row.authority_scope IS DISTINCT FROM 'user'
595
+ OR connection_row.subject_id IS DISTINCT FROM snapshot.owner_subject_id
596
+ OR connection_row.owner_organization_membership_id
597
+ IS DISTINCT FROM snapshot.owner_organization_membership_id
598
+ OR connection_row.origin_workspace_id IS DISTINCT FROM snapshot.origin_workspace_id
599
+ THEN reason := 'connection_owner_changed';
600
+ ELSIF connection_row.authority_generation IS DISTINCT FROM snapshot.connection_generation
601
+ THEN reason := 'connection_generation_changed';
602
+ ELSIF session_row.visibility IS DISTINCT FROM snapshot.session_visibility
603
+ OR session_row.authority_epoch IS DISTINCT FROM snapshot.session_authority_epoch
604
+ THEN reason := 'session_authority_epoch_changed';
605
+ ELSIF NOT EXISTS (
606
+ SELECT 1 FROM organization_memberships membership
607
+ WHERE membership.id = snapshot.owner_organization_membership_id
608
+ AND membership.account_id = p_account_id
609
+ AND membership.subject_id = snapshot.owner_subject_id
610
+ AND membership.status = 'active' AND membership.revoked_at IS NULL
611
+ AND membership.authorization_revision = snapshot.membership_authorization_revision
612
+ AND (membership.personal_workspace_id = p_workspace_id OR EXISTS (
613
+ SELECT 1 FROM workspace_memberships workspace_membership
614
+ WHERE workspace_membership.account_id = p_account_id
615
+ AND workspace_membership.workspace_id = p_workspace_id
616
+ AND workspace_membership.subject_id = snapshot.owner_subject_id
617
+ ))
618
+ ) THEN reason := 'owner_membership_inactive';
619
+ ELSIF NOT EXISTS (
620
+ SELECT 1 FROM organization_user_resource_authorities authority
621
+ WHERE authority.id = snapshot.authority_id AND authority.account_id = p_account_id
622
+ AND authority.organization_membership_id = snapshot.owner_organization_membership_id
623
+ AND authority.resource_kind = 'connection'
624
+ AND authority.resource_id = snapshot.connection_id
625
+ AND authority.origin_workspace_id = snapshot.origin_workspace_id
626
+ AND authority.generation = snapshot.authority_generation
627
+ AND authority.status = 'active' AND authority.revoked_at IS NULL
628
+ ) THEN reason := 'authority_status_inactive';
629
+ END IF;
630
+ resolved_connection_id := snapshot.connection_id;
631
+ connection_generation := snapshot.connection_generation;
632
+ origin_workspace_id := snapshot.origin_workspace_id;
633
+ resolved_connection_kind := snapshot.connection_kind;
634
+ authority_scope := snapshot.authority_scope;
635
+ owner_subject_id := snapshot.owner_subject_id;
636
+ authority_id := snapshot.authority_id;
637
+ grant_id := snapshot.grant_id;
638
+ ELSIF p_subject_scope = 'workspace' THEN
639
+ -- Workspace lane (0279): a workspace-owned connection is ambient shared
640
+ -- workspace capability, never frozen on the turn, so it is validated
641
+ -- against the live row inside the same canonical lifecycle fences and
642
+ -- recorded in the same idempotent audit facts. A frozen per-turn
643
+ -- personal snapshot for this server takes the snapshot lane above and
644
+ -- therefore denies a workspace-scope request outright (fail closed).
645
+ SELECT connection_value.* INTO connection_row
646
+ FROM opengeni_private.read_sender_connection(p_account_id, p_workspace_id, p_connection_id, NULL) connection_value;
647
+ IF p_connection_id IS NULL OR NOT FOUND THEN reason := 'connection_missing';
648
+ ELSIF connection_row.authority_scope IS DISTINCT FROM 'workspace'
649
+ OR p_owner_subject_id IS NOT NULL
650
+ OR connection_row.workspace_id IS DISTINCT FROM p_workspace_id
651
+ OR connection_row.origin_workspace_id IS DISTINCT FROM p_workspace_id
652
+ OR lower(connection_row.provider_domain) IS DISTINCT FROM lower(p_provider_domain)
653
+ OR (p_connection_kind IS NOT NULL
654
+ AND connection_row.kind IS DISTINCT FROM p_connection_kind)
655
+ THEN reason := 'connection_identity_changed';
656
+ ELSIF connection_row.status <> 'active' THEN reason := 'connection_status_inactive';
657
+ ELSE
658
+ resolved_connection_id := connection_row.id;
659
+ connection_generation := connection_row.authority_generation;
660
+ origin_workspace_id := connection_row.origin_workspace_id;
661
+ resolved_connection_kind := connection_row.kind;
662
+ authority_scope := 'workspace';
663
+ END IF;
664
+ ELSE
665
+ reason := 'accepted_attempt_authority_required';
666
+ END IF;
667
+ END IF;
668
+
669
+ authorization_status := CASE WHEN reason IS NULL THEN 'authorized' ELSE 'denied' END;
670
+ denial_reason := reason;
671
+ IF NOT has_prior THEN
672
+ INSERT INTO connection_use_audit_facts (
673
+ physical_request_id, use_phase, request_digest, account_id, workspace_id,
674
+ session_id, turn_id, attempt_id, execution_generation, server_id,
675
+ connection_id, connection_generation, authority_scope, owner_subject_id,
676
+ authority_id, grant_id, outcome, denial_reason,
677
+ initiator_kind, initiator_subject_id, initiating_human_subject_id,
678
+ authority_epoch, authority_visibility,
679
+ authority_owner_organization_membership_id
680
+ ) VALUES (
681
+ p_physical_request_id, p_use_phase, request_digest, p_account_id,
682
+ p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
683
+ p_execution_generation, p_server_id, resolved_connection_id,
684
+ connection_generation, authority_scope, owner_subject_id, authority_id,
685
+ grant_id, authorization_status, denial_reason,
686
+ audit_initiator_kind, audit_initiator_subject, audit_initiating_human,
687
+ audit_authority_epoch, audit_authority_visibility, audit_owner_membership
688
+ ) ON CONFLICT (physical_request_id) DO NOTHING;
689
+ END IF;
690
+ RETURN NEXT;
691
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true);
692
+ EXCEPTION WHEN OTHERS THEN RAISE;
693
+ END
694
+ $body$;
695
+
696
+
697
+ -- Connection ownership does not depend on the private-conversation product.
698
+ CREATE FUNCTION list_owned_connection_accounts(p_account_id uuid, p_workspace_id uuid)
699
+ RETURNS TABLE(connection_id uuid, origin_workspace_id uuid)
700
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path FROM CURRENT
701
+ AS $body$
702
+ DECLARE
703
+ sender_prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
704
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
705
+ BEGIN
706
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
707
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
708
+ OR p_workspace_id IS DISTINCT FROM nullif(current_setting('opengeni.workspace_id', true), '')::uuid
709
+ OR caller_subject IS NULL
710
+ THEN RAISE EXCEPTION 'connection account scope mismatch' USING ERRCODE = '42501'; END IF;
711
+ RETURN QUERY
712
+ SELECT a.resource_id, a.origin_workspace_id
713
+ FROM organization_user_resource_authorities a
714
+ JOIN organization_memberships m ON m.id = a.organization_membership_id AND m.account_id = a.account_id
715
+ WHERE a.account_id = p_account_id AND a.resource_kind = 'connection'
716
+ AND m.subject_id = caller_subject AND m.status = 'active' AND m.revoked_at IS NULL
717
+ AND a.status = 'active' AND a.revoked_at IS NULL
718
+ AND EXISTS (SELECT 1 FROM workspaces w WHERE w.id = p_workspace_id AND w.account_id = p_account_id
719
+ AND (m.personal_workspace_id = w.id OR EXISTS (
720
+ SELECT 1 FROM workspace_memberships wm WHERE wm.account_id = p_account_id
721
+ AND wm.workspace_id = w.id AND wm.subject_id = caller_subject)))
722
+ ORDER BY a.resource_id;
723
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true);
724
+ EXCEPTION WHEN OTHERS THEN RAISE;
725
+ END
726
+ $body$;
727
+ REVOKE ALL ON FUNCTION list_owned_connection_accounts(uuid, uuid) FROM PUBLIC;
728
+
729
+ -- Each occurrence captures current sender-owned accounts once. Historical
730
+ -- grant columns remain readable audit evidence; new rows never populate them.
731
+ ALTER TABLE scheduled_task_run_connection_authority_snapshots
732
+ ALTER COLUMN grant_id DROP NOT NULL,
733
+ ALTER COLUMN grant_generation DROP NOT NULL,
734
+ ALTER COLUMN grant_mode DROP NOT NULL,
735
+ ALTER COLUMN grant_context DROP NOT NULL,
736
+ DROP CONSTRAINT scheduled_run_connection_authority_shape_chk,
737
+ ADD CONSTRAINT scheduled_run_connection_authority_shape_chk CHECK (
738
+ task_authority_revision > 0 AND execution_digest ~ '^[0-9a-f]{64}$'
739
+ AND octet_length(server_id) BETWEEN 1 AND 256 AND connection_generation > 0
740
+ AND (selected_kind IS NULL OR selected_kind IN ('oauth2','api_key','app_install','delegated'))
741
+ AND (connection_type IS NULL OR connection_type IN ('mcp','atlassian','github_personal'))
742
+ AND membership_authorization_revision > 0 AND authority_generation > 0
743
+ AND session_visibility IN ('user_private','workspace_shared')
744
+ AND cardinality(selection_sources) > 0
745
+ AND (
746
+ (canonical_snapshot ->> 'authoritySource' = 'sender'
747
+ AND grant_id IS NULL AND grant_generation IS NULL AND grant_mode IS NULL
748
+ AND grant_context IS NULL AND grant_session_id IS NULL AND grant_authority_epoch IS NULL)
749
+ OR (canonical_snapshot ->> 'authoritySource' IS DISTINCT FROM 'sender'
750
+ AND grant_id IS NOT NULL AND grant_generation > 0
751
+ AND grant_mode IN ('once','session','always') AND grant_context = session_visibility)
752
+ )
753
+ );
754
+
755
+ CREATE OR REPLACE FUNCTION admit_scheduled_task_run_connection_authorities()
756
+ RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER
757
+ SET search_path FROM CURRENT
758
+ AS $body$
759
+ DECLARE
760
+ sender_prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
761
+ prior_subject text := current_setting('opengeni.subject_id', true);
762
+ item jsonb;
763
+ connection_row connections%ROWTYPE;
764
+ membership_row organization_memberships%ROWTYPE;
765
+ authority_row organization_user_resource_authorities%ROWTYPE;
766
+ target_row sessions%ROWTYPE;
767
+ owner_subject text;
768
+ target_id uuid;
769
+ canonical jsonb;
770
+ BEGIN
771
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
772
+ IF NEW.action_kind <> 'agent_turn' THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NEW; END IF;
773
+ SELECT task.owner_subject_id INTO STRICT owner_subject FROM scheduled_tasks task
774
+ WHERE task.id = NEW.task_id AND task.account_id = NEW.account_id
775
+ AND task.workspace_id = NEW.workspace_id FOR SHARE;
776
+ IF NEW.accepted_execution_snapshot ->> 'causalHumanSubjectId' IS DISTINCT FROM owner_subject
777
+ THEN RAISE EXCEPTION 'scheduled account owner changed' USING ERRCODE = '42501'; END IF;
778
+ PERFORM set_config('opengeni.subject_id', coalesce(owner_subject, ''), true);
779
+ target_id := nullif(NEW.accepted_execution_snapshot -> 'targetSessionExecution' ->> 'sessionId','')::uuid;
780
+ IF target_id IS NOT NULL THEN
781
+ SELECT value.* INTO STRICT target_row FROM sessions value
782
+ WHERE value.id = target_id AND value.account_id = NEW.account_id
783
+ AND value.workspace_id = NEW.workspace_id FOR SHARE;
784
+ END IF;
785
+ FOR item IN SELECT value FROM jsonb_array_elements(
786
+ NEW.accepted_execution_snapshot -> 'personalConnectionDelegations'
787
+ ) ORDER BY value ->> 'connectionId', value ->> 'serverId'
788
+ LOOP
789
+ IF item ? 'userDelegation' OR owner_subject IS NULL
790
+ OR item ->> 'ownerSubjectId' IS DISTINCT FROM owner_subject
791
+ THEN RAISE EXCEPTION 'scheduled account selection differs from its owner' USING ERRCODE = '42501'; END IF;
792
+ -- Social credentials have their own existing owner-scoped store/resolver.
793
+ IF item ->> 'connectionType' = 'social' THEN CONTINUE; END IF;
794
+ SELECT value.* INTO connection_row FROM opengeni_private.read_sender_connection(
795
+ NEW.account_id, nullif(item ->> 'originWorkspaceId','')::uuid,
796
+ (item ->> 'connectionId')::uuid, owner_subject
797
+ ) value;
798
+ IF NOT FOUND OR connection_row.authority_scope <> 'user'
799
+ OR connection_row.subject_id IS DISTINCT FROM owner_subject
800
+ OR connection_row.status <> 'active'
801
+ OR lower(connection_row.provider_domain) IS DISTINCT FROM lower(item ->> 'providerDomain')
802
+ OR (item ? 'kind' AND item ->> 'kind' IS DISTINCT FROM connection_row.kind)
803
+ THEN RAISE EXCEPTION 'scheduled connection is unavailable' USING ERRCODE = '42501'; END IF;
804
+ SELECT value.* INTO STRICT membership_row FROM organization_memberships value
805
+ WHERE value.id = connection_row.owner_organization_membership_id
806
+ AND value.account_id = NEW.account_id AND value.subject_id = owner_subject
807
+ AND value.status = 'active' AND value.revoked_at IS NULL FOR SHARE;
808
+ IF membership_row.personal_workspace_id IS DISTINCT FROM NEW.workspace_id
809
+ AND NOT EXISTS (SELECT 1 FROM workspace_memberships value
810
+ WHERE value.account_id = NEW.account_id AND value.workspace_id = NEW.workspace_id
811
+ AND value.subject_id = owner_subject)
812
+ THEN RAISE EXCEPTION 'scheduled account owner lacks workspace access' USING ERRCODE = '42501'; END IF;
813
+ SELECT value.* INTO STRICT authority_row FROM organization_user_resource_authorities value
814
+ WHERE value.id = connection_row.authority_id AND value.account_id = NEW.account_id
815
+ AND value.organization_membership_id = membership_row.id
816
+ AND value.resource_kind = 'connection' AND value.resource_id = connection_row.id
817
+ AND value.origin_workspace_id = connection_row.origin_workspace_id
818
+ AND value.status = 'active' AND value.revoked_at IS NULL FOR SHARE;
819
+ canonical := jsonb_build_object(
820
+ 'authoritySource','sender', 'runId',NEW.id, 'taskId',NEW.task_id,
821
+ 'taskAuthorityRevision',NEW.task_authority_revision,'executionDigest',NEW.task_execution_digest,
822
+ 'organizationId',NEW.account_id,'workspaceId',NEW.workspace_id,
823
+ 'serverId',item ->> 'serverId','connectionId',connection_row.id,
824
+ 'connectionGeneration',connection_row.authority_generation,
825
+ 'originWorkspaceId',connection_row.origin_workspace_id,
826
+ 'providerDomain',lower(connection_row.provider_domain),'connectionKind',connection_row.kind,
827
+ 'ownerSubjectId',owner_subject,'ownerOrganizationMembershipId',membership_row.id,
828
+ 'membershipAuthorizationRevision',membership_row.authorization_revision,
829
+ 'authorityId',authority_row.id,'authorityGeneration',authority_row.generation,
830
+ 'targetSessionId',target_id,'sessionVisibility',coalesce(target_row.visibility,'workspace_shared'),
831
+ 'sessionAuthorityEpoch',target_row.authority_epoch,
832
+ 'selection',item
833
+ );
834
+ INSERT INTO scheduled_task_run_connection_authority_snapshots (
835
+ run_id,task_id,task_authority_revision,account_id,workspace_id,execution_digest,
836
+ server_id,connection_id,connection_generation,origin_workspace_id,provider_domain,
837
+ connection_kind,selected_kind,connection_type,owner_subject_id,
838
+ owner_organization_membership_id,membership_authorization_revision,authority_id,authority_generation,
839
+ target_session_id,session_visibility,session_authority_epoch,selection_sources,
840
+ canonical_snapshot,snapshot_digest
841
+ ) VALUES (
842
+ NEW.id,NEW.task_id,NEW.task_authority_revision,NEW.account_id,NEW.workspace_id,NEW.task_execution_digest,
843
+ item ->> 'serverId',connection_row.id,connection_row.authority_generation,connection_row.origin_workspace_id,
844
+ lower(connection_row.provider_domain),connection_row.kind,item ->> 'kind',item ->> 'connectionType',owner_subject,
845
+ membership_row.id,membership_row.authorization_revision,authority_row.id,authority_row.generation,
846
+ target_id,coalesce(target_row.visibility,'workspace_shared'),target_row.authority_epoch,
847
+ ARRAY['mcp:' || (item ->> 'serverId')],canonical,digest(convert_to(canonical::text,'UTF8'),'sha256')
848
+ );
849
+ END LOOP;
850
+ PERFORM set_config('opengeni.subject_id', coalesce(prior_subject, ''), true);
851
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NEW;
852
+ EXCEPTION WHEN OTHERS THEN RAISE;
853
+ END
854
+ $body$;
855
+
856
+ CREATE OR REPLACE FUNCTION admit_scheduled_agent_run_execution()
857
+ RETURNS trigger
858
+ LANGUAGE plpgsql
859
+ SECURITY DEFINER
860
+ SET search_path FROM CURRENT
861
+ AS $function$
862
+ DECLARE
863
+ previous_membership_marker text := current_setting('opengeni.organization_tenancy_lifecycle', true);
864
+ task_row record;
865
+ target_row record;
866
+ latest_started record;
867
+ task_snapshot jsonb;
868
+ target_snapshot jsonb;
869
+ expected_target_session_id uuid;
870
+ connection_subject text;
871
+ connection_subject_count integer;
872
+ personal_resource_subject text;
873
+ personal_resource_subject_count integer;
874
+ revision_authority_subject text;
875
+ revision_authority_membership_id uuid;
876
+ revision_authority_membership_revision bigint;
877
+ accepted_causal_subject text;
878
+ generated_variable_set record;
879
+ generated_rig record;
880
+ generated_rig_version record;
881
+ expected_rig_default_variable_sets jsonb;
882
+ generated_slack record;
883
+ workspace_settings jsonb;
884
+ deployment_depth record;
885
+ expected_depth integer;
886
+ expected_depth_source text;
887
+ expected_compaction_mode text;
888
+ BEGIN
889
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
890
+ SELECT task.* INTO STRICT task_row
891
+ FROM scheduled_tasks task
892
+ WHERE task.id = NEW.task_id
893
+ AND task.account_id = NEW.account_id
894
+ AND task.workspace_id = NEW.workspace_id
895
+ FOR UPDATE;
896
+ IF task_row.status <> 'active'
897
+ OR task_row.deleted_at IS NOT NULL
898
+ OR NEW.action_kind IS DISTINCT FROM task_row.action ->> 'kind'
899
+ THEN RAISE EXCEPTION 'scheduled run action or task lifecycle changed during admission'
900
+ USING ERRCODE = '42501'; END IF;
901
+ IF NEW.action_kind <> 'agent_turn' THEN
902
+ IF NEW.accepted_execution_snapshot IS NOT NULL
903
+ OR NEW.accepted_execution_digest IS NOT NULL
904
+ THEN RAISE EXCEPTION 'non-agent scheduled run cannot carry agent execution truth'
905
+ USING ERRCODE = '42501'; END IF;
906
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_membership_marker, ''), true); RETURN NEW;
907
+ END IF;
908
+ task_snapshot := NEW.accepted_execution_snapshot -> 'task';
909
+ IF task_row.action ->> 'kind' IS DISTINCT FROM 'agent_turn'
910
+ OR NEW.task_authority_revision IS DISTINCT FROM task_row.authority_revision
911
+ OR NEW.task_execution_digest IS DISTINCT FROM task_row.execution_digest
912
+ OR NEW.accepted_execution_snapshot ->> 'version' IS DISTINCT FROM '1'
913
+ OR NEW.accepted_execution_snapshot -> 'task' ->> 'id' IS DISTINCT FROM NEW.task_id::text
914
+ OR NEW.accepted_execution_snapshot -> 'task' ->> 'accountId'
915
+ IS DISTINCT FROM NEW.account_id::text
916
+ OR NEW.accepted_execution_snapshot -> 'task' ->> 'workspaceId'
917
+ IS DISTINCT FROM NEW.workspace_id::text
918
+ OR (NEW.accepted_execution_snapshot -> 'task' ->> 'authorityRevision')::bigint
919
+ IS DISTINCT FROM NEW.task_authority_revision
920
+ OR NEW.accepted_execution_snapshot -> 'task' ->> 'executionDigest'
921
+ IS DISTINCT FROM NEW.task_execution_digest
922
+ OR NEW.accepted_execution_snapshot -> 'task' ->> 'status' IS DISTINCT FROM 'active'
923
+ OR NEW.accepted_execution_snapshot -> 'task' -> 'action' ->> 'kind'
924
+ IS DISTINCT FROM 'agent_turn'
925
+ OR task_snapshot -> 'schedule' IS DISTINCT FROM task_row.schedule
926
+ OR task_snapshot ->> 'temporalScheduleId' IS DISTINCT FROM task_row.temporal_schedule_id
927
+ OR task_snapshot ->> 'runMode' IS DISTINCT FROM task_row.run_mode
928
+ OR task_snapshot ->> 'overlapPolicy' IS DISTINCT FROM task_row.overlap_policy
929
+ OR task_snapshot -> 'action' IS DISTINCT FROM task_row.action
930
+ OR task_snapshot -> 'agentConfig' IS DISTINCT FROM task_row.agent_config
931
+ OR task_snapshot ->> 'createdBy' IS NULL
932
+ OR task_snapshot -> 'createdBy' ->> 'kind' IS DISTINCT FROM task_row.created_by_kind
933
+ OR task_snapshot -> 'createdBy' ->> 'subjectId'
934
+ IS DISTINCT FROM task_row.created_by_subject_id
935
+ OR task_snapshot -> 'createdByContext' IS DISTINCT FROM task_row.created_by_context
936
+ OR task_snapshot -> 'metadata' IS DISTINCT FROM task_row.metadata
937
+ OR nullif(task_snapshot ->> 'variableSetId', '')::uuid
938
+ IS DISTINCT FROM task_row.variable_set_id
939
+ OR nullif(task_snapshot ->> 'environmentId', '')::uuid
940
+ IS DISTINCT FROM task_row.variable_set_id
941
+ OR nullif(task_snapshot ->> 'rigId', '')::uuid IS DISTINCT FROM task_row.rig_id
942
+ OR (
943
+ task_row.run_mode = 'existing_session'
944
+ AND (
945
+ nullif(task_snapshot ->> 'targetSessionId', '')::uuid
946
+ IS DISTINCT FROM task_row.reusable_session_id
947
+ OR nullif(task_snapshot ->> 'reusableSessionId', '')::uuid IS NOT NULL
948
+ )
949
+ )
950
+ OR (
951
+ task_row.run_mode <> 'existing_session'
952
+ AND (
953
+ nullif(task_snapshot ->> 'reusableSessionId', '')::uuid
954
+ IS DISTINCT FROM task_row.reusable_session_id
955
+ OR nullif(task_snapshot ->> 'targetSessionId', '')::uuid IS NOT NULL
956
+ )
957
+ )
958
+ OR NEW.accepted_execution_snapshot -> 'xaiProviderAccountAuthoritySnapshot'
959
+ IS DISTINCT FROM task_row.xai_provider_account_authority_snapshot
960
+ THEN
961
+ RAISE EXCEPTION 'scheduled agent run accepted execution changed during admission'
962
+ USING ERRCODE = '40001';
963
+ END IF;
964
+
965
+ SELECT min(selected ->> 'ownerSubjectId'),
966
+ count(DISTINCT selected ->> 'ownerSubjectId')::integer
967
+ INTO connection_subject, connection_subject_count
968
+ FROM jsonb_array_elements(NEW.accepted_execution_snapshot -> 'personalConnectionDelegations') selected
969
+ WHERE nullif(btrim(selected ->> 'ownerSubjectId'), '') IS NOT NULL;
970
+ SELECT min(authority.initiating_human_subject_id),
971
+ count(DISTINCT authority.initiating_human_subject_id)::integer
972
+ INTO personal_resource_subject, personal_resource_subject_count
973
+ FROM scheduled_task_personal_resource_authorities authority
974
+ WHERE authority.task_id = NEW.task_id
975
+ AND authority.task_authority_revision = NEW.task_authority_revision
976
+ AND authority.account_id = NEW.account_id
977
+ AND authority.workspace_id = NEW.workspace_id;
978
+ SELECT authority.subject_id, authority.organization_membership_id,
979
+ authority.membership_authorization_revision
980
+ INTO revision_authority_subject, revision_authority_membership_id,
981
+ revision_authority_membership_revision
982
+ FROM scheduled_task_revision_authorities authority
983
+ WHERE authority.task_id = NEW.task_id
984
+ AND authority.task_authority_revision = NEW.task_authority_revision
985
+ AND authority.account_id = NEW.account_id
986
+ AND authority.workspace_id = NEW.workspace_id;
987
+ IF connection_subject_count > 1
988
+ OR personal_resource_subject_count > 1
989
+ OR NEW.accepted_execution_snapshot ->> 'connectionAuthoritySubjectId'
990
+ IS DISTINCT FROM connection_subject
991
+ OR NEW.accepted_execution_snapshot ->> 'personalResourceAuthoritySubjectId'
992
+ IS DISTINCT FROM personal_resource_subject
993
+ OR (
994
+ revision_authority_subject IS NOT NULL
995
+ AND personal_resource_subject IS NOT NULL
996
+ AND revision_authority_subject IS DISTINCT FROM personal_resource_subject
997
+ )
998
+ OR (
999
+ revision_authority_subject IS NOT NULL
1000
+ AND connection_subject IS NOT NULL
1001
+ AND revision_authority_subject IS DISTINCT FROM connection_subject
1002
+ )
1003
+ OR (
1004
+ connection_subject IS NOT NULL
1005
+ AND personal_resource_subject IS NOT NULL
1006
+ AND connection_subject IS DISTINCT FROM personal_resource_subject
1007
+ )
1008
+ OR (
1009
+ task_row.xai_provider_account_authority_snapshot ->> 'scope' = 'user'
1010
+ AND (
1011
+ task_row.created_by_kind <> 'subject'
1012
+ OR NEW.accepted_execution_snapshot ->> 'xaiAuthoritySubjectId'
1013
+ IS DISTINCT FROM task_row.created_by_subject_id
1014
+ OR (
1015
+ connection_subject IS NOT NULL
1016
+ AND connection_subject IS DISTINCT FROM task_row.created_by_subject_id
1017
+ )
1018
+ OR (
1019
+ personal_resource_subject IS NOT NULL
1020
+ AND personal_resource_subject IS DISTINCT FROM task_row.created_by_subject_id
1021
+ )
1022
+ OR (
1023
+ revision_authority_subject IS NOT NULL
1024
+ AND revision_authority_subject IS DISTINCT FROM task_row.created_by_subject_id
1025
+ )
1026
+ )
1027
+ )
1028
+ OR (
1029
+ task_row.xai_provider_account_authority_snapshot ->> 'scope' <> 'user'
1030
+ AND NEW.accepted_execution_snapshot ->> 'xaiAuthoritySubjectId' IS NOT NULL
1031
+ )
1032
+ THEN
1033
+ RAISE EXCEPTION 'scheduled accepted causal subject differs from frozen authority'
1034
+ USING ERRCODE = '42501';
1035
+ END IF;
1036
+ accepted_causal_subject := coalesce(
1037
+ revision_authority_subject,
1038
+ personal_resource_subject,
1039
+ connection_subject,
1040
+ CASE WHEN task_row.xai_provider_account_authority_snapshot ->> 'scope' = 'user'
1041
+ THEN task_row.created_by_subject_id END
1042
+ );
1043
+ IF NEW.accepted_execution_snapshot ->> 'causalHumanSubjectId'
1044
+ IS DISTINCT FROM accepted_causal_subject
1045
+ OR NEW.accepted_execution_snapshot -> 'causalHumanAuthority'
1046
+ IS DISTINCT FROM (CASE WHEN revision_authority_subject IS NULL THEN 'null'::jsonb
1047
+ ELSE jsonb_build_object(
1048
+ 'subjectId', revision_authority_subject,
1049
+ 'organizationMembershipId', revision_authority_membership_id,
1050
+ 'membershipAuthorizationRevision',
1051
+ revision_authority_membership_revision
1052
+ ) END)
1053
+ OR (
1054
+ NEW.status = 'queued'
1055
+ AND
1056
+ revision_authority_subject IS NOT NULL
1057
+ AND NOT EXISTS (
1058
+ SELECT 1 FROM organization_memberships membership
1059
+ WHERE membership.id = revision_authority_membership_id
1060
+ AND membership.account_id = NEW.account_id
1061
+ AND membership.subject_id = revision_authority_subject
1062
+ AND membership.status = 'active'
1063
+ AND membership.revoked_at IS NULL
1064
+ AND membership.authorization_revision =
1065
+ revision_authority_membership_revision
1066
+ AND (
1067
+ membership.personal_workspace_id = NEW.workspace_id
1068
+ OR EXISTS (
1069
+ SELECT 1 FROM workspace_memberships workspace_membership
1070
+ WHERE workspace_membership.account_id = NEW.account_id
1071
+ AND workspace_membership.workspace_id = NEW.workspace_id
1072
+ AND workspace_membership.subject_id = revision_authority_subject
1073
+ )
1074
+ )
1075
+ )
1076
+ )
1077
+ OR (
1078
+ (
1079
+ EXISTS (
1080
+ SELECT 1 FROM workspace_variable_sets variable_set
1081
+ WHERE variable_set.account_id = NEW.account_id
1082
+ AND variable_set.authority_scope = 'user'
1083
+ AND variable_set.id IN (
1084
+ task_row.variable_set_id,
1085
+ nullif(
1086
+ NEW.accepted_execution_snapshot -> 'targetSessionExecution' ->> 'variableSetId', ''
1087
+ )::uuid
1088
+ )
1089
+ )
1090
+ OR EXISTS (
1091
+ SELECT 1 FROM rigs rig
1092
+ WHERE rig.account_id = NEW.account_id
1093
+ AND rig.authority_scope = 'user'
1094
+ AND rig.id IN (
1095
+ task_row.rig_id,
1096
+ nullif(
1097
+ NEW.accepted_execution_snapshot -> 'targetSessionExecution' ->> 'rigId', ''
1098
+ )::uuid
1099
+ )
1100
+ )
1101
+ )
1102
+ AND accepted_causal_subject IS NULL
1103
+ )
1104
+ THEN
1105
+ RAISE EXCEPTION 'scheduled accepted causal human is invalid'
1106
+ USING ERRCODE = '42501';
1107
+ END IF;
1108
+
1109
+ expected_target_session_id := CASE
1110
+ WHEN task_row.run_mode = 'existing_session' THEN task_row.reusable_session_id
1111
+ WHEN task_row.run_mode = 'reusable_session' THEN task_row.reusable_session_id
1112
+ ELSE NULL
1113
+ END;
1114
+ target_snapshot := NEW.accepted_execution_snapshot -> 'targetSessionExecution';
1115
+ -- A targeted occurrence whose exact session no longer exists is a
1116
+ -- deterministic terminal outcome for that occurrence, never a retry loop.
1117
+ IF task_row.run_mode = 'existing_session' AND expected_target_session_id IS NULL THEN
1118
+ NEW.status := 'failed';
1119
+ NEW.error := 'scheduled_target_session_unavailable';
1120
+ NEW.completed_at := clock_timestamp();
1121
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_membership_marker, ''), true); RETURN NEW;
1122
+ END IF;
1123
+ IF expected_target_session_id IS NULL THEN
1124
+ IF target_snapshot IS DISTINCT FROM 'null'::jsonb
1125
+ OR NEW.accepted_execution_snapshot -> 'generatedSessionBinding' = 'null'::jsonb
1126
+ OR NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1127
+ ->> 'nestedAgentDepthPolicySource' NOT IN ('session','workspace','deployment','default')
1128
+ OR (NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1129
+ ->> 'effectiveMaxNestedAgentDepth')::integer < 0
1130
+ OR (
1131
+ task_row.agent_config ->> 'maxNestedAgentDepth' IS NOT NULL
1132
+ AND (
1133
+ NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1134
+ ->> 'nestedAgentDepthPolicySource' IS DISTINCT FROM 'session'
1135
+ OR (NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1136
+ ->> 'effectiveMaxNestedAgentDepth')::integer
1137
+ IS DISTINCT FROM (task_row.agent_config ->> 'maxNestedAgentDepth')::integer
1138
+ )
1139
+ )
1140
+ OR (
1141
+ task_row.agent_config ->> 'maxNestedAgentDepth' IS NULL
1142
+ AND NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1143
+ ->> 'nestedAgentDepthPolicySource' = 'session'
1144
+ )
1145
+ OR NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1146
+ ->> 'codexCompactionMode' NOT IN ('portable','remote_v2')
1147
+ OR (
1148
+ NEW.accepted_execution_snapshot ->> 'resolvedModel' NOT LIKE 'codex/%'
1149
+ AND NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1150
+ ->> 'codexCompactionMode' <> 'portable'
1151
+ )
1152
+ THEN
1153
+ RAISE EXCEPTION 'generated scheduled run cannot carry target-session execution policy'
1154
+ USING ERRCODE = '42501';
1155
+ END IF;
1156
+
1157
+ IF task_row.variable_set_id IS NULL THEN
1158
+ IF NEW.accepted_execution_snapshot -> 'resolvedVariableSet' IS DISTINCT FROM 'null'::jsonb
1159
+ THEN
1160
+ RAISE EXCEPTION 'scheduled generated Variable Set changed during admission'
1161
+ USING ERRCODE = '40001';
1162
+ END IF;
1163
+ ELSE
1164
+ SELECT variable_set.id, variable_set.generation, variable_set.status
1165
+ INTO generated_variable_set
1166
+ FROM workspace_variable_sets variable_set
1167
+ WHERE variable_set.id = task_row.variable_set_id
1168
+ AND variable_set.account_id = NEW.account_id
1169
+ FOR SHARE;
1170
+ IF NOT FOUND
1171
+ OR generated_variable_set.status <> 'active'
1172
+ OR NEW.accepted_execution_snapshot -> 'resolvedVariableSet' ->> 'id'
1173
+ IS DISTINCT FROM generated_variable_set.id::text
1174
+ OR (NEW.accepted_execution_snapshot -> 'resolvedVariableSet' ->> 'generation')::bigint
1175
+ IS DISTINCT FROM generated_variable_set.generation
1176
+ THEN
1177
+ RAISE EXCEPTION 'scheduled generated Variable Set changed during admission'
1178
+ USING ERRCODE = '40001';
1179
+ END IF;
1180
+ END IF;
1181
+
1182
+ IF task_row.rig_id IS NULL THEN
1183
+ IF NEW.accepted_execution_snapshot -> 'resolvedRig' IS DISTINCT FROM 'null'::jsonb
1184
+ THEN
1185
+ RAISE EXCEPTION 'scheduled generated Rig changed during admission'
1186
+ USING ERRCODE = '40001';
1187
+ END IF;
1188
+ ELSE
1189
+ SELECT rig_value.id, rig_value.status INTO generated_rig
1190
+ FROM rigs rig_value
1191
+ WHERE rig_value.id = task_row.rig_id
1192
+ AND rig_value.account_id = NEW.account_id
1193
+ FOR SHARE;
1194
+ SELECT version_value.id, version_value.default_variable_set_ids
1195
+ INTO generated_rig_version
1196
+ FROM rig_versions version_value
1197
+ WHERE version_value.rig_id = task_row.rig_id
1198
+ AND version_value.account_id = NEW.account_id
1199
+ AND version_value.active
1200
+ FOR SHARE;
1201
+ PERFORM 1 FROM workspace_variable_sets variable_set
1202
+ WHERE variable_set.account_id = NEW.account_id
1203
+ AND variable_set.id IN (
1204
+ SELECT default_id::uuid
1205
+ FROM jsonb_array_elements_text(
1206
+ coalesce(generated_rig_version.default_variable_set_ids, '[]'::jsonb)
1207
+ ) default_id
1208
+ )
1209
+ ORDER BY variable_set.id
1210
+ FOR SHARE;
1211
+ SELECT coalesce(
1212
+ jsonb_agg(
1213
+ jsonb_build_object('id', variable_set.id, 'generation', variable_set.generation)
1214
+ ORDER BY selected.ordinality
1215
+ ),
1216
+ '[]'::jsonb
1217
+ ) INTO expected_rig_default_variable_sets
1218
+ FROM jsonb_array_elements_text(
1219
+ coalesce(generated_rig_version.default_variable_set_ids, '[]'::jsonb)
1220
+ ) WITH ORDINALITY selected(id, ordinality)
1221
+ JOIN workspace_variable_sets variable_set
1222
+ ON variable_set.id = selected.id::uuid
1223
+ AND variable_set.account_id = NEW.account_id
1224
+ AND variable_set.status = 'active';
1225
+ IF generated_rig.id IS NULL
1226
+ OR generated_rig.status <> 'active'
1227
+ OR generated_rig_version.id IS NULL
1228
+ OR NEW.accepted_execution_snapshot -> 'resolvedRig' ->> 'id'
1229
+ IS DISTINCT FROM generated_rig.id::text
1230
+ OR NEW.accepted_execution_snapshot -> 'resolvedRig' ->> 'versionId'
1231
+ IS DISTINCT FROM generated_rig_version.id::text
1232
+ OR NEW.accepted_execution_snapshot -> 'resolvedRig' -> 'defaultVariableSets'
1233
+ IS DISTINCT FROM expected_rig_default_variable_sets
1234
+ THEN
1235
+ RAISE EXCEPTION 'scheduled generated Rig changed during admission'
1236
+ USING ERRCODE = '40001';
1237
+ END IF;
1238
+ END IF;
1239
+
1240
+ IF nullif(task_row.agent_config ->> 'slackBotConnectionId', '') IS NULL THEN
1241
+ IF NEW.accepted_execution_snapshot -> 'resolvedSlackBotConnection'
1242
+ IS DISTINCT FROM 'null'::jsonb
1243
+ THEN
1244
+ RAISE EXCEPTION 'scheduled Slack bot authority changed during admission'
1245
+ USING ERRCODE = '40001';
1246
+ END IF;
1247
+ ELSE
1248
+ SELECT connection_value.id, connection_value.version,
1249
+ connection_value.verified_install_version, connection_value.metadata,
1250
+ connection_value.status, connection_value.subject_id,
1251
+ connection_value.provider_domain, connection_value.kind,
1252
+ connection_value.verified_install_at
1253
+ INTO generated_slack
1254
+ FROM connections connection_value
1255
+ WHERE connection_value.id = (task_row.agent_config ->> 'slackBotConnectionId')::uuid
1256
+ AND connection_value.account_id = NEW.account_id
1257
+ AND connection_value.workspace_id = NEW.workspace_id
1258
+ FOR SHARE;
1259
+ IF generated_slack.id IS NULL
1260
+ OR generated_slack.status <> 'active'
1261
+ OR generated_slack.subject_id IS NOT NULL
1262
+ OR generated_slack.provider_domain <> 'slack.com'
1263
+ OR generated_slack.kind <> 'app_install'
1264
+ OR generated_slack.verified_install_at IS NULL
1265
+ OR generated_slack.verified_install_version IS DISTINCT FROM generated_slack.version
1266
+ OR NEW.accepted_execution_snapshot -> 'resolvedSlackBotConnection' ->> 'id'
1267
+ IS DISTINCT FROM generated_slack.id::text
1268
+ OR (NEW.accepted_execution_snapshot -> 'resolvedSlackBotConnection' ->> 'version')::integer
1269
+ IS DISTINCT FROM generated_slack.version
1270
+ OR (NEW.accepted_execution_snapshot -> 'resolvedSlackBotConnection'
1271
+ ->> 'verifiedInstallVersion')::integer
1272
+ IS DISTINCT FROM generated_slack.verified_install_version
1273
+ OR NEW.accepted_execution_snapshot -> 'resolvedSlackBotConnection' -> 'metadata'
1274
+ IS DISTINCT FROM generated_slack.metadata
1275
+ THEN
1276
+ RAISE EXCEPTION 'scheduled Slack bot authority changed during admission'
1277
+ USING ERRCODE = '40001';
1278
+ END IF;
1279
+ END IF;
1280
+
1281
+ SELECT workspace_value.settings INTO STRICT workspace_settings
1282
+ FROM workspaces workspace_value
1283
+ WHERE workspace_value.id = NEW.workspace_id
1284
+ AND workspace_value.account_id = NEW.account_id
1285
+ FOR SHARE;
1286
+ SELECT configuration.max_nested_agent_depth, configuration.policy_source
1287
+ INTO STRICT deployment_depth
1288
+ FROM nested_agent_depth_configuration configuration
1289
+ WHERE configuration.singleton
1290
+ FOR SHARE;
1291
+ IF task_row.agent_config ->> 'maxNestedAgentDepth' IS NOT NULL THEN
1292
+ expected_depth := (task_row.agent_config ->> 'maxNestedAgentDepth')::integer;
1293
+ expected_depth_source := 'session';
1294
+ ELSIF jsonb_typeof(workspace_settings -> 'maxNestedAgentDepth') = 'number' THEN
1295
+ expected_depth := (workspace_settings ->> 'maxNestedAgentDepth')::integer;
1296
+ expected_depth_source := 'workspace';
1297
+ ELSE
1298
+ expected_depth := deployment_depth.max_nested_agent_depth;
1299
+ expected_depth_source := deployment_depth.policy_source;
1300
+ END IF;
1301
+ expected_compaction_mode := CASE
1302
+ WHEN NEW.accepted_execution_snapshot ->> 'resolvedModel' NOT LIKE 'codex/%'
1303
+ THEN 'portable'
1304
+ WHEN workspace_settings ->> 'codexCompactionDefault' = 'portable'
1305
+ THEN 'portable'
1306
+ ELSE 'remote_v2'
1307
+ END;
1308
+ IF (NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1309
+ ->> 'effectiveMaxNestedAgentDepth')::integer IS DISTINCT FROM expected_depth
1310
+ OR NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1311
+ ->> 'nestedAgentDepthPolicySource' IS DISTINCT FROM expected_depth_source
1312
+ OR NEW.accepted_execution_snapshot -> 'generatedSessionBinding'
1313
+ ->> 'codexCompactionMode' IS DISTINCT FROM expected_compaction_mode
1314
+ THEN
1315
+ RAISE EXCEPTION 'scheduled generated policy changed during admission'
1316
+ USING ERRCODE = '40001';
1317
+ END IF;
1318
+ ELSE
1319
+ IF NEW.accepted_execution_snapshot -> 'generatedSessionBinding' IS DISTINCT FROM 'null'::jsonb
1320
+ THEN RAISE EXCEPTION 'targeted scheduled run cannot carry generated-session binding'
1321
+ USING ERRCODE = '42501'; END IF;
1322
+ SELECT session_value.* INTO target_row
1323
+ FROM sessions session_value
1324
+ WHERE session_value.id = expected_target_session_id
1325
+ AND session_value.account_id = NEW.account_id
1326
+ AND session_value.workspace_id = NEW.workspace_id
1327
+ AND session_value.status <> 'cancelled'
1328
+ FOR SHARE;
1329
+ IF NOT FOUND THEN
1330
+ -- The exact target was cancelled (or is no longer visible): settle this
1331
+ -- occurrence terminally instead of failing the activity.
1332
+ NEW.status := 'skipped';
1333
+ NEW.error := 'session_cancelled';
1334
+ NEW.completed_at := clock_timestamp();
1335
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_membership_marker, ''), true); RETURN NEW;
1336
+ END IF;
1337
+ SELECT turn_value.* INTO latest_started
1338
+ FROM session_events event_value
1339
+ JOIN session_turns turn_value
1340
+ ON turn_value.workspace_id = event_value.workspace_id
1341
+ AND turn_value.session_id = event_value.session_id
1342
+ AND turn_value.id = event_value.turn_id
1343
+ WHERE event_value.workspace_id = NEW.workspace_id
1344
+ AND event_value.session_id = expected_target_session_id
1345
+ AND event_value.type = 'turn.started'
1346
+ ORDER BY event_value.sequence DESC
1347
+ LIMIT 1;
1348
+ IF target_snapshot ->> 'sessionId' IS DISTINCT FROM target_row.id::text
1349
+ OR target_snapshot ->> 'visibility' IS DISTINCT FROM target_row.visibility
1350
+ OR (target_snapshot ->> 'authorityEpoch')::integer
1351
+ IS DISTINCT FROM target_row.authority_epoch
1352
+ OR target_snapshot ->> 'model'
1353
+ IS DISTINCT FROM coalesce(latest_started.model, target_row.model)
1354
+ OR target_snapshot ->> 'reasoningEffort' IS DISTINCT FROM coalesce(
1355
+ latest_started.reasoning_effort,
1356
+ CASE WHEN target_row.metadata ->> 'reasoningEffort' IN (
1357
+ 'none','minimal','low','medium','high','xhigh','max'
1358
+ )
1359
+ THEN target_row.metadata ->> 'reasoningEffort' ELSE 'medium' END
1360
+ )
1361
+ OR target_snapshot ->> 'latencyMode' IS DISTINCT FROM coalesce(
1362
+ latest_started.latency_mode,
1363
+ CASE WHEN target_row.metadata ->> 'latencyMode' IN ('standard','priority','fast')
1364
+ THEN target_row.metadata ->> 'latencyMode' ELSE 'standard' END
1365
+ )
1366
+ OR target_snapshot -> 'tools'
1367
+ IS DISTINCT FROM (CASE WHEN latest_started.tools_provided THEN latest_started.tools ELSE target_row.tools END)
1368
+ OR target_snapshot ->> 'sandboxBackend'
1369
+ IS DISTINCT FROM coalesce(latest_started.sandbox_backend, target_row.sandbox_backend)
1370
+ OR target_snapshot ->> 'sandboxOs'
1371
+ IS DISTINCT FROM coalesce(latest_started.sandbox_os, target_row.sandbox_os)
1372
+ OR target_snapshot -> 'firstPartyMcpTools'
1373
+ IS DISTINCT FROM target_row.first_party_mcp_tools
1374
+ OR target_snapshot -> 'firstPartyMcpPermissions'
1375
+ IS DISTINCT FROM coalesce(to_jsonb(target_row.first_party_mcp_permissions), 'null'::jsonb)
1376
+ OR target_snapshot -> 'toolPolicy' IS DISTINCT FROM target_row.tool_policy
1377
+ OR target_snapshot -> 'mcpServerIds' IS DISTINCT FROM (
1378
+ SELECT coalesce(jsonb_agg(server_value.server_id ORDER BY server_value.server_id), '[]'::jsonb)
1379
+ FROM session_mcp_servers server_value
1380
+ WHERE server_value.workspace_id = NEW.workspace_id
1381
+ AND server_value.session_id = target_row.id
1382
+ )
1383
+ OR (target_snapshot ->> 'toolPolicyVersion')::integer
1384
+ IS DISTINCT FROM target_row.tool_policy_version
1385
+ OR nullif(target_snapshot ->> 'variableSetId', '')::uuid
1386
+ IS DISTINCT FROM target_row.variable_set_id
1387
+ OR nullif(target_snapshot ->> 'variableSetGeneration', '')::bigint
1388
+ IS DISTINCT FROM (
1389
+ SELECT variable_set.generation
1390
+ FROM workspace_variable_sets variable_set
1391
+ WHERE variable_set.id = target_row.variable_set_id
1392
+ AND variable_set.account_id = NEW.account_id
1393
+ )
1394
+ OR nullif(target_snapshot ->> 'rigId', '')::uuid IS DISTINCT FROM target_row.rig_id
1395
+ OR nullif(target_snapshot ->> 'rigVersionId', '')::uuid
1396
+ IS DISTINCT FROM target_row.rig_version_id
1397
+ OR target_snapshot -> 'rigDefaultVariableSets' IS DISTINCT FROM (
1398
+ SELECT coalesce(
1399
+ jsonb_agg(
1400
+ jsonb_build_object('id', variable_set.id, 'generation', variable_set.generation)
1401
+ ORDER BY selected.ordinality
1402
+ ),
1403
+ '[]'::jsonb
1404
+ )
1405
+ FROM rig_versions version_value
1406
+ CROSS JOIN LATERAL jsonb_array_elements_text(
1407
+ coalesce(version_value.default_variable_set_ids, '[]'::jsonb)
1408
+ ) WITH ORDINALITY selected(id, ordinality)
1409
+ JOIN workspace_variable_sets variable_set
1410
+ ON variable_set.id = selected.id::uuid
1411
+ AND variable_set.account_id = NEW.account_id
1412
+ AND variable_set.status = 'active'
1413
+ WHERE version_value.id = target_row.rig_version_id
1414
+ AND version_value.rig_id = target_row.rig_id
1415
+ AND version_value.account_id = NEW.account_id
1416
+ )
1417
+ OR nullif(target_snapshot ->> 'maxNestedAgentDepthOverride', '')::integer
1418
+ IS DISTINCT FROM target_row.max_nested_agent_depth_override
1419
+ OR (target_snapshot ->> 'effectiveMaxNestedAgentDepth')::integer
1420
+ IS DISTINCT FROM target_row.effective_max_nested_agent_depth
1421
+ THEN
1422
+ RAISE EXCEPTION 'scheduled target-session execution policy changed during admission'
1423
+ USING ERRCODE = '40001';
1424
+ END IF;
1425
+ END IF;
1426
+ IF (
1427
+ SELECT count(DISTINCT subject_id)
1428
+ FROM (
1429
+ SELECT authority.initiating_human_subject_id AS subject_id
1430
+ FROM scheduled_task_personal_resource_authorities authority
1431
+ WHERE authority.task_id = NEW.task_id
1432
+ AND authority.task_authority_revision = NEW.task_authority_revision
1433
+ AND authority.account_id = NEW.account_id
1434
+ AND authority.workspace_id = NEW.workspace_id
1435
+ UNION ALL
1436
+ SELECT selected ->> 'ownerSubjectId'
1437
+ FROM jsonb_array_elements(NEW.accepted_execution_snapshot -> 'personalConnectionDelegations') selected
1438
+ ) causal_subjects
1439
+ ) > 1 THEN
1440
+ RAISE EXCEPTION 'scheduled authority classes require one causal human'
1441
+ USING ERRCODE = '42501';
1442
+ END IF;
1443
+ IF EXISTS (
1444
+ SELECT 1
1445
+ FROM scheduled_task_personal_resource_snapshots snapshot
1446
+ JOIN organization_user_resource_grants grant_value
1447
+ ON grant_value.id = snapshot.grant_id
1448
+ AND grant_value.account_id = snapshot.account_id
1449
+ WHERE snapshot.task_id = NEW.task_id
1450
+ AND snapshot.task_authority_revision = NEW.task_authority_revision
1451
+ AND snapshot.grant_mode = 'once'
1452
+ AND grant_value.status = 'consumed'
1453
+ ) THEN
1454
+ NEW.status := 'failed';
1455
+ NEW.error := 'scheduled_authority_exhausted';
1456
+ NEW.completed_at := clock_timestamp();
1457
+ END IF;
1458
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(previous_membership_marker, ''), true); RETURN NEW;
1459
+ END
1460
+ $function$;
1461
+
1462
+ CREATE OR REPLACE FUNCTION bind_scheduled_task_run_connection_authorities(p_account_id uuid, p_workspace_id uuid, p_run_id uuid, p_session_id uuid)
1463
+ RETURNS integer
1464
+ LANGUAGE plpgsql
1465
+ SECURITY DEFINER
1466
+ SET search_path FROM CURRENT
1467
+ AS $function$
1468
+ DECLARE
1469
+ run_row record;
1470
+ session_row record;
1471
+ snapshot_row record;
1472
+ canonical jsonb;
1473
+ bound_count integer := 0;
1474
+ BEGIN
1475
+ IF p_account_id IS DISTINCT FROM nullif(
1476
+ current_setting('opengeni.account_id', true), ''
1477
+ )::uuid OR p_workspace_id IS DISTINCT FROM nullif(
1478
+ current_setting('opengeni.workspace_id', true), ''
1479
+ )::uuid THEN
1480
+ RAISE EXCEPTION 'scheduled connection run bind scope mismatch'
1481
+ USING ERRCODE = '42501';
1482
+ END IF;
1483
+ SELECT run.* INTO STRICT run_row FROM scheduled_task_runs run
1484
+ WHERE run.id = p_run_id AND run.account_id = p_account_id
1485
+ AND run.workspace_id = p_workspace_id
1486
+ FOR UPDATE;
1487
+ IF run_row.status NOT IN ('queued', 'dispatched') THEN
1488
+ RAISE EXCEPTION 'scheduled connection run is not turn-admissible'
1489
+ USING ERRCODE = '42501';
1490
+ END IF;
1491
+ SELECT session_value.* INTO STRICT session_row FROM sessions session_value
1492
+ WHERE session_value.id = p_session_id AND session_value.account_id = p_account_id
1493
+ AND session_value.workspace_id = p_workspace_id AND session_value.status <> 'cancelled'
1494
+ FOR SHARE;
1495
+ IF run_row.session_id IS DISTINCT FROM p_session_id THEN
1496
+ RAISE EXCEPTION 'scheduled connection run session binding changed'
1497
+ USING ERRCODE = '42501';
1498
+ END IF;
1499
+
1500
+ FOR snapshot_row IN SELECT snapshot.*
1501
+ FROM scheduled_task_run_connection_authority_snapshots snapshot
1502
+ WHERE snapshot.run_id = p_run_id
1503
+ ORDER BY snapshot.server_id
1504
+ FOR UPDATE
1505
+ LOOP
1506
+ IF snapshot_row.task_id IS DISTINCT FROM run_row.task_id
1507
+ OR snapshot_row.task_authority_revision IS DISTINCT FROM run_row.task_authority_revision
1508
+ OR snapshot_row.execution_digest IS DISTINCT FROM run_row.task_execution_digest
1509
+ OR snapshot_row.snapshot_digest IS DISTINCT FROM digest(
1510
+ convert_to(snapshot_row.canonical_snapshot::text, 'UTF8'), 'sha256'
1511
+ ) THEN RAISE EXCEPTION 'scheduled connection run snapshot digest changed'
1512
+ USING ERRCODE = '42501';
1513
+ END IF;
1514
+ IF snapshot_row.target_session_id IS NULL THEN
1515
+ IF snapshot_row.canonical_snapshot ->> 'authoritySource' IS DISTINCT FROM 'sender'
1516
+ OR (session_row.visibility = 'user_private'
1517
+ AND session_row.owner_subject_id IS DISTINCT FROM snapshot_row.owner_subject_id)
1518
+ THEN RAISE EXCEPTION 'scheduled connection run owner does not match its target'
1519
+ USING ERRCODE = '42501'; END IF;
1520
+ canonical := snapshot_row.canonical_snapshot || jsonb_build_object(
1521
+ 'targetSessionId', p_session_id,
1522
+ 'sessionVisibility', session_row.visibility,
1523
+ 'sessionAuthorityEpoch', session_row.authority_epoch
1524
+ );
1525
+ UPDATE scheduled_task_run_connection_authority_snapshots snapshot
1526
+ SET target_session_id = p_session_id,
1527
+ session_visibility = session_row.visibility,
1528
+ session_authority_epoch = session_row.authority_epoch,
1529
+ canonical_snapshot = canonical,
1530
+ snapshot_digest = digest(convert_to(canonical::text, 'UTF8'), 'sha256'),
1531
+ bound_at = clock_timestamp()
1532
+ WHERE snapshot.run_id = p_run_id AND snapshot.server_id = snapshot_row.server_id;
1533
+ ELSIF snapshot_row.target_session_id IS DISTINCT FROM p_session_id
1534
+ OR snapshot_row.session_visibility IS DISTINCT FROM session_row.visibility
1535
+ OR snapshot_row.session_authority_epoch IS DISTINCT FROM session_row.authority_epoch
1536
+ THEN RAISE EXCEPTION 'scheduled connection run is bound to another session authority'
1537
+ USING ERRCODE = '42501';
1538
+ END IF;
1539
+ bound_count := bound_count + 1;
1540
+ END LOOP;
1541
+ RETURN bound_count;
1542
+ END
1543
+ $function$;
1544
+
1545
+ CREATE OR REPLACE FUNCTION validate_scheduled_agent_run_live_authority(p_account_id uuid, p_workspace_id uuid, p_run_id uuid)
1546
+ RETURNS text
1547
+ LANGUAGE plpgsql
1548
+ SECURITY DEFINER
1549
+ SET search_path FROM CURRENT
1550
+ AS $function$
1551
+ DECLARE
1552
+ sender_prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
1553
+ run_row record;
1554
+ accepted jsonb;
1555
+ causal jsonb;
1556
+ snapshot record;
1557
+ BEGIN
1558
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
1559
+ IF p_account_id IS DISTINCT FROM nullif(
1560
+ current_setting('opengeni.account_id', true), ''
1561
+ )::uuid
1562
+ OR p_workspace_id IS DISTINCT FROM nullif(
1563
+ current_setting('opengeni.workspace_id', true), ''
1564
+ )::uuid
1565
+ THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_authority_scope_changed'; END IF;
1566
+ SELECT run.* INTO run_row
1567
+ FROM scheduled_task_runs run
1568
+ WHERE run.id = p_run_id
1569
+ AND run.account_id = p_account_id
1570
+ AND run.workspace_id = p_workspace_id
1571
+ FOR UPDATE;
1572
+ IF NOT FOUND OR run_row.accepted_execution_snapshot IS NULL THEN
1573
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_authority_snapshot_missing';
1574
+ END IF;
1575
+ accepted := run_row.accepted_execution_snapshot;
1576
+ causal := accepted -> 'causalHumanAuthority';
1577
+
1578
+ -- Match task admission and organization lifecycle lock order. Claim must
1579
+ -- linearize before delivery: a concurrent suspension/revocation either
1580
+ -- completes first and is observed below, or waits until this claim commits.
1581
+ PERFORM 1 FROM organization_memberships membership
1582
+ WHERE membership.account_id = p_account_id
1583
+ AND membership.id IN (
1584
+ SELECT (causal ->> 'organizationMembershipId')::uuid
1585
+ WHERE causal IS NOT NULL AND causal <> 'null'::jsonb
1586
+ UNION
1587
+ SELECT frozen.owner_organization_membership_id
1588
+ FROM scheduled_task_run_connection_authority_snapshots frozen
1589
+ WHERE frozen.run_id = p_run_id
1590
+ )
1591
+ ORDER BY membership.id
1592
+ FOR SHARE;
1593
+ PERFORM 1 FROM workspace_memberships membership
1594
+ WHERE membership.account_id = p_account_id
1595
+ AND membership.workspace_id = p_workspace_id
1596
+ AND membership.subject_id IN (
1597
+ SELECT causal ->> 'subjectId'
1598
+ WHERE causal IS NOT NULL AND causal <> 'null'::jsonb
1599
+ UNION
1600
+ SELECT frozen.owner_subject_id
1601
+ FROM scheduled_task_run_connection_authority_snapshots frozen
1602
+ WHERE frozen.run_id = p_run_id
1603
+ )
1604
+ ORDER BY membership.subject_id
1605
+ FOR SHARE;
1606
+ PERFORM 1 FROM connections connection_value
1607
+ WHERE connection_value.account_id = p_account_id
1608
+ AND connection_value.id IN (
1609
+ SELECT frozen.connection_id
1610
+ FROM scheduled_task_run_connection_authority_snapshots frozen
1611
+ WHERE frozen.run_id = p_run_id
1612
+ UNION
1613
+ SELECT (accepted -> 'resolvedSlackBotConnection' ->> 'id')::uuid
1614
+ WHERE accepted -> 'resolvedSlackBotConnection' <> 'null'::jsonb
1615
+ )
1616
+ ORDER BY connection_value.id
1617
+ FOR SHARE;
1618
+ PERFORM 1 FROM organization_user_resource_authorities authority
1619
+ WHERE authority.account_id = p_account_id
1620
+ AND authority.id IN (
1621
+ SELECT frozen.authority_id
1622
+ FROM scheduled_task_run_connection_authority_snapshots frozen
1623
+ WHERE frozen.run_id = p_run_id
1624
+ UNION
1625
+ SELECT xai_authority.id
1626
+ FROM organization_user_resource_authorities xai_authority
1627
+ WHERE accepted -> 'xaiProviderAccountAuthoritySnapshot' ->> 'scope' = 'user'
1628
+ AND causal IS NOT NULL AND causal <> 'null'::jsonb
1629
+ AND xai_authority.account_id = p_account_id
1630
+ AND xai_authority.organization_membership_id =
1631
+ (causal ->> 'organizationMembershipId')::uuid
1632
+ AND xai_authority.resource_kind = 'xai_subscription'
1633
+ AND xai_authority.generation =
1634
+ (accepted -> 'xaiProviderAccountAuthoritySnapshot'
1635
+ ->> 'authorityGeneration')::bigint
1636
+ )
1637
+ ORDER BY authority.id
1638
+ FOR SHARE;
1639
+
1640
+ PERFORM 1 FROM rigs rig
1641
+ WHERE rig.account_id = p_account_id
1642
+ AND rig.id IN (
1643
+ SELECT (accepted -> 'resolvedRig' ->> 'id')::uuid
1644
+ WHERE accepted -> 'targetSessionExecution' = 'null'::jsonb
1645
+ AND accepted -> 'resolvedRig' <> 'null'::jsonb
1646
+ UNION
1647
+ SELECT (accepted -> 'targetSessionExecution' ->> 'rigId')::uuid
1648
+ WHERE accepted -> 'targetSessionExecution' <> 'null'::jsonb
1649
+ AND accepted -> 'targetSessionExecution' ->> 'rigId' IS NOT NULL
1650
+ )
1651
+ ORDER BY rig.id
1652
+ FOR SHARE;
1653
+ PERFORM 1 FROM rig_versions version_value
1654
+ WHERE version_value.account_id = p_account_id
1655
+ AND version_value.id IN (
1656
+ SELECT (accepted -> 'resolvedRig' ->> 'versionId')::uuid
1657
+ WHERE accepted -> 'targetSessionExecution' = 'null'::jsonb
1658
+ AND accepted -> 'resolvedRig' <> 'null'::jsonb
1659
+ UNION
1660
+ SELECT (accepted -> 'targetSessionExecution' ->> 'rigVersionId')::uuid
1661
+ WHERE accepted -> 'targetSessionExecution' <> 'null'::jsonb
1662
+ AND accepted -> 'targetSessionExecution' ->> 'rigVersionId' IS NOT NULL
1663
+ )
1664
+ ORDER BY version_value.id
1665
+ FOR SHARE;
1666
+
1667
+ IF causal IS NOT NULL AND causal <> 'null'::jsonb AND NOT EXISTS (
1668
+ SELECT 1 FROM organization_memberships membership
1669
+ WHERE membership.id = (causal ->> 'organizationMembershipId')::uuid
1670
+ AND membership.account_id = p_account_id
1671
+ AND membership.subject_id = causal ->> 'subjectId'
1672
+ AND membership.status = 'active'
1673
+ AND membership.revoked_at IS NULL
1674
+ AND membership.authorization_revision =
1675
+ (causal ->> 'membershipAuthorizationRevision')::bigint
1676
+ AND (
1677
+ membership.personal_workspace_id = p_workspace_id
1678
+ OR EXISTS (
1679
+ SELECT 1 FROM workspace_memberships workspace_membership
1680
+ WHERE workspace_membership.account_id = p_account_id
1681
+ AND workspace_membership.workspace_id = p_workspace_id
1682
+ AND workspace_membership.subject_id = causal ->> 'subjectId'
1683
+ )
1684
+ )
1685
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_causal_membership_changed'; END IF;
1686
+ IF accepted -> 'xaiProviderAccountAuthoritySnapshot' ->> 'scope' = 'user'
1687
+ AND NOT EXISTS (
1688
+ SELECT 1 FROM organization_user_resource_authorities authority
1689
+ WHERE authority.account_id = p_account_id
1690
+ AND authority.organization_membership_id =
1691
+ (causal ->> 'organizationMembershipId')::uuid
1692
+ AND authority.resource_kind = 'xai_subscription'
1693
+ AND authority.generation =
1694
+ (accepted -> 'xaiProviderAccountAuthoritySnapshot'
1695
+ ->> 'authorityGeneration')::bigint
1696
+ AND authority.status = 'active'
1697
+ AND authority.revoked_at IS NULL
1698
+ )
1699
+ THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_xai_authority_changed'; END IF;
1700
+ IF accepted -> 'resolvedSlackBotConnection' <> 'null'::jsonb
1701
+ AND NOT EXISTS (
1702
+ SELECT 1 FROM connections connection_value
1703
+ WHERE connection_value.id =
1704
+ (accepted -> 'resolvedSlackBotConnection' ->> 'id')::uuid
1705
+ AND connection_value.account_id = p_account_id
1706
+ AND connection_value.workspace_id = p_workspace_id
1707
+ AND connection_value.subject_id IS NULL
1708
+ AND connection_value.provider_domain = 'slack.com'
1709
+ AND connection_value.kind = 'app_install'
1710
+ AND connection_value.status = 'active'
1711
+ AND connection_value.verified_install_at IS NOT NULL
1712
+ AND connection_value.version =
1713
+ (accepted -> 'resolvedSlackBotConnection' ->> 'version')::integer
1714
+ AND connection_value.verified_install_version =
1715
+ (accepted -> 'resolvedSlackBotConnection'
1716
+ ->> 'verifiedInstallVersion')::integer
1717
+ AND connection_value.verified_install_version = connection_value.version
1718
+ AND connection_value.metadata IS NOT DISTINCT FROM
1719
+ accepted -> 'resolvedSlackBotConnection' -> 'metadata'
1720
+ )
1721
+ THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_slack_bot_changed'; END IF;
1722
+
1723
+ FOR snapshot IN
1724
+ SELECT value.* FROM scheduled_task_run_connection_authority_snapshots value
1725
+ WHERE value.run_id = p_run_id
1726
+ ORDER BY value.server_id
1727
+ LOOP
1728
+ IF snapshot.canonical_snapshot ->> 'authoritySource' IS DISTINCT FROM 'sender'
1729
+ THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_connection_authority_changed'; END IF;
1730
+ IF NOT EXISTS (
1731
+ SELECT 1 FROM organization_memberships membership
1732
+ WHERE membership.id = snapshot.owner_organization_membership_id
1733
+ AND membership.account_id = p_account_id
1734
+ AND membership.subject_id = snapshot.owner_subject_id
1735
+ AND membership.status = 'active'
1736
+ AND membership.revoked_at IS NULL
1737
+ AND membership.authorization_revision = snapshot.membership_authorization_revision
1738
+ AND (
1739
+ membership.personal_workspace_id = p_workspace_id
1740
+ OR EXISTS (
1741
+ SELECT 1 FROM workspace_memberships workspace_membership
1742
+ WHERE workspace_membership.account_id = p_account_id
1743
+ AND workspace_membership.workspace_id = p_workspace_id
1744
+ AND workspace_membership.subject_id = snapshot.owner_subject_id
1745
+ )
1746
+ )
1747
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_connection_membership_changed'; END IF;
1748
+ IF NOT EXISTS (
1749
+ SELECT 1 FROM opengeni_private.read_sender_connection(
1750
+ p_account_id, snapshot.origin_workspace_id, snapshot.connection_id, snapshot.owner_subject_id
1751
+ ) connection_value
1752
+ WHERE connection_value.id = snapshot.connection_id
1753
+ AND connection_value.account_id = p_account_id
1754
+ AND connection_value.workspace_id = snapshot.origin_workspace_id
1755
+ AND connection_value.subject_id = snapshot.owner_subject_id
1756
+ AND connection_value.owner_organization_membership_id =
1757
+ snapshot.owner_organization_membership_id
1758
+ AND connection_value.authority_scope = 'user'
1759
+ AND connection_value.authority_generation = snapshot.connection_generation
1760
+ AND connection_value.status = 'active'
1761
+ AND lower(connection_value.provider_domain) = snapshot.provider_domain
1762
+ AND connection_value.kind = snapshot.connection_kind
1763
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_connection_changed'; END IF;
1764
+ IF NOT EXISTS (
1765
+ SELECT 1 FROM organization_user_resource_authorities authority
1766
+ WHERE authority.id = snapshot.authority_id
1767
+ AND authority.account_id = p_account_id
1768
+ AND authority.organization_membership_id =
1769
+ snapshot.owner_organization_membership_id
1770
+ AND authority.resource_kind = 'connection'
1771
+ AND authority.resource_id = snapshot.connection_id
1772
+ AND authority.origin_workspace_id = snapshot.origin_workspace_id
1773
+ AND authority.generation = snapshot.authority_generation
1774
+ AND authority.status = 'active'
1775
+ AND authority.revoked_at IS NULL
1776
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_connection_authority_changed'; END IF;
1777
+ END LOOP;
1778
+
1779
+ IF accepted -> 'targetSessionExecution' = 'null'::jsonb THEN
1780
+ IF accepted -> 'resolvedVariableSet' <> 'null'::jsonb AND NOT EXISTS (
1781
+ SELECT 1 FROM workspace_variable_sets variable_set
1782
+ WHERE variable_set.id = (accepted -> 'resolvedVariableSet' ->> 'id')::uuid
1783
+ AND variable_set.account_id = p_account_id
1784
+ AND variable_set.status = 'active'
1785
+ AND variable_set.generation =
1786
+ (accepted -> 'resolvedVariableSet' ->> 'generation')::bigint
1787
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_variable_set_changed'; END IF;
1788
+ IF accepted -> 'resolvedRig' <> 'null'::jsonb AND NOT EXISTS (
1789
+ SELECT 1 FROM rigs rig
1790
+ JOIN rig_versions version_value
1791
+ ON version_value.id = (accepted -> 'resolvedRig' ->> 'versionId')::uuid
1792
+ AND version_value.rig_id = rig.id
1793
+ AND version_value.account_id = rig.account_id
1794
+ WHERE rig.id = (accepted -> 'resolvedRig' ->> 'id')::uuid
1795
+ AND rig.account_id = p_account_id
1796
+ AND rig.status = 'active'
1797
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_rig_changed'; END IF;
1798
+ IF accepted -> 'resolvedRig' <> 'null'::jsonb AND (
1799
+ accepted -> 'resolvedRig' -> 'defaultVariableSets' IS DISTINCT FROM (
1800
+ SELECT coalesce(
1801
+ jsonb_agg(
1802
+ jsonb_build_object('id', variable_set.id, 'generation', variable_set.generation)
1803
+ ORDER BY selected.ordinality
1804
+ ),
1805
+ '[]'::jsonb
1806
+ )
1807
+ FROM rig_versions version_value
1808
+ CROSS JOIN LATERAL jsonb_array_elements_text(
1809
+ coalesce(version_value.default_variable_set_ids, '[]'::jsonb)
1810
+ ) WITH ORDINALITY selected(id, ordinality)
1811
+ JOIN workspace_variable_sets variable_set
1812
+ ON variable_set.id = selected.id::uuid
1813
+ AND variable_set.account_id = p_account_id
1814
+ AND variable_set.status = 'active'
1815
+ WHERE version_value.id = (accepted -> 'resolvedRig' ->> 'versionId')::uuid
1816
+ AND version_value.rig_id = (accepted -> 'resolvedRig' ->> 'id')::uuid
1817
+ AND version_value.account_id = p_account_id
1818
+ )
1819
+ ) THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN 'scheduled_rig_default_variable_set_changed'; END IF;
1820
+ END IF;
1821
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NULL;
1822
+ EXCEPTION WHEN OTHERS THEN RAISE;
1823
+ END
1824
+ $function$;
1825
+
1826
+ CREATE OR REPLACE FUNCTION opengeni_private.capture_scheduled_turn_connection_authorities()
1827
+ RETURNS trigger
1828
+ LANGUAGE plpgsql
1829
+ SECURITY DEFINER
1830
+ SET search_path FROM CURRENT
1831
+ AS $function$
1832
+ DECLARE
1833
+ run_row record;
1834
+ run_snapshot record;
1835
+ item jsonb;
1836
+ selected_count integer;
1837
+ snapshot_count integer;
1838
+ canonical jsonb;
1839
+ accepted jsonb;
1840
+ execution_policy jsonb;
1841
+ BEGIN
1842
+ IF TG_OP = 'UPDATE' THEN
1843
+ IF NEW.scheduled_task_run_id IS DISTINCT FROM OLD.scheduled_task_run_id THEN
1844
+ RAISE EXCEPTION 'scheduled logical-turn occurrence identity is immutable'
1845
+ USING ERRCODE = '23514';
1846
+ END IF;
1847
+ IF NEW.personal_connection_delegations IS NOT DISTINCT FROM OLD.personal_connection_delegations
1848
+ THEN RETURN NEW;
1849
+ END IF;
1850
+ RAISE EXCEPTION 'scheduled connection authority cannot change after acceptance'
1851
+ USING ERRCODE = '42501';
1852
+ END IF;
1853
+
1854
+ SELECT run.* INTO STRICT run_row FROM scheduled_task_runs run
1855
+ WHERE run.id = NEW.scheduled_task_run_id AND run.account_id = NEW.account_id
1856
+ AND run.workspace_id = NEW.workspace_id
1857
+ FOR UPDATE;
1858
+ IF run_row.status NOT IN ('queued', 'dispatched')
1859
+ OR run_row.session_id IS DISTINCT FROM NEW.session_id
1860
+ THEN
1861
+ RAISE EXCEPTION 'scheduled turn does not match its run session'
1862
+ USING ERRCODE = '42501';
1863
+ END IF;
1864
+ accepted := run_row.accepted_execution_snapshot;
1865
+ execution_policy := accepted -> 'targetSessionExecution';
1866
+ IF execution_policy = 'null'::jsonb THEN
1867
+ IF NEW.model IS DISTINCT FROM accepted ->> 'resolvedModel'
1868
+ OR NEW.reasoning_effort IS DISTINCT FROM accepted ->> 'resolvedReasoningEffort'
1869
+ OR NEW.latency_mode IS DISTINCT FROM accepted ->> 'resolvedLatencyMode'
1870
+ OR NEW.tools IS DISTINCT FROM accepted -> 'resolvedTools'
1871
+ OR NEW.sandbox_backend IS DISTINCT FROM accepted ->> 'resolvedSandboxBackend'
1872
+ OR NEW.sandbox_os IS DISTINCT FROM accepted ->> 'resolvedSandboxOs'
1873
+ THEN RAISE EXCEPTION 'generated scheduled turn differs from accepted execution policy'
1874
+ USING ERRCODE = '42501'; END IF;
1875
+ ELSE
1876
+ IF execution_policy ->> 'sessionId' IS DISTINCT FROM NEW.session_id::text
1877
+ OR NEW.model IS DISTINCT FROM execution_policy ->> 'model'
1878
+ OR NEW.reasoning_effort IS DISTINCT FROM execution_policy ->> 'reasoningEffort'
1879
+ OR NEW.latency_mode IS DISTINCT FROM execution_policy ->> 'latencyMode'
1880
+ OR NEW.tools IS DISTINCT FROM execution_policy -> 'tools'
1881
+ OR NEW.sandbox_backend IS DISTINCT FROM execution_policy ->> 'sandboxBackend'
1882
+ OR NEW.sandbox_os IS DISTINCT FROM execution_policy ->> 'sandboxOs'
1883
+ THEN RAISE EXCEPTION 'targeted scheduled turn differs from accepted execution policy'
1884
+ USING ERRCODE = '42501'; END IF;
1885
+ END IF;
1886
+ IF NEW.personal_connection_delegations IS DISTINCT FROM accepted -> 'personalConnectionDelegations'
1887
+ OR NEW.initiating_human_subject_id IS DISTINCT FROM accepted ->> 'causalHumanSubjectId'
1888
+ THEN RAISE EXCEPTION 'scheduled turn differs from its accepted owner accounts'
1889
+ USING ERRCODE = '42501'; END IF;
1890
+ PERFORM bind_scheduled_task_run_connection_authorities(
1891
+ NEW.account_id, NEW.workspace_id, NEW.scheduled_task_run_id, NEW.session_id
1892
+ );
1893
+
1894
+ SELECT count(*)::integer INTO snapshot_count
1895
+ FROM scheduled_task_run_connection_authority_snapshots snapshot
1896
+ WHERE snapshot.run_id = NEW.scheduled_task_run_id;
1897
+ SELECT count(*)::integer INTO selected_count
1898
+ FROM jsonb_array_elements(NEW.personal_connection_delegations) selected
1899
+ WHERE selected ->> 'connectionType' IS DISTINCT FROM 'social';
1900
+ IF selected_count IS DISTINCT FROM snapshot_count THEN
1901
+ RAISE EXCEPTION 'scheduled turn connection authority widened or disappeared'
1902
+ USING ERRCODE = '42501';
1903
+ END IF;
1904
+
1905
+ FOR run_snapshot IN SELECT snapshot.*
1906
+ FROM scheduled_task_run_connection_authority_snapshots snapshot
1907
+ WHERE snapshot.run_id = NEW.scheduled_task_run_id
1908
+ ORDER BY snapshot.server_id
1909
+ LOOP
1910
+ SELECT value INTO STRICT item
1911
+ FROM jsonb_array_elements(NEW.personal_connection_delegations)
1912
+ WHERE value ->> 'serverId' = run_snapshot.server_id;
1913
+ IF run_snapshot.canonical_snapshot ->> 'authoritySource' IS DISTINCT FROM 'sender'
1914
+ OR NEW.initiating_human_subject_id IS DISTINCT FROM run_snapshot.owner_subject_id
1915
+ OR item IS DISTINCT FROM run_snapshot.canonical_snapshot -> 'selection'
1916
+ THEN RAISE EXCEPTION 'scheduled turn connection selection changed from its run'
1917
+ USING ERRCODE = '42501'; END IF;
1918
+ canonical := jsonb_build_object(
1919
+ 'organizationId', NEW.account_id,
1920
+ 'originWorkspaceId', run_snapshot.origin_workspace_id,
1921
+ 'targetWorkspaceId', NEW.workspace_id,
1922
+ 'targetSessionId', NEW.session_id,
1923
+ 'targetSessionVisibility', run_snapshot.session_visibility,
1924
+ 'targetSessionAuthorityEpoch', run_snapshot.session_authority_epoch,
1925
+ 'acceptedWork', jsonb_build_object(
1926
+ 'kind', 'scheduled_task', 'taskId', run_snapshot.task_id,
1927
+ 'taskAuthorityRevision', run_snapshot.task_authority_revision,
1928
+ 'runId', run_snapshot.run_id
1929
+ ),
1930
+ 'connectionId', run_snapshot.connection_id,
1931
+ 'connectionGeneration', run_snapshot.connection_generation,
1932
+ 'connectionStatus', 'active', 'providerDomain', run_snapshot.provider_domain,
1933
+ 'connectionKind', run_snapshot.connection_kind, 'scope', 'user',
1934
+ 'ownerSubjectId', run_snapshot.owner_subject_id,
1935
+ 'ownerOrganizationMembershipId', run_snapshot.owner_organization_membership_id,
1936
+ 'ownerMembershipAuthorizationRevision', run_snapshot.membership_authorization_revision,
1937
+ 'authoritySource', 'sender',
1938
+ 'selectionSources', to_jsonb(run_snapshot.selection_sources),
1939
+ 'userDelegation', NULL
1940
+ );
1941
+ INSERT INTO turn_connection_authority_snapshots (
1942
+ account_id, workspace_id, session_id, turn_id, server_id,
1943
+ connection_id, connection_generation, origin_workspace_id,
1944
+ provider_domain, connection_kind, authority_scope, authority_source,
1945
+ owner_subject_id, owner_organization_membership_id,
1946
+ membership_authorization_revision, authority_id, authority_generation,
1947
+ grant_id, grant_generation, grant_mode, grant_context, grant_session_id,
1948
+ grant_authority_epoch, session_visibility, session_authority_epoch,
1949
+ canonical_snapshot, snapshot_digest
1950
+ ) VALUES (
1951
+ NEW.account_id, NEW.workspace_id, NEW.session_id, NEW.id, run_snapshot.server_id,
1952
+ run_snapshot.connection_id, run_snapshot.connection_generation,
1953
+ run_snapshot.origin_workspace_id, run_snapshot.provider_domain,
1954
+ run_snapshot.connection_kind, 'user', 'sender',
1955
+ run_snapshot.owner_subject_id, run_snapshot.owner_organization_membership_id,
1956
+ run_snapshot.membership_authorization_revision, run_snapshot.authority_id,
1957
+ run_snapshot.authority_generation, NULL,
1958
+ NULL, NULL,
1959
+ NULL, NULL,
1960
+ NULL, run_snapshot.session_visibility,
1961
+ run_snapshot.session_authority_epoch, canonical,
1962
+ digest(convert_to(canonical::text, 'UTF8'), 'sha256')
1963
+ );
1964
+ END LOOP;
1965
+ RETURN NEW;
1966
+ END
1967
+ $function$;
1968
+
1969
+ -- Schedule definitions retain account choices, not connection consent grants.
1970
+ CREATE OR REPLACE FUNCTION freeze_scheduled_task_personal_resources(p_account_id uuid, p_workspace_id uuid, p_task_id uuid, p_revision bigint)
1971
+ RETURNS integer
1972
+ LANGUAGE plpgsql
1973
+ SECURITY DEFINER
1974
+ SET search_path FROM CURRENT
1975
+ AS $function$
1976
+ DECLARE
1977
+ task_row record;
1978
+ member_row record;
1979
+ session_row record;
1980
+ resource_row record;
1981
+ grant_row record;
1982
+ initiating_subject text := coalesce(
1983
+ nullif(btrim(current_setting('opengeni.initiating_human_subject_id', true)), ''),
1984
+ nullif(btrim(current_setting('opengeni.subject_id', true)), '')
1985
+ );
1986
+ target_session uuid;
1987
+ target_visibility text := 'workspace_shared';
1988
+ target_epoch integer;
1989
+ target_rig_version_id uuid;
1990
+ resource_total integer := 0;
1991
+ BEGIN
1992
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
1993
+ OR p_workspace_id IS DISTINCT FROM nullif(
1994
+ current_setting('opengeni.workspace_id', true), ''
1995
+ )::uuid
1996
+ THEN
1997
+ RAISE EXCEPTION 'scheduled personal-resource task scope mismatch' USING ERRCODE = '42501';
1998
+ END IF;
1999
+
2000
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
2001
+ backend_pid, transaction_id, capability_kind
2002
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'task_write')
2003
+ ON CONFLICT DO NOTHING;
2004
+
2005
+ SELECT task.* INTO STRICT task_row
2006
+ FROM scheduled_tasks task
2007
+ WHERE task.id = p_task_id
2008
+ AND task.account_id = p_account_id
2009
+ AND task.workspace_id = p_workspace_id
2010
+ AND task.authority_revision = p_revision
2011
+ FOR UPDATE;
2012
+
2013
+ IF task_row.run_mode = 'existing_session'
2014
+ OR (task_row.run_mode = 'reusable_session' AND task_row.reusable_session_id IS NOT NULL)
2015
+ THEN
2016
+ target_session := task_row.reusable_session_id;
2017
+ SELECT session_value.* INTO STRICT session_row
2018
+ FROM sessions session_value
2019
+ WHERE session_value.id = target_session
2020
+ AND session_value.account_id = p_account_id
2021
+ AND session_value.workspace_id = p_workspace_id
2022
+ AND session_value.status <> 'cancelled'
2023
+ FOR SHARE;
2024
+ target_visibility := session_row.visibility;
2025
+ target_epoch := session_row.authority_epoch;
2026
+ target_rig_version_id := session_row.rig_version_id;
2027
+ END IF;
2028
+
2029
+ SELECT count(*)::integer INTO resource_total
2030
+ FROM (
2031
+ SELECT variable_set.id
2032
+ FROM workspace_variable_sets variable_set
2033
+ WHERE variable_set.id = task_row.variable_set_id
2034
+ AND variable_set.account_id = p_account_id
2035
+ AND variable_set.authority_scope = 'user'
2036
+ UNION
2037
+ SELECT rig.id
2038
+ FROM rigs rig
2039
+ WHERE rig.id = task_row.rig_id
2040
+ AND rig.account_id = p_account_id
2041
+ AND rig.authority_scope = 'user'
2042
+ UNION
2043
+ SELECT default_variable_set.id
2044
+ FROM rigs rig
2045
+ JOIN rig_versions rig_version
2046
+ ON rig_version.rig_id = rig.id
2047
+ AND rig_version.account_id = rig.account_id
2048
+ AND (
2049
+ (target_session IS NULL AND rig_version.active)
2050
+ OR (target_session IS NOT NULL AND rig_version.id = target_rig_version_id)
2051
+ )
2052
+ CROSS JOIN LATERAL jsonb_array_elements_text(
2053
+ rig_version.default_variable_set_ids
2054
+ ) default_id(value)
2055
+ JOIN workspace_variable_sets default_variable_set
2056
+ ON default_variable_set.id = default_id.value::uuid
2057
+ AND default_variable_set.account_id = p_account_id
2058
+ AND default_variable_set.authority_scope = 'user'
2059
+ WHERE rig.id = task_row.rig_id
2060
+ AND rig.account_id = p_account_id
2061
+ ) selected;
2062
+
2063
+ IF resource_total = 0 THEN
2064
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
2065
+ WHERE backend_pid = pg_backend_pid()
2066
+ AND transaction_id = pg_current_xact_id_if_assigned()
2067
+ AND capability_kind = 'task_write';
2068
+ RETURN 0;
2069
+ END IF;
2070
+
2071
+ IF initiating_subject IS NULL THEN
2072
+ RAISE EXCEPTION 'scheduled personal resources require a causal human'
2073
+ USING ERRCODE = '42501';
2074
+ END IF;
2075
+
2076
+ SELECT membership.* INTO STRICT member_row
2077
+ FROM organization_memberships membership
2078
+ WHERE membership.account_id = p_account_id
2079
+ AND membership.subject_id = initiating_subject
2080
+ AND membership.status = 'active'
2081
+ AND membership.revoked_at IS NULL
2082
+ FOR SHARE;
2083
+
2084
+ IF member_row.personal_workspace_id IS DISTINCT FROM p_workspace_id THEN
2085
+ PERFORM 1 FROM workspace_memberships workspace_membership
2086
+ WHERE workspace_membership.account_id = p_account_id
2087
+ AND workspace_membership.workspace_id = p_workspace_id
2088
+ AND workspace_membership.subject_id = initiating_subject
2089
+ FOR KEY SHARE;
2090
+ IF NOT FOUND THEN
2091
+ RAISE EXCEPTION 'scheduled personal-resource owner lacks workspace access'
2092
+ USING ERRCODE = '42501';
2093
+ END IF;
2094
+ END IF;
2095
+
2096
+ INSERT INTO scheduled_task_personal_resource_authorities (
2097
+ task_id, task_authority_revision, account_id, workspace_id,
2098
+ initiating_human_subject_id, owner_organization_membership_id,
2099
+ membership_authorization_revision, target_session_id, session_visibility,
2100
+ session_authority_epoch, execution_digest, resource_count
2101
+ ) VALUES (
2102
+ p_task_id, p_revision, p_account_id, p_workspace_id,
2103
+ initiating_subject, member_row.id, member_row.authorization_revision,
2104
+ target_session, target_visibility, target_epoch, task_row.execution_digest,
2105
+ resource_total
2106
+ );
2107
+
2108
+ FOR resource_row IN
2109
+ WITH selected AS (
2110
+ SELECT 'variable_set'::text AS resource_kind, variable_set.id AS resource_id,
2111
+ NULL::uuid AS resource_version_id, 'variable_set.use'::text AS action,
2112
+ 'session_variable_set'::text AS selection_source,
2113
+ variable_set.workspace_id AS resource_workspace_id, variable_set.authority_id,
2114
+ variable_set.owner_organization_membership_id, variable_set.origin_workspace_id
2115
+ FROM workspace_variable_sets variable_set
2116
+ WHERE variable_set.id = task_row.variable_set_id
2117
+ AND variable_set.account_id = p_account_id
2118
+ AND variable_set.authority_scope = 'user'
2119
+ UNION ALL
2120
+ SELECT 'rig'::text, rig.id, rig_version.id, 'rig.use'::text,
2121
+ 'session_rig'::text, rig.workspace_id, rig.authority_id,
2122
+ rig.owner_organization_membership_id, rig.origin_workspace_id
2123
+ FROM rigs rig
2124
+ JOIN rig_versions rig_version
2125
+ ON rig_version.rig_id = rig.id
2126
+ AND rig_version.account_id = rig.account_id
2127
+ AND (
2128
+ (target_session IS NULL AND rig_version.active)
2129
+ OR (target_session IS NOT NULL AND rig_version.id = target_rig_version_id)
2130
+ )
2131
+ WHERE rig.id = task_row.rig_id
2132
+ AND rig.account_id = p_account_id
2133
+ AND rig.authority_scope = 'user'
2134
+ UNION ALL
2135
+ SELECT 'variable_set'::text, default_variable_set.id, NULL::uuid,
2136
+ 'variable_set.use'::text,
2137
+ ('rig_default_variable_set:' || default_id.ordinality::text)::text,
2138
+ default_variable_set.workspace_id, default_variable_set.authority_id,
2139
+ default_variable_set.owner_organization_membership_id,
2140
+ default_variable_set.origin_workspace_id
2141
+ FROM rigs rig
2142
+ JOIN rig_versions rig_version
2143
+ ON rig_version.rig_id = rig.id
2144
+ AND rig_version.account_id = rig.account_id
2145
+ AND (
2146
+ (target_session IS NULL AND rig_version.active)
2147
+ OR (target_session IS NOT NULL AND rig_version.id = target_rig_version_id)
2148
+ )
2149
+ CROSS JOIN LATERAL jsonb_array_elements_text(
2150
+ rig_version.default_variable_set_ids
2151
+ ) WITH ORDINALITY default_id(value, ordinality)
2152
+ JOIN workspace_variable_sets default_variable_set
2153
+ ON default_variable_set.id = default_id.value::uuid
2154
+ AND default_variable_set.account_id = p_account_id
2155
+ AND default_variable_set.authority_scope = 'user'
2156
+ WHERE rig.id = task_row.rig_id
2157
+ AND rig.account_id = p_account_id
2158
+ )
2159
+ SELECT resource_kind, resource_id, min(resource_version_id::text)::uuid resource_version_id,
2160
+ action, array_agg(selection_source ORDER BY selection_source) selection_sources,
2161
+ min(resource_workspace_id::text)::uuid resource_workspace_id,
2162
+ min(authority_id::text)::uuid authority_id,
2163
+ min(owner_organization_membership_id::text)::uuid owner_organization_membership_id,
2164
+ min(origin_workspace_id::text)::uuid origin_workspace_id
2165
+ FROM selected
2166
+ GROUP BY resource_kind, resource_id, action
2167
+ ORDER BY resource_kind, resource_id
2168
+ LOOP
2169
+ IF resource_row.owner_organization_membership_id IS DISTINCT FROM member_row.id
2170
+ THEN
2171
+ RAISE EXCEPTION 'scheduled personal resource belongs to another human or organization'
2172
+ USING ERRCODE = '42501';
2173
+ END IF;
2174
+
2175
+ SELECT grant_value.* INTO grant_row
2176
+ FROM organization_user_resource_grants grant_value
2177
+ JOIN organization_user_resource_authorities authority
2178
+ ON authority.id = grant_value.authority_id
2179
+ AND authority.account_id = grant_value.account_id
2180
+ WHERE authority.id = resource_row.authority_id
2181
+ AND authority.account_id = p_account_id
2182
+ AND authority.organization_membership_id = member_row.id
2183
+ AND authority.resource_kind = resource_row.resource_kind
2184
+ AND authority.resource_id = resource_row.resource_id
2185
+ AND authority.status = 'active'
2186
+ AND authority.revoked_at IS NULL
2187
+ AND grant_value.owner_organization_membership_id = member_row.id
2188
+ AND grant_value.workspace_id = p_workspace_id
2189
+ AND grant_value.action = resource_row.action
2190
+ AND grant_value.context = target_visibility
2191
+ AND grant_value.status = 'active'
2192
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
2193
+ AND (
2194
+ (target_session IS NULL AND grant_value.mode = 'always'
2195
+ AND grant_value.session_id IS NULL AND grant_value.authority_epoch IS NULL)
2196
+ OR (target_session IS NOT NULL AND (
2197
+ (grant_value.mode IN ('once', 'session')
2198
+ AND grant_value.session_id = target_session
2199
+ AND grant_value.authority_epoch = target_epoch)
2200
+ OR (grant_value.mode = 'always'
2201
+ AND grant_value.session_id IS NULL AND grant_value.authority_epoch IS NULL)
2202
+ ))
2203
+ )
2204
+ ORDER BY CASE grant_value.mode WHEN 'once' THEN 1 WHEN 'session' THEN 2 ELSE 3 END,
2205
+ grant_value.generation DESC, grant_value.id
2206
+ LIMIT 1
2207
+ FOR SHARE OF authority, grant_value;
2208
+ IF NOT FOUND THEN
2209
+ RAISE EXCEPTION 'matching scheduled personal-resource grant required'
2210
+ USING ERRCODE = '42501';
2211
+ END IF;
2212
+
2213
+ INSERT INTO scheduled_task_personal_resource_snapshots (
2214
+ task_id, task_authority_revision, account_id, workspace_id,
2215
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
2216
+ origin_workspace_id, owner_organization_membership_id,
2217
+ membership_authorization_revision, authority_id, authority_generation,
2218
+ target_workspace_id, session_visibility, session_authority_epoch,
2219
+ grant_id, grant_generation, grant_mode, grant_context,
2220
+ grant_session_id, grant_authority_epoch
2221
+ )
2222
+ SELECT p_task_id, p_revision, p_account_id, p_workspace_id,
2223
+ resource_row.resource_kind, resource_row.resource_id,
2224
+ resource_row.resource_version_id, resource_row.selection_sources, resource_row.action,
2225
+ resource_row.origin_workspace_id, member_row.id, member_row.authorization_revision,
2226
+ authority.id, authority.generation, p_workspace_id, target_visibility, target_epoch,
2227
+ grant_row.id, grant_row.generation, grant_row.mode, grant_row.context,
2228
+ grant_row.session_id, grant_row.authority_epoch
2229
+ FROM organization_user_resource_authorities authority
2230
+ WHERE authority.id = resource_row.authority_id
2231
+ AND authority.account_id = p_account_id;
2232
+ END LOOP;
2233
+
2234
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
2235
+ WHERE backend_pid = pg_backend_pid()
2236
+ AND transaction_id = pg_current_xact_id_if_assigned()
2237
+ AND capability_kind = 'task_write';
2238
+ RETURN resource_total;
2239
+ EXCEPTION WHEN OTHERS THEN
2240
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
2241
+ WHERE backend_pid = pg_backend_pid()
2242
+ AND transaction_id = pg_current_xact_id_if_assigned()
2243
+ AND capability_kind = 'task_write';
2244
+ RAISE;
2245
+ END
2246
+ $function$
2247
+ ;
2248
+
2249
+ CREATE OR REPLACE FUNCTION clone_scheduled_task_personal_resource_authority(p_account_id uuid, p_workspace_id uuid, p_task_id uuid, p_source_revision bigint, p_target_revision bigint)
2250
+ RETURNS integer
2251
+ LANGUAGE plpgsql
2252
+ SECURITY DEFINER
2253
+ SET search_path FROM CURRENT
2254
+ AS $function$
2255
+ DECLARE
2256
+ source_authority record;
2257
+ target_execution_digest text;
2258
+ copied_count integer;
2259
+ BEGIN
2260
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
2261
+ OR p_workspace_id IS DISTINCT FROM nullif(
2262
+ current_setting('opengeni.workspace_id', true), ''
2263
+ )::uuid
2264
+ THEN
2265
+ RAISE EXCEPTION 'scheduled personal-resource clone scope mismatch'
2266
+ USING ERRCODE = '42501';
2267
+ END IF;
2268
+
2269
+ IF p_source_revision <= 0
2270
+ OR p_target_revision <= p_source_revision
2271
+ THEN
2272
+ RAISE EXCEPTION 'scheduled personal-resource authority clone revision is invalid'
2273
+ USING ERRCODE = '22023';
2274
+ END IF;
2275
+
2276
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
2277
+ backend_pid, transaction_id, capability_kind
2278
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), 'task_write')
2279
+ ON CONFLICT DO NOTHING;
2280
+
2281
+ SELECT task.execution_digest INTO target_execution_digest
2282
+ FROM scheduled_tasks task
2283
+ WHERE task.id = p_task_id
2284
+ AND task.account_id = p_account_id
2285
+ AND task.workspace_id = p_workspace_id
2286
+ AND task.authority_revision = p_target_revision
2287
+ FOR UPDATE;
2288
+ IF NOT FOUND THEN
2289
+ RAISE EXCEPTION 'scheduled personal-resource clone target is not current'
2290
+ USING ERRCODE = '40001';
2291
+ END IF;
2292
+
2293
+ SELECT authority.* INTO source_authority
2294
+ FROM scheduled_task_personal_resource_authorities authority
2295
+ WHERE authority.task_id = p_task_id
2296
+ AND authority.task_authority_revision = p_source_revision
2297
+ AND authority.account_id = p_account_id
2298
+ AND authority.workspace_id = p_workspace_id;
2299
+ IF NOT FOUND THEN
2300
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
2301
+ WHERE backend_pid = pg_backend_pid()
2302
+ AND transaction_id = pg_current_xact_id_if_assigned()
2303
+ AND capability_kind = 'task_write';
2304
+ RETURN 0;
2305
+ END IF;
2306
+
2307
+ INSERT INTO scheduled_task_personal_resource_authorities (
2308
+ task_id, task_authority_revision, account_id, workspace_id,
2309
+ initiating_human_subject_id, owner_organization_membership_id,
2310
+ membership_authorization_revision, target_session_id, session_visibility,
2311
+ session_authority_epoch, execution_digest, resource_count
2312
+ ) VALUES (
2313
+ source_authority.task_id, p_target_revision,
2314
+ source_authority.account_id, source_authority.workspace_id,
2315
+ source_authority.initiating_human_subject_id,
2316
+ source_authority.owner_organization_membership_id,
2317
+ source_authority.membership_authorization_revision,
2318
+ source_authority.target_session_id, source_authority.session_visibility,
2319
+ source_authority.session_authority_epoch, target_execution_digest,
2320
+ source_authority.resource_count
2321
+ );
2322
+
2323
+ INSERT INTO scheduled_task_personal_resource_snapshots (
2324
+ task_id, task_authority_revision, account_id, workspace_id,
2325
+ resource_kind, resource_id, resource_version_id, selection_sources, action,
2326
+ origin_workspace_id, owner_organization_membership_id,
2327
+ membership_authorization_revision, authority_id, authority_generation,
2328
+ target_workspace_id, session_visibility, session_authority_epoch,
2329
+ grant_id, grant_generation, grant_mode, grant_context,
2330
+ grant_session_id, grant_authority_epoch
2331
+ )
2332
+ SELECT snapshot.task_id, p_target_revision,
2333
+ snapshot.account_id, snapshot.workspace_id, snapshot.resource_kind,
2334
+ snapshot.resource_id, snapshot.resource_version_id, snapshot.selection_sources,
2335
+ snapshot.action, snapshot.origin_workspace_id,
2336
+ snapshot.owner_organization_membership_id,
2337
+ snapshot.membership_authorization_revision, snapshot.authority_id,
2338
+ snapshot.authority_generation, snapshot.target_workspace_id,
2339
+ snapshot.session_visibility, snapshot.session_authority_epoch,
2340
+ snapshot.grant_id, snapshot.grant_generation, snapshot.grant_mode,
2341
+ snapshot.grant_context, snapshot.grant_session_id, snapshot.grant_authority_epoch
2342
+ FROM scheduled_task_personal_resource_snapshots snapshot
2343
+ WHERE snapshot.task_id = p_task_id
2344
+ AND snapshot.task_authority_revision = p_source_revision;
2345
+ GET DIAGNOSTICS copied_count = ROW_COUNT;
2346
+ IF copied_count <> source_authority.resource_count THEN
2347
+ RAISE EXCEPTION 'scheduled personal-resource authority clone is incomplete'
2348
+ USING ERRCODE = '42501';
2349
+ END IF;
2350
+
2351
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
2352
+ WHERE backend_pid = pg_backend_pid()
2353
+ AND transaction_id = pg_current_xact_id_if_assigned()
2354
+ AND capability_kind = 'task_write';
2355
+ RETURN copied_count;
2356
+ EXCEPTION WHEN OTHERS THEN
2357
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
2358
+ WHERE backend_pid = pg_backend_pid()
2359
+ AND transaction_id = pg_current_xact_id_if_assigned()
2360
+ AND capability_kind = 'task_write';
2361
+ RAISE;
2362
+ END
2363
+ $function$
2364
+ ;
2365
+
2366
+
2367
+ -- Retire native connection-consent issuance; other resource grants remain.
2368
+ CREATE OR REPLACE FUNCTION issue_self_user_resource_grant(p_account_id uuid, p_authority_id uuid, p_workspace_id uuid, p_action text, p_mode text, p_context text, p_session_id uuid DEFAULT NULL::uuid, p_workspace_shared_acknowledged boolean DEFAULT false)
2369
+ RETURNS TABLE(grant_id uuid, target_workspace_id uuid, target_session_id uuid, action text, grant_mode text, grant_context text, grant_generation bigint, grant_status text, expires_at timestamp with time zone)
2370
+ LANGUAGE plpgsql
2371
+ SECURITY DEFINER
2372
+ SET search_path FROM CURRENT
2373
+ AS $function$
2374
+ #variable_conflict use_column
2375
+ DECLARE
2376
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
2377
+ owner_membership_id uuid;
2378
+ target_epoch integer;
2379
+ target_visibility text;
2380
+ normalized_action text := lower(btrim(p_action));
2381
+ BEGIN
2382
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
2383
+ OR caller_subject IS NULL
2384
+ THEN
2385
+ RAISE EXCEPTION 'user-resource grant scope mismatch' USING ERRCODE = '42501';
2386
+ END IF;
2387
+ IF p_mode NOT IN ('once', 'session', 'always')
2388
+ OR p_context NOT IN ('user_private', 'workspace_shared')
2389
+ OR normalized_action = 'connection.use' OR normalized_action = '' OR length(normalized_action) > 64
2390
+ OR normalized_action !~ '^[a-z0-9](?:[a-z0-9._:-]*[a-z0-9])?$'
2391
+ THEN
2392
+ RAISE EXCEPTION 'invalid user-resource grant request' USING ERRCODE = '22023';
2393
+ END IF;
2394
+ IF p_context = 'workspace_shared' AND NOT p_workspace_shared_acknowledged THEN
2395
+ RAISE EXCEPTION 'workspace_shared requires durable shared-output acknowledgement'
2396
+ USING ERRCODE = '42501';
2397
+ END IF;
2398
+ IF (p_mode = 'always' AND p_session_id IS NOT NULL)
2399
+ OR (p_mode IN ('once', 'session') AND p_session_id IS NULL)
2400
+ THEN
2401
+ RAISE EXCEPTION 'user-resource grant session fence is invalid' USING ERRCODE = '22023';
2402
+ END IF;
2403
+
2404
+ SELECT membership.id INTO STRICT owner_membership_id
2405
+ FROM organization_memberships membership
2406
+ WHERE membership.account_id = p_account_id
2407
+ AND membership.subject_id = caller_subject
2408
+ AND membership.status = 'active'
2409
+ AND membership.revoked_at IS NULL
2410
+ FOR SHARE;
2411
+
2412
+ PERFORM 1 FROM organization_user_resource_authorities authority
2413
+ WHERE authority.id = p_authority_id
2414
+ AND authority.account_id = p_account_id
2415
+ AND authority.organization_membership_id = owner_membership_id
2416
+ AND authority.status = 'active'
2417
+ AND authority.revoked_at IS NULL
2418
+ FOR UPDATE;
2419
+ IF NOT FOUND THEN
2420
+ RAISE EXCEPTION 'active self-owned user-resource authority required' USING ERRCODE = '42501';
2421
+ END IF;
2422
+
2423
+ PERFORM 1 FROM workspaces workspace_value
2424
+ WHERE workspace_value.id = p_workspace_id AND workspace_value.account_id = p_account_id
2425
+ FOR SHARE;
2426
+ IF NOT FOUND THEN
2427
+ RAISE EXCEPTION 'target workspace is outside the organization' USING ERRCODE = '42501';
2428
+ END IF;
2429
+ PERFORM 1 FROM organization_memberships membership
2430
+ WHERE membership.id = owner_membership_id
2431
+ AND (membership.personal_workspace_id = p_workspace_id OR EXISTS (
2432
+ SELECT 1 FROM workspace_memberships workspace_membership
2433
+ WHERE workspace_membership.account_id = p_account_id
2434
+ AND workspace_membership.workspace_id = p_workspace_id
2435
+ AND workspace_membership.subject_id = caller_subject
2436
+ ));
2437
+ IF NOT FOUND THEN
2438
+ RAISE EXCEPTION 'owner lacks current target-workspace access' USING ERRCODE = '42501';
2439
+ END IF;
2440
+
2441
+ IF p_session_id IS NOT NULL THEN
2442
+ SELECT session_value.authority_epoch, session_value.visibility
2443
+ INTO STRICT target_epoch, target_visibility
2444
+ FROM sessions session_value
2445
+ WHERE session_value.id = p_session_id
2446
+ AND session_value.account_id = p_account_id
2447
+ AND session_value.workspace_id = p_workspace_id
2448
+ AND session_value.status <> 'cancelled'
2449
+ FOR SHARE;
2450
+ IF target_visibility IS DISTINCT FROM p_context THEN
2451
+ RAISE EXCEPTION 'grant context does not match current session visibility'
2452
+ USING ERRCODE = '42501';
2453
+ END IF;
2454
+ END IF;
2455
+
2456
+ INSERT INTO organization_user_resource_grants (
2457
+ account_id, authority_id, owner_organization_membership_id, workspace_id,
2458
+ session_id, action, mode, context, authority_epoch, generation, status
2459
+ ) VALUES (
2460
+ p_account_id, p_authority_id, owner_membership_id, p_workspace_id,
2461
+ p_session_id, normalized_action, p_mode, p_context, target_epoch, 1, 'active'
2462
+ )
2463
+ ON CONFLICT (account_id, authority_id, workspace_id, action, mode, context,
2464
+ (coalesce(session_id, '00000000-0000-0000-0000-000000000000'::uuid)),
2465
+ (coalesce(authority_epoch, 0))) WHERE status = 'active'
2466
+ DO UPDATE SET updated_at = organization_user_resource_grants.updated_at
2467
+ RETURNING id, workspace_id, session_id, organization_user_resource_grants.action,
2468
+ mode, context, generation, status, organization_user_resource_grants.expires_at
2469
+ INTO grant_id, target_workspace_id, target_session_id, action, grant_mode,
2470
+ grant_context, grant_generation, grant_status, expires_at;
2471
+ RETURN NEXT;
2472
+ END
2473
+ $function$
2474
+ ;
2475
+
2476
+ CREATE OR REPLACE FUNCTION issue_self_user_resource_grant(p_account_id uuid, p_authority_id uuid, p_workspace_id uuid, p_resource_kind text, p_mode text, p_context text, p_session_id uuid DEFAULT NULL::uuid, p_expected_authority_epoch integer DEFAULT NULL::integer, p_workspace_shared_acknowledged boolean DEFAULT false)
2477
+ RETURNS TABLE(grant_id uuid, organization_id uuid, authority_generation bigint, target_workspace_id uuid, target_session_id uuid, action text, grant_mode text, grant_context text, authority_epoch integer, grant_generation bigint, grant_status text, expires_at timestamp with time zone)
2478
+ LANGUAGE plpgsql
2479
+ SECURITY DEFINER
2480
+ SET search_path FROM CURRENT
2481
+ AS $function$
2482
+ #variable_conflict use_column
2483
+ DECLARE
2484
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
2485
+ owner_membership_id uuid;
2486
+ target_epoch integer;
2487
+ target_visibility text;
2488
+ canonical_action text;
2489
+ BEGIN
2490
+ PERFORM set_config(
2491
+ 'opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true
2492
+ );
2493
+ IF p_account_id IS DISTINCT FROM nullif(current_setting('opengeni.account_id', true), '')::uuid
2494
+ OR p_workspace_id IS DISTINCT FROM nullif(
2495
+ current_setting('opengeni.workspace_id', true), ''
2496
+ )::uuid
2497
+ OR caller_subject IS NULL
2498
+ THEN
2499
+ RAISE EXCEPTION 'user-resource grant scope mismatch' USING ERRCODE = '42501';
2500
+ END IF;
2501
+ canonical_action := CASE p_resource_kind
2502
+ WHEN 'document' THEN 'document.read'
2503
+ WHEN 'variable_set' THEN 'variable_set.use'
2504
+ WHEN 'rig' THEN 'rig.use'
2505
+ WHEN 'connected_machine' THEN 'connected_machine.use'
2506
+ ELSE NULL
2507
+ END;
2508
+ IF p_resource_kind IS NULL OR canonical_action IS NULL
2509
+ OR p_mode IS NULL OR p_mode NOT IN ('session', 'always')
2510
+ OR p_context IS NULL OR p_context NOT IN ('user_private', 'workspace_shared')
2511
+ OR (p_mode = 'always' AND (p_session_id IS NOT NULL OR p_expected_authority_epoch IS NOT NULL))
2512
+ OR (p_mode = 'session' AND (p_session_id IS NULL OR p_expected_authority_epoch IS NULL))
2513
+ OR coalesce(p_expected_authority_epoch, 1) <= 0
2514
+ THEN
2515
+ RAISE EXCEPTION 'invalid user-resource grant request' USING ERRCODE = '22023';
2516
+ END IF;
2517
+ IF p_context = 'workspace_shared' AND p_workspace_shared_acknowledged IS NOT TRUE THEN
2518
+ RAISE EXCEPTION 'workspace_shared requires durable shared-output acknowledgement'
2519
+ USING ERRCODE = '42501';
2520
+ END IF;
2521
+ IF NOT EXISTS (
2522
+ SELECT 1 FROM session_tenancy_activations activation
2523
+ WHERE activation.account_id = p_account_id AND activation.activation_version = 1
2524
+ ) THEN
2525
+ RAISE EXCEPTION 'session tenancy product is not activated' USING ERRCODE = '42501';
2526
+ END IF;
2527
+
2528
+ SELECT membership.id INTO owner_membership_id
2529
+ FROM organization_memberships membership
2530
+ WHERE membership.account_id = p_account_id
2531
+ AND membership.subject_id = caller_subject
2532
+ AND membership.status = 'active'
2533
+ AND membership.revoked_at IS NULL
2534
+ FOR SHARE;
2535
+ IF NOT FOUND THEN
2536
+ RAISE EXCEPTION 'owner membership not found or access denied' USING ERRCODE = '42501';
2537
+ END IF;
2538
+
2539
+ SELECT authority.generation INTO authority_generation
2540
+ FROM organization_user_resource_authorities authority
2541
+ WHERE authority.id = p_authority_id
2542
+ AND authority.account_id = p_account_id
2543
+ AND authority.organization_membership_id = owner_membership_id
2544
+ AND authority.resource_kind = p_resource_kind
2545
+ AND authority.status = 'active'
2546
+ AND authority.revoked_at IS NULL
2547
+ FOR UPDATE;
2548
+ IF NOT FOUND THEN
2549
+ RAISE EXCEPTION 'user-resource authority not found or access denied' USING ERRCODE = '42501';
2550
+ END IF;
2551
+
2552
+ PERFORM 1 FROM workspaces workspace_value
2553
+ WHERE workspace_value.id = p_workspace_id AND workspace_value.account_id = p_account_id
2554
+ FOR SHARE;
2555
+ IF NOT FOUND OR NOT EXISTS (
2556
+ SELECT 1 FROM organization_memberships membership
2557
+ WHERE membership.id = owner_membership_id
2558
+ AND (
2559
+ membership.personal_workspace_id = p_workspace_id
2560
+ OR EXISTS (
2561
+ SELECT 1 FROM workspace_memberships workspace_membership
2562
+ WHERE workspace_membership.account_id = p_account_id
2563
+ AND workspace_membership.workspace_id = p_workspace_id
2564
+ AND workspace_membership.subject_id = caller_subject
2565
+ )
2566
+ )
2567
+ ) THEN
2568
+ RAISE EXCEPTION 'owner lacks current target-workspace access' USING ERRCODE = '42501';
2569
+ END IF;
2570
+
2571
+ IF p_session_id IS NOT NULL THEN
2572
+ SELECT session_value.authority_epoch, session_value.visibility
2573
+ INTO target_epoch, target_visibility
2574
+ FROM sessions session_value
2575
+ WHERE session_value.id = p_session_id
2576
+ AND session_value.account_id = p_account_id
2577
+ AND session_value.workspace_id = p_workspace_id
2578
+ AND session_value.status <> 'cancelled'
2579
+ FOR SHARE;
2580
+ IF NOT FOUND
2581
+ OR target_visibility IS DISTINCT FROM p_context
2582
+ OR target_epoch IS DISTINCT FROM p_expected_authority_epoch
2583
+ THEN
2584
+ RAISE EXCEPTION 'target session not found or access denied' USING ERRCODE = '42501';
2585
+ END IF;
2586
+ END IF;
2587
+
2588
+ UPDATE organization_user_resource_grants grant_value
2589
+ SET status = 'expired', updated_at = clock_timestamp()
2590
+ WHERE grant_value.account_id = p_account_id
2591
+ AND grant_value.authority_id = p_authority_id
2592
+ AND grant_value.status = 'active'
2593
+ AND grant_value.expires_at IS NOT NULL
2594
+ AND grant_value.expires_at <= clock_timestamp();
2595
+
2596
+ INSERT INTO organization_user_resource_grants (
2597
+ account_id, authority_id, owner_organization_membership_id, workspace_id,
2598
+ session_id, action, mode, context, authority_epoch, generation, status
2599
+ ) VALUES (
2600
+ p_account_id, p_authority_id, owner_membership_id, p_workspace_id,
2601
+ p_session_id, canonical_action, p_mode, p_context, target_epoch, 1, 'active'
2602
+ )
2603
+ ON CONFLICT (account_id, authority_id, workspace_id, action, mode, context,
2604
+ (coalesce(session_id, '00000000-0000-0000-0000-000000000000'::uuid)),
2605
+ (coalesce(authority_epoch, 0))) WHERE status = 'active'
2606
+ DO UPDATE SET updated_at = organization_user_resource_grants.updated_at
2607
+ RETURNING id, workspace_id, session_id, organization_user_resource_grants.action,
2608
+ mode, context, organization_user_resource_grants.authority_epoch, generation,
2609
+ status, organization_user_resource_grants.expires_at
2610
+ INTO grant_id, target_workspace_id, target_session_id, action, grant_mode,
2611
+ grant_context, authority_epoch, grant_generation, grant_status, expires_at;
2612
+ organization_id := p_account_id;
2613
+ RETURN NEXT;
2614
+ END
2615
+ $function$
2616
+ ;
2617
+
2618
+
2619
+ -- No callable native consent or task-head connection-grant compatibility lane.
2620
+ DROP FUNCTION issue_self_local_connection_use_grant(uuid,uuid,uuid,text,boolean);
2621
+ DROP FUNCTION list_self_connection_authorities(uuid);
2622
+ DROP FUNCTION issue_self_connection_use_grant(uuid,uuid,uuid,text,text,uuid,boolean);
2623
+ DROP FUNCTION revoke_self_connection_use_grant(uuid,uuid);
2624
+ DROP FUNCTION resolve_connection_use_authority(uuid,uuid,uuid,jsonb);
2625
+ DROP FUNCTION resolve_personal_connection_authority_selection(uuid,uuid,text,uuid,jsonb);
2626
+ DROP FUNCTION refresh_scheduled_task_personal_resources_clone_connections(uuid,uuid,uuid,bigint,bigint);
2627
+ DROP FUNCTION freeze_scheduled_task_connection_authorities_inner(uuid,uuid,uuid,bigint);
2628
+ DROP FUNCTION clone_scheduled_task_connection_authorities_inner(uuid,uuid,uuid,bigint,bigint);
2629
+ DROP FUNCTION freeze_scheduled_task_personal_resources_0252(uuid,uuid,uuid,bigint);
2630
+ DROP FUNCTION clone_scheduled_task_personal_resource_authority_0252(uuid,uuid,uuid,bigint,bigint);
2631
+
2632
+ -- Task-head connection snapshots are audit history only. Ownership and each
2633
+ -- accepted occurrence replace their old revision/grant execution gates.
2634
+ DROP TRIGGER scheduled_task_connection_authority_execution_revision ON scheduled_tasks;
2635
+ DROP FUNCTION fence_scheduled_task_connection_authority_execution_update();
2636
+ COMMENT ON COLUMN scheduled_tasks.personal_connection_delegations IS
2637
+ 'Historical account/grant selections. Never read or written by current execution; current account choices live in agent_config.';
2638
+ CREATE OR REPLACE FUNCTION record_scheduled_task_revision_authority(p_account_id uuid, p_workspace_id uuid, p_task_id uuid, p_task_authority_revision bigint)
2639
+ RETURNS text
2640
+ LANGUAGE plpgsql
2641
+ SECURITY DEFINER
2642
+ SET search_path FROM CURRENT
2643
+ AS $function$
2644
+ DECLARE
2645
+ sender_prior_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
2646
+ subject_value text := nullif(btrim(
2647
+ current_setting('opengeni.initiating_human_subject_id', true)
2648
+ ), '');
2649
+ task_row record;
2650
+ membership_row record;
2651
+ resource_bearing boolean;
2652
+ BEGIN
2653
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true);
2654
+ IF p_account_id IS DISTINCT FROM nullif(
2655
+ current_setting('opengeni.account_id', true), ''
2656
+ )::uuid
2657
+ OR p_workspace_id IS DISTINCT FROM nullif(
2658
+ current_setting('opengeni.workspace_id', true), ''
2659
+ )::uuid
2660
+ THEN RAISE EXCEPTION 'scheduled revision authority scope mismatch'
2661
+ USING ERRCODE = '42501'; END IF;
2662
+ SELECT task.* INTO STRICT task_row
2663
+ FROM scheduled_tasks task
2664
+ WHERE task.id = p_task_id
2665
+ AND task.account_id = p_account_id
2666
+ AND task.workspace_id = p_workspace_id
2667
+ AND task.authority_revision = p_task_authority_revision
2668
+ FOR SHARE;
2669
+ IF task_row.action ->> 'kind' <> 'agent_turn' THEN PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NULL; END IF;
2670
+ -- Connection identity is the immutable task owner and is resolved per run.
2671
+ -- This revision receipt belongs only to retained native personal resources;
2672
+ -- verified host identities need not have a native organization membership.
2673
+ resource_bearing := EXISTS (
2674
+ SELECT 1 FROM workspace_variable_sets variable_set
2675
+ WHERE variable_set.id = task_row.variable_set_id
2676
+ AND variable_set.account_id = task_row.account_id
2677
+ AND variable_set.authority_scope = 'user'
2678
+ )
2679
+ OR EXISTS (
2680
+ SELECT 1 FROM rigs rig
2681
+ WHERE rig.id = task_row.rig_id
2682
+ AND rig.account_id = task_row.account_id
2683
+ AND rig.authority_scope = 'user'
2684
+ )
2685
+ OR task_row.xai_provider_account_authority_snapshot ->> 'scope' = 'user'
2686
+ OR EXISTS (
2687
+ SELECT 1 FROM scheduled_task_personal_resource_authorities authority
2688
+ WHERE authority.task_id = task_row.id
2689
+ AND authority.task_authority_revision = task_row.authority_revision
2690
+ );
2691
+ -- A service/API-key/delegated writer is not a managed human and cannot be a
2692
+ -- revision authorizer. That is allowed for a task that delegates no personal
2693
+ -- authority (workspace/organization Variable Sets and Rigs use ordinary
2694
+ -- workspace authority); a personal-resource task fails closed instead of
2695
+ -- running without a causal human.
2696
+ IF subject_value IS NULL OR NOT EXISTS (
2697
+ SELECT 1 FROM organization_memberships membership
2698
+ WHERE membership.account_id = p_account_id
2699
+ AND membership.subject_id = subject_value
2700
+ ) THEN
2701
+ IF resource_bearing THEN
2702
+ RAISE EXCEPTION
2703
+ 'scheduled personal-resource agent task requires an exact human revision authorizer'
2704
+ USING ERRCODE = '42501';
2705
+ END IF;
2706
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN NULL;
2707
+ END IF;
2708
+ SELECT membership.* INTO membership_row
2709
+ FROM organization_memberships membership
2710
+ WHERE membership.account_id = p_account_id
2711
+ AND membership.subject_id = subject_value
2712
+ AND membership.status = 'active'
2713
+ AND membership.revoked_at IS NULL
2714
+ AND (
2715
+ membership.personal_workspace_id = p_workspace_id
2716
+ OR EXISTS (
2717
+ SELECT 1 FROM workspace_memberships workspace_membership
2718
+ WHERE workspace_membership.account_id = p_account_id
2719
+ AND workspace_membership.workspace_id = p_workspace_id
2720
+ AND workspace_membership.subject_id = subject_value
2721
+ )
2722
+ )
2723
+ FOR SHARE;
2724
+ IF NOT FOUND THEN
2725
+ RAISE EXCEPTION 'scheduled revision authorizer lacks active workspace membership'
2726
+ USING ERRCODE = '42501';
2727
+ END IF;
2728
+ INSERT INTO scheduled_task_revision_authorities (
2729
+ task_id, task_authority_revision, account_id, workspace_id, subject_id,
2730
+ organization_membership_id, membership_authorization_revision, execution_digest
2731
+ ) VALUES (
2732
+ task_row.id, task_row.authority_revision, task_row.account_id,
2733
+ task_row.workspace_id, subject_value, membership_row.id,
2734
+ membership_row.authorization_revision, task_row.execution_digest
2735
+ );
2736
+ PERFORM set_config('opengeni.organization_tenancy_lifecycle', coalesce(sender_prior_lifecycle, ''), true); RETURN subject_value;
2737
+ EXCEPTION WHEN OTHERS THEN RAISE;
2738
+ END
2739
+ $function$
2740
+ ;
2741
+
2742
+ -- Replacing a routine must retain its trusted data schema ahead of pg_temp.
2743
+ DO $sender_routine_paths$
2744
+ DECLARE data_schema text := current_schema(); routine record;
2745
+ BEGIN
2746
+ FOR routine IN
2747
+ SELECT n.nspname, p.proname, pg_get_function_identity_arguments(p.oid) AS arguments
2748
+ FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace
2749
+ WHERE (CASE WHEN n.nspname = data_schema THEN p.proname
2750
+ ELSE n.nspname || '.' || p.proname END) = ANY (ARRAY[
2751
+ 'opengeni_private.guard_scheduled_task_owner',
2752
+ 'opengeni_private.guard_scheduled_run_owner',
2753
+ 'opengeni_private.read_sender_connection',
2754
+ 'opengeni_private.capture_accepted_turn_connection_authorities',
2755
+ 'resolve_accepted_connection_use',
2756
+ 'list_owned_connection_accounts',
2757
+ 'admit_scheduled_task_run_connection_authorities',
2758
+ 'admit_scheduled_agent_run_execution',
2759
+ 'bind_scheduled_task_run_connection_authorities',
2760
+ 'validate_scheduled_agent_run_live_authority',
2761
+ 'opengeni_private.capture_scheduled_turn_connection_authorities',
2762
+ 'freeze_scheduled_task_personal_resources',
2763
+ 'clone_scheduled_task_personal_resource_authority',
2764
+ 'issue_self_user_resource_grant',
2765
+ 'record_scheduled_task_revision_authority'
2766
+ ]) AND n.nspname IN (data_schema, 'opengeni_private')
2767
+ LOOP
2768
+ EXECUTE format('ALTER FUNCTION %I.%I(%s) SET search_path = pg_catalog, %I, pg_temp',
2769
+ routine.nspname, routine.proname, routine.arguments, data_schema);
2770
+ END LOOP;
2771
+ END
2772
+ $sender_routine_paths$;