@opengeni/db 3.7.4 → 4.0.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/automations.d.ts +4 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/capability-integrations.d.ts +2 -0
  4. package/dist/{chunk-6DPJA36C.js → chunk-2XEZK4LF.js} +7 -1
  5. package/dist/chunk-2XEZK4LF.js.map +1 -0
  6. package/dist/{chunk-4NAR7FBA.js → chunk-3RI33YQN.js} +67 -1
  7. package/dist/chunk-3RI33YQN.js.map +1 -0
  8. package/dist/{chunk-SZAWDXQC.js → chunk-CJROYHSC.js} +3 -3
  9. package/dist/{chunk-DZCYW2WT.js → chunk-MX6V5F65.js} +2 -2
  10. package/dist/{chunk-S3TDDCS2.js → chunk-N5VXJ4I2.js} +151 -78
  11. package/dist/chunk-N5VXJ4I2.js.map +1 -0
  12. package/dist/{chunk-U4WPPEBH.js → chunk-NLFHMPUB.js} +2 -2
  13. package/dist/{chunk-5NNCGFQY.js → chunk-P6NSEG2S.js} +2 -2
  14. package/dist/{chunk-O56DM5GZ.js → chunk-QM6YOWHZ.js} +624 -57
  15. package/dist/chunk-QM6YOWHZ.js.map +1 -0
  16. package/dist/{chunk-US4IS6IV.js → chunk-RZNOSEMC.js} +2 -2
  17. package/dist/{chunk-F6PBS6WE.js → chunk-UFAMOUWL.js} +2 -2
  18. package/dist/codemode-operations.d.ts +5 -0
  19. package/dist/codex-token-resolver.d.ts +6 -2
  20. package/dist/connection-token-resolver.d.ts +8 -0
  21. package/dist/editable-artifact-durable-export.js +2 -2
  22. package/dist/editable-artifacts.js +3 -3
  23. package/dist/generated-images.d.ts +15 -0
  24. package/dist/index.d.ts +393 -91
  25. package/dist/index.js +9992 -6069
  26. package/dist/index.js.map +1 -1
  27. package/dist/insights.d.ts +9 -0
  28. package/dist/managed-auth-session-sets.js +3 -3
  29. package/dist/managed-user-setup.d.ts +3 -0
  30. package/dist/mcp-oauth.d.ts +80 -0
  31. package/dist/migrate.d.ts +1 -0
  32. package/dist/migrate.js +3 -1
  33. package/dist/model-catalog.d.ts +153 -0
  34. package/dist/model-context-snapshots.d.ts +20 -0
  35. package/dist/new-session-drafts.d.ts +12 -0
  36. package/dist/organization-membership-lifecycle.d.ts +8 -1
  37. package/dist/organization-model-providers.d.ts +101 -0
  38. package/dist/pack-components.d.ts +1 -0
  39. package/dist/pr-review.d.ts +9 -0
  40. package/dist/provision-roles.js +1 -1
  41. package/dist/runtime-posture.d.ts +8 -8
  42. package/dist/schema.d.ts +7708 -5050
  43. package/dist/schema.js +25 -1
  44. package/dist/session-background-commands.d.ts +18 -6
  45. package/dist/session-background-commands.js +15 -9
  46. package/dist/session-control.d.ts +37 -2
  47. package/dist/session-goal-pacing.d.ts +3 -6
  48. package/dist/session-queue-commands.d.ts +17 -0
  49. package/dist/session-tenancy.js +3 -3
  50. package/dist/tool-gateway-approvals.d.ts +31 -0
  51. package/dist/video-generation.js +3 -3
  52. package/dist/workspace-artifacts.d.ts +17 -3
  53. package/dist/xai-subscription.d.ts +8 -0
  54. package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
  55. package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
  56. package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
  57. package/drizzle/0390_organization_model_provider_connections.sql +197 -0
  58. package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
  59. package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
  60. package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
  61. package/drizzle/0394_session_selected_skill_activation.sql +20 -0
  62. package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
  63. package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
  64. package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
  65. package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
  66. package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
  67. package/drizzle/0400_session_attempt_model_context_snapshots.sql +65 -0
  68. package/drizzle/0401_organization_user_setup_token_transport.sql +178 -0
  69. package/drizzle/0402_session_input_wait_and_background_command_results.sql +1023 -0
  70. package/drizzle/0403_codex_unconditional_credential_leasing.sql +181 -0
  71. package/drizzle/0404_mcp_oauth_authorization_server.sql +471 -0
  72. package/drizzle/0405_tool_gateway_approval_capabilities.sql +184 -0
  73. package/drizzle/0406_workspace_html_site_sources.sql +71 -0
  74. package/package.json +6 -6
  75. package/src/automations.ts +9 -1
  76. package/src/capability-integrations.ts +8 -0
  77. package/src/codemode-operations.ts +31 -21
  78. package/src/codex-token-resolver.ts +16 -6
  79. package/src/connection-token-resolver.ts +52 -3
  80. package/src/generated-images.ts +70 -6
  81. package/src/index.ts +3894 -994
  82. package/src/insights-model-bundle.ts +54 -5
  83. package/src/insights.ts +53 -7
  84. package/src/managed-user-setup.ts +14 -3
  85. package/src/mcp-oauth.ts +488 -0
  86. package/src/migrate.ts +9 -1
  87. package/src/model-catalog.ts +682 -0
  88. package/src/model-context-snapshots.ts +151 -0
  89. package/src/new-session-drafts.ts +66 -21
  90. package/src/organization-membership-lifecycle.ts +47 -5
  91. package/src/organization-model-providers.ts +614 -0
  92. package/src/pack-components.ts +15 -2
  93. package/src/pr-review.ts +19 -0
  94. package/src/provision-roles.ts +4 -0
  95. package/src/runtime-posture.ts +64 -0
  96. package/src/schema.ts +666 -61
  97. package/src/session-background-commands.ts +180 -78
  98. package/src/session-control.ts +181 -12
  99. package/src/session-goal-pacing.ts +4 -8
  100. package/src/session-queue-commands.ts +157 -82
  101. package/src/tool-gateway-approvals.ts +168 -0
  102. package/src/workspace-artifacts.ts +633 -178
  103. package/src/xai-subscription.ts +66 -14
  104. package/dist/chunk-4NAR7FBA.js.map +0 -1
  105. package/dist/chunk-6DPJA36C.js.map +0 -1
  106. package/dist/chunk-O56DM5GZ.js.map +0 -1
  107. package/dist/chunk-S3TDDCS2.js.map +0 -1
  108. /package/dist/{chunk-SZAWDXQC.js.map → chunk-CJROYHSC.js.map} +0 -0
  109. /package/dist/{chunk-DZCYW2WT.js.map → chunk-MX6V5F65.js.map} +0 -0
  110. /package/dist/{chunk-U4WPPEBH.js.map → chunk-NLFHMPUB.js.map} +0 -0
  111. /package/dist/{chunk-5NNCGFQY.js.map → chunk-P6NSEG2S.js.map} +0 -0
  112. /package/dist/{chunk-US4IS6IV.js.map → chunk-RZNOSEMC.js.map} +0 -0
  113. /package/dist/{chunk-F6PBS6WE.js.map → chunk-UFAMOUWL.js.map} +0 -0
