@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.
- package/dist/automations.d.ts +0 -2
- package/dist/canonical-human-identities.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-components.d.ts +5 -13
- package/dist/capability-integrations.d.ts +23 -8
- package/dist/capability-reconciliation.d.ts +5 -0
- package/dist/capability-reconciliation.js +21 -0
- package/dist/capability-reconciliation.js.map +1 -0
- package/dist/{chunk-6T2GGYWE.js → chunk-4UJCXBR2.js} +4 -5800
- package/dist/chunk-4UJCXBR2.js.map +1 -0
- package/dist/{chunk-MWMXWHJD.js → chunk-7JCDSMX2.js} +2 -2
- package/dist/chunk-AJA2ZIUW.js +219 -0
- package/dist/chunk-AJA2ZIUW.js.map +1 -0
- package/dist/chunk-CDAT6EKW.js +247 -0
- package/dist/chunk-CDAT6EKW.js.map +1 -0
- package/dist/{chunk-DPONPYJD.js → chunk-CJI6G254.js} +70 -18
- package/dist/chunk-CJI6G254.js.map +1 -0
- package/dist/chunk-G32NVMRJ.js +276 -0
- package/dist/chunk-G32NVMRJ.js.map +1 -0
- package/dist/chunk-GABXCG3N.js +5811 -0
- package/dist/chunk-GABXCG3N.js.map +1 -0
- package/dist/{chunk-ERKVSPDX.js → chunk-LVY33EWL.js} +3 -3
- package/dist/{chunk-45CP2YKY.js → chunk-MAMBI2HR.js} +9 -3
- package/dist/{chunk-45CP2YKY.js.map → chunk-MAMBI2HR.js.map} +1 -1
- package/dist/{chunk-F73UKJY6.js → chunk-MN5PZMEK.js} +91 -108
- package/dist/chunk-MN5PZMEK.js.map +1 -0
- package/dist/chunk-PJI4MUZ7.js +131 -0
- package/dist/chunk-PJI4MUZ7.js.map +1 -0
- package/dist/{chunk-T6BWIGWJ.js → chunk-RIYHP2WU.js} +2 -2
- package/dist/{chunk-7HMK3B45.js → chunk-RL2MUXM3.js} +16 -7
- package/dist/chunk-RL2MUXM3.js.map +1 -0
- package/dist/{chunk-HQDVI2DB.js → chunk-SYKHLPTK.js} +4 -3
- package/dist/{chunk-HQDVI2DB.js.map → chunk-SYKHLPTK.js.map} +1 -1
- package/dist/{chunk-T6YLHJLS.js → chunk-XIXVVBW2.js} +198 -51
- package/dist/chunk-XIXVVBW2.js.map +1 -0
- package/dist/chunk-ZPJ6V2VG.js +156 -0
- package/dist/chunk-ZPJ6V2VG.js.map +1 -0
- package/dist/codex-capacity-recovery.d.ts +12 -0
- package/dist/codex-failure-eligibility.d.ts +9 -0
- package/dist/company-profile-schema.d.ts +4 -2
- package/dist/connect-attempts.d.ts +15 -0
- package/dist/connect-attempts.js +25 -0
- package/dist/connect-attempts.js.map +1 -0
- package/dist/connection-authority.d.ts +7 -63
- package/dist/connection-refresh-lock.d.ts +4 -0
- package/dist/connection-token-resolver.d.ts +8 -43
- package/dist/database-timing.d.ts +10 -0
- package/dist/database.d.ts +4 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.d.ts +9 -0
- package/dist/editable-artifacts.js +7 -5
- package/dist/index.d.ts +447 -78
- package/dist/index.js +12705 -12872
- package/dist/index.js.map +1 -1
- package/dist/insights-usage-bundle.d.ts +3 -0
- package/dist/integration-bindings.d.ts +1 -1
- package/dist/integration-facets.d.ts +18 -1
- package/dist/knowledge-entries-schema.d.ts +2 -0
- package/dist/knowledge-entries.d.ts +1 -0
- package/dist/knowledge-indexing.d.ts +1 -0
- package/dist/live-session-attempt.d.ts +35 -137
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-account-bindings.d.ts +4 -0
- package/dist/mcp-oauth.d.ts +11 -0
- package/dist/mcp-operations.js +2 -2
- package/dist/migrate.js +1 -1
- package/dist/organization-integration-policy.d.ts +51 -0
- package/dist/organization-integration-policy.js +18 -0
- package/dist/organization-integration-policy.js.map +1 -0
- package/dist/organization-usage.d.ts +20 -0
- package/dist/plugin-packages.d.ts +9 -12
- package/dist/pr-review.d.ts +0 -11
- package/dist/preference-registry-schema.d.ts +17 -0
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.d.ts +124 -6
- package/dist/retained-provider-commands.js +29 -97
- package/dist/retained-provider-commands.js.map +1 -1
- package/dist/retired-session-create-metadata.d.ts +4 -0
- package/dist/runtime-posture.d.ts +7 -5
- package/dist/schema.d.ts +876 -656
- package/dist/schema.js +5 -7
- package/dist/session-admission-block.d.ts +32 -0
- package/dist/session-attempt-writers.d.ts +4 -0
- package/dist/session-background-commands.js +5 -4
- package/dist/session-command-output.js +8 -126
- package/dist/session-command-output.js.map +1 -1
- package/dist/session-control.d.ts +5 -1
- package/dist/session-event-slices.d.ts +10 -0
- package/dist/session-event-slices.js +8 -222
- package/dist/session-event-slices.js.map +1 -1
- package/dist/session-file-attachments.d.ts +28 -0
- package/dist/session-goal-reports.d.ts +43 -0
- package/dist/session-mcp-approval.d.ts +4 -0
- package/dist/session-mcp-credential-rotation.js +5 -4
- package/dist/session-mcp-credential-rotation.js.map +1 -1
- package/dist/session-message-search.d.ts +22 -0
- package/dist/session-queue-commands.d.ts +2 -6
- package/dist/session-retry.d.ts +36 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/skill-source-release-impact.d.ts +18 -0
- package/dist/skill-source-release.d.ts +9 -0
- package/dist/skills.d.ts +7 -1
- package/dist/user-resource-authority.d.ts +0 -8
- package/dist/video-generation.js +3 -3
- package/dist/workspace-tool-defaults.js +5 -4
- package/dist/workspace-tool-defaults.js.map +1 -1
- package/drizzle/0468_knowledge_relationship_projection.sql +30 -0
- package/drizzle/0469_knowledge_source_discovery.sql +85 -0
- package/drizzle/0470_organization_integration_policy.sql +151 -0
- package/drizzle/0471_usage_events_account_recent_index.sql +4 -0
- package/drizzle/0472_usage_events_workspace_recent_index.sql +4 -0
- package/drizzle/0473_organization_usage_analytical_capability.sql +237 -0
- package/drizzle/0474_goal_report_requirements.sql +156 -0
- package/drizzle/0475_session_event_history_statistics.sql +15 -0
- package/drizzle/0476_session_event_history_policy_planning.sql +29 -0
- package/drizzle/0477_managed_sign_in_methods.sql +303 -0
- package/drizzle/0478_sender_owned_connections.sql +2772 -0
- package/drizzle/0479_connection_create_idempotency.sql +16 -0
- package/drizzle/0480_private_session_organization_key_administration.sql +154 -0
- package/drizzle/0481_personal_connection_visibility_cleanup.sql +45 -0
- package/drizzle/0482_remove_packs.sql +417 -0
- package/drizzle/0483_preclaim_admission_block.sql +85 -0
- package/drizzle/0484_insights_usage_projection.sql +211 -0
- package/drizzle/0485_inherited_mcp_approval_policies.sql +6 -0
- package/drizzle/0486_reasoning_configuration_message_forks.sql +35 -0
- package/drizzle/0487_skill_catalog_message_forks.sql +26 -0
- package/drizzle/0488_permanent_skill_removal.sql +207 -0
- package/drizzle/0489_modal_command_byte_offsets.sql +17 -0
- package/drizzle/0490_periodic_capture_attempt_cadence.sql +6 -0
- package/drizzle/0491_warm_capture_holder_reclamation.sql +57 -0
- package/drizzle/0492_codex_accepted_source_authority.sql +249 -0
- package/drizzle/0493_stopping_command_error_containment.sql +58 -0
- package/drizzle/0494_mcp_account_bindings.sql +334 -0
- package/drizzle/0495_consented_sandbox_recovery.sql +220 -0
- package/drizzle/0496_supervised_command_settlement.sql +386 -0
- package/drizzle/0497_modal_inventory_owner_read_capability.sql +112 -0
- package/drizzle/0498_enrollment_membership_fence.sql +87 -0
- package/drizzle/0499_session_attachment_access.sql +224 -0
- package/drizzle/0500_session_execution_epoch.sql +37 -0
- package/drizzle/0501_session_sharing_execution.sql +591 -0
- package/drizzle/0502_active_message_boundary_forks.sql +30 -0
- package/package.json +17 -5
- package/src/automations.ts +17 -73
- package/src/canonical-human-identities.ts +17 -5
- package/src/capability-components.ts +7 -24
- package/src/capability-integrations.ts +301 -21
- package/src/capability-reconciliation.ts +19 -0
- package/src/codex-capacity-recovery.ts +47 -0
- package/src/codex-failure-eligibility.ts +93 -0
- package/src/connect-attempts.ts +49 -18
- package/src/connection-authority.ts +12 -158
- package/src/connection-refresh-lock.ts +22 -0
- package/src/connection-token-resolver.ts +58 -530
- package/src/database-timing.ts +43 -0
- package/src/database.ts +61 -26
- package/src/editable-artifacts.ts +1 -1
- package/src/index.ts +3578 -1273
- package/src/insights-model-bundle.ts +19 -7
- package/src/insights-usage-bundle.ts +16 -19
- package/src/integration-bindings.ts +2 -7
- package/src/integration-facets.ts +73 -6
- package/src/knowledge-entries.ts +8 -1
- package/src/live-session-attempt.ts +0 -22
- package/src/mcp-account-bindings.ts +12 -0
- package/src/mcp-oauth.ts +26 -0
- package/src/organization-integration-policy.ts +235 -0
- package/src/organization-usage.ts +83 -0
- package/src/plugin-packages.ts +226 -42
- package/src/pr-review.ts +7 -69
- package/src/preference-registry-schema.ts +1 -0
- package/src/provision-roles.ts +50 -37
- package/src/retained-provider-commands.ts +286 -9
- package/src/retired-session-create-metadata.ts +16 -0
- package/src/runtime-posture.ts +217 -13
- package/src/schema.ts +104 -120
- package/src/session-admission-block.ts +29 -0
- package/src/session-attempt-writers.ts +48 -0
- package/src/session-control.ts +21 -5
- package/src/session-event-slices.ts +76 -26
- package/src/session-file-attachments.ts +66 -0
- package/src/session-goal-reports.ts +255 -0
- package/src/session-mcp-approval.ts +30 -0
- package/src/session-message-search.ts +348 -0
- package/src/session-queue-commands.ts +60 -36
- package/src/session-retry.ts +319 -0
- package/src/skill-config-migration.ts +11 -2
- package/src/skill-source-release-impact.ts +33 -0
- package/src/skill-source-release.ts +67 -26
- package/src/skills.ts +44 -6
- package/src/user-resource-authority.ts +0 -56
- package/dist/chunk-6T2GGYWE.js.map +0 -1
- package/dist/chunk-7HMK3B45.js.map +0 -1
- package/dist/chunk-DPONPYJD.js.map +0 -1
- package/dist/chunk-F73UKJY6.js.map +0 -1
- package/dist/chunk-T6YLHJLS.js.map +0 -1
- package/dist/host-mcp-binding-match.d.ts +0 -5
- package/dist/host-mcp-bindings.d.ts +0 -79
- package/dist/host-mcp-resolvers.d.ts +0 -47
- package/dist/host-mcp-task-authority.d.ts +0 -43
- package/dist/host-selection-identity.d.ts +0 -6
- package/dist/pack-components.d.ts +0 -83
- package/dist/pack-installations.d.ts +0 -86
- package/src/host-mcp-binding-match.ts +0 -34
- package/src/host-mcp-bindings.ts +0 -777
- package/src/host-mcp-resolvers.ts +0 -266
- package/src/host-mcp-task-authority.ts +0 -384
- package/src/host-selection-identity.ts +0 -29
- package/src/pack-components.ts +0 -980
- package/src/pack-installations.ts +0 -950
- /package/dist/{chunk-MWMXWHJD.js.map → chunk-7JCDSMX2.js.map} +0 -0
- /package/dist/{chunk-ERKVSPDX.js.map → chunk-LVY33EWL.js.map} +0 -0
- /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$;
|