@@ -0,0 +1,154 @@
1
+ -- deployment-mode: rolling
2
+ -- Pausing or soft-deleting a scheduled task is a durable first-claim cutoff.
3
+ -- Runs whose scheduler-owned turn already exists remain accepted work and keep
4
+ -- their normal attempt/recovery lifecycle. Every earlier occurrence becomes a
5
+ -- terminal receipt, so a later Resume cannot revive a deposit accepted before
6
+ -- the lifecycle boundary.
7
+
8
+ SET LOCAL lock_timeout = '5s';
9
+ SET LOCAL statement_timeout = '5min';
10
+
11
+ -- Old claimers do not inspect scheduled_task_runs.status after this migration
12
+ -- marks an occurrence terminal. Keep the rolling boundary safe in the database:
13
+ -- every binary must cross this state transition before it can create the
14
+ -- scheduler-owned turn or start provider work.
15
+ CREATE FUNCTION fence_terminal_scheduled_occurrence_delivery()
16
+ RETURNS trigger
17
+ LANGUAGE plpgsql
18
+ SECURITY DEFINER
19
+ SET search_path FROM CURRENT
20
+ AS $body$
21
+ DECLARE
22
+ run_status text;
23
+ BEGIN
24
+ SELECT run.status INTO run_status
25
+ FROM scheduled_task_runs run
26
+ WHERE run.id = OLD.scheduled_task_run_id
27
+ AND run.account_id = OLD.account_id
28
+ AND run.workspace_id = OLD.workspace_id
29
+ AND run.session_id = OLD.session_id
30
+ AND run.action_kind = 'agent_turn'
31
+ FOR UPDATE;
32
+
33
+ IF run_status IS DISTINCT FROM 'dispatched' THEN
34
+ RAISE EXCEPTION 'terminal scheduled occurrence cannot be delivered'
35
+ USING ERRCODE = '42501';
36
+ END IF;
37
+ RETURN NEW;
38
+ END
39
+ $body$;
40
+
41
+ CREATE TRIGGER scheduled_terminal_occurrence_delivery_fence
42
+ BEFORE UPDATE OF state ON session_system_updates
43
+ FOR EACH ROW
44
+ WHEN (
45
+ OLD.scheduled_task_run_id IS NOT NULL
46
+ AND OLD.state = 'pending'
47
+ AND NEW.state = 'delivered'
48
+ )
49
+ EXECUTE FUNCTION fence_terminal_scheduled_occurrence_delivery();
50
+
51
+ CREATE FUNCTION invalidate_unclaimed_scheduled_agent_runs_on_task_inactive()
52
+ RETURNS trigger
53
+ LANGUAGE plpgsql
54
+ SECURITY DEFINER
55
+ SET search_path FROM CURRENT
56
+ AS $body$
57
+ DECLARE
58
+ run_row record;
59
+ invalidation_error text;
60
+ BEGIN
61
+ invalidation_error := CASE
62
+ -- 0275 introduced deleted_at. JSON row introspection keeps this rolling
63
+ -- trigger installable in legacy-upgrade fixtures that intentionally replay
64
+ -- 0275 after the current tail, while current schemas still classify delete.
65
+ WHEN pg_catalog.to_jsonb(OLD) ->> 'deleted_at' IS NULL
66
+ AND pg_catalog.to_jsonb(NEW) ->> 'deleted_at' IS NOT NULL
67
+ THEN 'scheduled_task_deleted_before_claim'
68
+ ELSE 'scheduled_task_paused_before_claim'
69
+ END;
70
+
71
+ INSERT INTO opengeni_private.scheduled_personal_resource_capabilities (
72
+ backend_pid, transaction_id, capability_kind
73
+ ) VALUES (
74
+ pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'run_lifecycle'
75
+ )
76
+ ON CONFLICT DO NOTHING;
77
+
78
+ -- Run admission takes the task row first. Deposit and claim both lock the run
79
+ -- before they can publish an occurrence or a scheduler-owned turn. Once this
80
+ -- loop owns a run, a fresh turn lookup therefore decides the race exactly:
81
+ -- no turn means no claim crossed the lifecycle boundary.
82
+ FOR run_row IN
83
+ SELECT run.id
84
+ FROM scheduled_task_runs run
85
+ WHERE run.account_id = NEW.account_id
86
+ AND run.workspace_id = NEW.workspace_id
87
+ AND run.task_id = NEW.id
88
+ AND run.action_kind = 'agent_turn'
89
+ AND run.status IN ('queued', 'dispatched')
90
+ ORDER BY run.id
91
+ FOR UPDATE
92
+ LOOP
93
+ IF NOT EXISTS (
94
+ SELECT 1
95
+ FROM session_turns turn_value
96
+ WHERE turn_value.account_id = NEW.account_id
97
+ AND turn_value.workspace_id = NEW.workspace_id
98
+ AND turn_value.scheduled_task_run_id = run_row.id
99
+ ) THEN
100
+ UPDATE scheduled_task_runs run
101
+ SET status = 'skipped',
102
+ error = invalidation_error,
103
+ completed_at = pg_catalog.clock_timestamp(),
104
+ updated_at = pg_catalog.clock_timestamp()
105
+ WHERE run.id = run_row.id
106
+ AND run.account_id = NEW.account_id
107
+ AND run.workspace_id = NEW.workspace_id
108
+ AND run.status IN ('queued', 'dispatched');
109
+ END IF;
110
+ END LOOP;
111
+
112
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
113
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
114
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
115
+ AND capability_kind = 'run_lifecycle';
116
+ RETURN NEW;
117
+ EXCEPTION WHEN OTHERS THEN
118
+ DELETE FROM opengeni_private.scheduled_personal_resource_capabilities
119
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
120
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
121
+ AND capability_kind = 'run_lifecycle';
122
+ RAISE;
123
+ END
124
+ $body$;
125
+
126
+ CREATE TRIGGER scheduled_task_unclaimed_occurrence_invalidation
127
+ AFTER UPDATE OF status ON scheduled_tasks
128
+ FOR EACH ROW
129
+ WHEN (OLD.status = 'active' AND NEW.status = 'paused')
130
+ EXECUTE FUNCTION invalidate_unclaimed_scheduled_agent_runs_on_task_inactive();
131
+
132
+ REVOKE ALL ON FUNCTION
133
+ invalidate_unclaimed_scheduled_agent_runs_on_task_inactive()
134
+ FROM PUBLIC;
135
+ REVOKE ALL ON FUNCTION fence_terminal_scheduled_occurrence_delivery() FROM PUBLIC;
136
+
137
+ DO $pin_scheduled_task_unclaimed_invalidation$
138
+ DECLARE
139
+ data_schema text := pg_catalog.current_schema();
140
+ BEGIN
141
+ EXECUTE pg_catalog.format(
142
+ 'ALTER FUNCTION %I.invalidate_unclaimed_scheduled_agent_runs_on_task_inactive() '
143
+ || 'SET search_path = pg_catalog, %I, pg_temp',
144
+ data_schema,
145
+ data_schema
146
+ );
147
+ EXECUTE pg_catalog.format(
148
+ 'ALTER FUNCTION %I.fence_terminal_scheduled_occurrence_delivery() '
149
+ || 'SET search_path = pg_catalog, %I, pg_temp',
150
+ data_schema,
151
+ data_schema
152
+ );
153
+ END
154
+ $pin_scheduled_task_unclaimed_invalidation$;
@@ -0,0 +1,28 @@
1
+ -- deployment-mode: rolling
2
+ -- Preserve the hypothetical OpenGeni credit price separately from both the
3
+ -- upstream provider estimate and the amount actually priced to credits.
4
+ -- Historical external facts remain unknown rather than being recomputed with
5
+ -- today's margin or provider rates.
6
+
7
+ SET LOCAL lock_timeout = '5s';
8
+ SET LOCAL statement_timeout = '5min';
9
+
10
+ ALTER TABLE "model_call_facts"
11
+ ADD COLUMN IF NOT EXISTS "equivalent_credit_cost_micros" bigint;
12
+
13
+ ALTER TABLE "model_call_facts"
14
+ DROP CONSTRAINT IF EXISTS "model_call_facts_equivalent_credit_cost_check",
15
+ ADD CONSTRAINT "model_call_facts_equivalent_credit_cost_check"
16
+ CHECK (
17
+ "equivalent_credit_cost_micros" IS NULL
18
+ OR (
19
+ "equivalent_credit_cost_micros" >= 0
20
+ AND "estimated_provider_cost_micros" IS NOT NULL
21
+ )
22
+ ) NOT VALID;
23
+
24
+ ALTER TABLE "model_call_facts"
25
+ VALIDATE CONSTRAINT "model_call_facts_equivalent_credit_cost_check";
26
+
27
+ RESET statement_timeout;
28
+ RESET lock_timeout;
@@ -0,0 +1,113 @@
1
+ -- deployment-mode: rolling
2
+ -- A finite provider lifetime is a hard destruction boundary. Once the normal
3
+ -- lead-time rotation request is durable, persistent Browser/Computer placement
4
+ -- must yield while enough time remains to quiesce writers and capture the exact
5
+ -- workspace generation. Waiting until the provider deadline keeps the lease
6
+ -- holder alive until the same instant the provider may destroy the box.
7
+ SET LOCAL lock_timeout = '5s';
8
+ SET LOCAL statement_timeout = '10min';
9
+
10
+ DO $sandbox_deadline_rotation_preemption$
11
+ DECLARE
12
+ definition text;
13
+ patched text;
14
+ eligibility_anchor constant text :=
15
+ E' AND lease.provider_deadline_at <= pg_catalog.now()';
16
+ eligibility_replacement constant text :=
17
+ E' -- 0397 deadline rotation preemption: an automatic finite-lifetime\n'
18
+ || E' -- rotation yields persistent controllers at the lead boundary;\n'
19
+ || E' -- unrelated operator rotations retain the hard-deadline fallback.\n'
20
+ || E' AND (\n'
21
+ || E' lease.rotation_reason = ''provider_deadline''\n'
22
+ || E' OR lease.provider_deadline_at <= pg_catalog.now()\n'
23
+ || E' )';
24
+ message_anchor constant text :=
25
+ E' error_message = ''Interaction controller reached its sandbox provider deadline'',';
26
+ message_replacement constant text :=
27
+ E' error_message = ''Interaction controller yielded for sandbox provider rotation'',';
28
+ occurrences integer;
29
+ BEGIN
30
+ SELECT pg_catalog.pg_get_functiondef(
31
+ 'opengeni_private.reap_stale_interaction_transitions(bigint)'::regprocedure
32
+ ) INTO definition;
33
+
34
+ IF definition IS NULL
35
+ OR pg_catalog.strpos(
36
+ definition,
37
+ '0391 provider-deadline interaction follow-up'
38
+ ) = 0
39
+ OR pg_catalog.strpos(
40
+ definition,
41
+ 'interaction_holder.kind = ''interaction'''
42
+ ) = 0
43
+ OR pg_catalog.strpos(definition, 'provider_deadline_rotation') = 0
44
+ THEN
45
+ RAISE EXCEPTION '0397 interaction rotation prerequisite definition drift'
46
+ USING ERRCODE = '55000';
47
+ END IF;
48
+
49
+ IF pg_catalog.strpos(definition, '0397 deadline rotation preemption') > 0 THEN
50
+ IF pg_catalog.strpos(
51
+ definition,
52
+ 'lease.rotation_reason = ''provider_deadline'''
53
+ ) = 0
54
+ OR pg_catalog.strpos(
55
+ definition,
56
+ 'Interaction controller yielded for sandbox provider rotation'
57
+ ) = 0
58
+ THEN
59
+ RAISE EXCEPTION '0397 interaction rotation replay definition drift'
60
+ USING ERRCODE = '55000';
61
+ END IF;
62
+ ELSE
63
+ patched := definition;
64
+
65
+ occurrences := (
66
+ pg_catalog.length(patched)
67
+ - pg_catalog.length(pg_catalog.replace(patched, eligibility_anchor, ''))
68
+ ) / pg_catalog.length(eligibility_anchor);
69
+ IF occurrences <> 1 THEN
70
+ RAISE EXCEPTION '0397 interaction rotation eligibility anchor drift'
71
+ USING ERRCODE = '55000';
72
+ END IF;
73
+ patched := pg_catalog.replace(
74
+ patched,
75
+ eligibility_anchor,
76
+ eligibility_replacement
77
+ );
78
+
79
+ occurrences := (
80
+ pg_catalog.length(patched)
81
+ - pg_catalog.length(pg_catalog.replace(patched, message_anchor, ''))
82
+ ) / pg_catalog.length(message_anchor);
83
+ IF occurrences <> 1 THEN
84
+ RAISE EXCEPTION '0397 interaction rotation message anchor drift'
85
+ USING ERRCODE = '55000';
86
+ END IF;
87
+ patched := pg_catalog.replace(
88
+ patched,
89
+ message_anchor,
90
+ message_replacement
91
+ );
92
+
93
+ IF pg_catalog.strpos(patched, '0397 deadline rotation preemption') = 0
94
+ OR pg_catalog.strpos(
95
+ patched,
96
+ 'lease.rotation_reason = ''provider_deadline'''
97
+ ) = 0
98
+ OR pg_catalog.strpos(
99
+ patched,
100
+ 'OR lease.provider_deadline_at <= pg_catalog.now()'
101
+ ) = 0
102
+ THEN
103
+ RAISE EXCEPTION '0397 interaction rotation patch failed'
104
+ USING ERRCODE = '55000';
105
+ END IF;
106
+
107
+ EXECUTE patched;
108
+ END IF;
109
+ END
110
+ $sandbox_deadline_rotation_preemption$;
111
+
112
+ RESET statement_timeout;
113
+ RESET lock_timeout;
@@ -0,0 +1,206 @@
1
+ -- deployment-mode: rolling
2
+ -- Shared-workspace creators receive explicit operational administration while
3
+ -- organization administrators retain a separate, content-blind lifecycle
4
+ -- authority for the workspace management surface.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ ALTER FUNCTION organization_workspace_command(jsonb)
10
+ RENAME TO organization_workspace_command_without_creator_access;
11
+
12
+ REVOKE ALL ON FUNCTION organization_workspace_command_without_creator_access(jsonb) FROM PUBLIC;
13
+
14
+ CREATE FUNCTION organization_workspace_command(p_command jsonb)
15
+ RETURNS jsonb
16
+ LANGUAGE plpgsql SECURITY DEFINER
17
+ SET search_path FROM CURRENT
18
+ AS $body$
19
+ DECLARE
20
+ result jsonb;
21
+ actor_membership_id uuid;
22
+ creator_grant_operation_id uuid;
23
+ creator_grant_operation_hash text;
24
+ BEGIN
25
+ result := organization_workspace_command_without_creator_access(p_command);
26
+ IF p_command ->> 'action' IS DISTINCT FROM 'create' THEN
27
+ RETURN result;
28
+ END IF;
29
+ -- A create receipt written before this migration has no child grant receipt.
30
+ -- Preserve that historical outcome instead of turning a retry into a new
31
+ -- privilege grant after rollout.
32
+ IF coalesce((result ->> 'replay')::boolean, false) THEN
33
+ RETURN result;
34
+ END IF;
35
+
36
+ SELECT membership.id INTO actor_membership_id
37
+ FROM organization_memberships membership
38
+ WHERE membership.account_id = nullif(p_command ->> 'organizationId', '')::uuid
39
+ AND membership.subject_id = p_command ->> 'actorSubjectId'
40
+ AND membership.status = 'active'
41
+ AND membership.role IN ('owner', 'admin');
42
+ IF actor_membership_id IS NULL THEN
43
+ RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
44
+ END IF;
45
+
46
+ -- Derive one stable, private follow-up operation from the public create key.
47
+ -- A committed create can therefore be retried safely after a lost response,
48
+ -- without ever re-elevating a creator who was deliberately demoted later.
49
+ creator_grant_operation_hash := pg_catalog.md5(
50
+ 'organization-workspace-creator-admin:' || (p_command ->> 'operationId')
51
+ );
52
+ creator_grant_operation_id := (
53
+ pg_catalog.substr(creator_grant_operation_hash, 1, 12)
54
+ || '5'
55
+ || pg_catalog.substr(creator_grant_operation_hash, 14, 3)
56
+ || '8'
57
+ || pg_catalog.substr(creator_grant_operation_hash, 18)
58
+ )::uuid;
59
+
60
+ result := result || organization_workspace_command_without_creator_access(
61
+ pg_catalog.jsonb_build_object(
62
+ 'action', 'grant',
63
+ 'organizationId', p_command ->> 'organizationId',
64
+ 'actorSubjectId', p_command ->> 'actorSubjectId',
65
+ 'workspaceId', result ->> 'workspaceId',
66
+ 'targetOrganizationMembershipId', actor_membership_id,
67
+ 'role', 'admin',
68
+ 'operationId', creator_grant_operation_id
69
+ )
70
+ );
71
+ RETURN result;
72
+ END
73
+ $body$;
74
+
75
+ CREATE FUNCTION authorize_organization_shared_workspace_administration(
76
+ p_account_id uuid,
77
+ p_workspace_id uuid,
78
+ p_actor_subject_id text
79
+ ) RETURNS void
80
+ LANGUAGE plpgsql SECURITY DEFINER
81
+ SET search_path FROM CURRENT
82
+ AS $body$
83
+ DECLARE
84
+ actor organization_memberships%ROWTYPE;
85
+ BEGIN
86
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_actor_subject_id IS NULL
87
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
88
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
89
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
90
+ THEN
91
+ RAISE EXCEPTION 'organization workspace administration is invalid'
92
+ USING ERRCODE = '22023';
93
+ END IF;
94
+
95
+ PERFORM pg_catalog.pg_advisory_xact_lock(
96
+ pg_catalog.hashtextextended('organization-membership:' || p_account_id::text, 0)
97
+ );
98
+ PERFORM pg_catalog.set_config(
99
+ 'opengeni.organization_tenancy_lifecycle',
100
+ 'organization_membership_lifecycle', true
101
+ );
102
+ PERFORM 1 FROM managed_accounts account
103
+ WHERE account.id = p_account_id FOR KEY SHARE;
104
+ IF NOT FOUND THEN
105
+ RAISE EXCEPTION 'organization not found' USING ERRCODE = 'P0002';
106
+ END IF;
107
+
108
+ SELECT * INTO actor FROM organization_memberships membership
109
+ WHERE membership.account_id = p_account_id
110
+ AND membership.subject_id = p_actor_subject_id
111
+ FOR SHARE;
112
+ IF NOT FOUND OR actor.status <> 'active' OR actor.role NOT IN ('owner', 'admin') THEN
113
+ RAISE EXCEPTION 'organization administration required' USING ERRCODE = '42501';
114
+ END IF;
115
+ IF NOT EXISTS (
116
+ SELECT 1 FROM workspaces workspace
117
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
118
+ ) THEN
119
+ RAISE EXCEPTION 'workspace not found' USING ERRCODE = 'P0002';
120
+ END IF;
121
+ IF EXISTS (
122
+ SELECT 1 FROM organization_memberships membership
123
+ WHERE membership.account_id = p_account_id
124
+ AND membership.personal_workspace_id = p_workspace_id
125
+ ) THEN
126
+ RAISE EXCEPTION 'personal workspaces are not administrable'
127
+ USING ERRCODE = '42501';
128
+ END IF;
129
+ END
130
+ $body$;
131
+
132
+ REVOKE ALL ON FUNCTION organization_workspace_command(jsonb) FROM PUBLIC;
133
+ REVOKE ALL ON FUNCTION authorize_organization_shared_workspace_administration(uuid,uuid,text)
134
+ FROM PUBLIC;
135
+
136
+ DO $pin_and_grant$
137
+ DECLARE
138
+ data_schema text := current_schema();
139
+ legacy_owner oid;
140
+ grantee_name text;
141
+ BEGIN
142
+ EXECUTE format(
143
+ 'ALTER FUNCTION %I.organization_workspace_command_without_creator_access(jsonb) SET search_path = pg_catalog, %I, pg_temp',
144
+ data_schema, data_schema
145
+ );
146
+ EXECUTE format(
147
+ 'ALTER FUNCTION %I.organization_workspace_command(jsonb) SET search_path = pg_catalog, %I, pg_temp',
148
+ data_schema, data_schema
149
+ );
150
+ EXECUTE format(
151
+ 'ALTER FUNCTION %I.authorize_organization_shared_workspace_administration(uuid,uuid,text) SET search_path = pg_catalog, %I, pg_temp',
152
+ data_schema, data_schema
153
+ );
154
+ -- Renaming preserved the legacy function's ACL. Atomically move every
155
+ -- supported runtime role to the replacement wrapper before removing its
156
+ -- bypass access, so a migrate-then-provision rollout has no outage window.
157
+ SELECT function.proowner INTO legacy_owner
158
+ FROM pg_catalog.pg_proc function
159
+ WHERE function.oid =
160
+ 'organization_workspace_command_without_creator_access(jsonb)'::regprocedure;
161
+ FOR grantee_name IN
162
+ SELECT role.rolname
163
+ FROM pg_catalog.pg_proc function
164
+ CROSS JOIN LATERAL pg_catalog.aclexplode(
165
+ coalesce(function.proacl, pg_catalog.acldefault('f', function.proowner))
166
+ ) grant_value
167
+ JOIN pg_catalog.pg_roles role ON role.oid = grant_value.grantee
168
+ WHERE function.oid =
169
+ 'organization_workspace_command_without_creator_access(jsonb)'::regprocedure
170
+ AND grant_value.privilege_type = 'EXECUTE'
171
+ AND grant_value.grantee <> legacy_owner
172
+ LOOP
173
+ EXECUTE format(
174
+ 'GRANT EXECUTE ON FUNCTION %I.organization_workspace_command(jsonb) TO %I',
175
+ data_schema, grantee_name
176
+ );
177
+ EXECUTE format(
178
+ 'GRANT EXECUTE ON FUNCTION %I.authorize_organization_shared_workspace_administration(uuid,uuid,text) TO %I',
179
+ data_schema, grantee_name
180
+ );
181
+ EXECUTE format(
182
+ 'REVOKE EXECUTE ON FUNCTION %I.organization_workspace_command_without_creator_access(jsonb) FROM %I',
183
+ data_schema, grantee_name
184
+ );
185
+ END LOOP;
186
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
187
+ EXECUTE format(
188
+ 'REVOKE ALL ON FUNCTION %I.organization_workspace_command_without_creator_access(jsonb) FROM opengeni_app',
189
+ data_schema
190
+ );
191
+ EXECUTE format(
192
+ 'GRANT EXECUTE ON FUNCTION %I.organization_workspace_command(jsonb) TO opengeni_app',
193
+ data_schema
194
+ );
195
+ EXECUTE format(
196
+ 'GRANT EXECUTE ON FUNCTION %I.authorize_organization_shared_workspace_administration(uuid,uuid,text) TO opengeni_app',
197
+ data_schema
198
+ );
199
+ END IF;
200
+ END
201
+ $pin_and_grant$;
202
+
203
+ COMMENT ON FUNCTION organization_workspace_command(jsonb) IS
204
+ 'Idempotent organization-admin shared-workspace command. A create atomically materializes an explicit named admin grant for its creator.';
205
+ COMMENT ON FUNCTION authorize_organization_shared_workspace_administration(uuid,uuid,text) IS
206
+ 'Transaction-scoped, content-blind organization owner/admin authority for managing one shared workspace. It grants no operational workspace access.';