@opengeni/db 3.7.4 → 3.8.2-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 (77) hide show
  1. package/dist/automations.d.ts +4 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/{chunk-O56DM5GZ.js → chunk-53A2PEHT.js} +328 -31
  4. package/dist/chunk-53A2PEHT.js.map +1 -0
  5. package/dist/{chunk-SZAWDXQC.js → chunk-5S4BQAHX.js} +3 -3
  6. package/dist/{chunk-S3TDDCS2.js → chunk-CITYUGMW.js} +3 -3
  7. package/dist/{chunk-4NAR7FBA.js → chunk-F5YOZEYQ.js} +49 -1
  8. package/dist/chunk-F5YOZEYQ.js.map +1 -0
  9. package/dist/{chunk-F6PBS6WE.js → chunk-HZV7O6JR.js} +2 -2
  10. package/dist/{chunk-5NNCGFQY.js → chunk-RLQVPOVR.js} +2 -2
  11. package/dist/{chunk-DZCYW2WT.js → chunk-UKOGHUGC.js} +2 -2
  12. package/dist/{chunk-US4IS6IV.js → chunk-X6TO5HII.js} +2 -2
  13. package/dist/{chunk-U4WPPEBH.js → chunk-Y53WEFYB.js} +2 -2
  14. package/dist/connection-token-resolver.d.ts +4 -0
  15. package/dist/editable-artifact-durable-export.js +2 -2
  16. package/dist/editable-artifacts.js +3 -3
  17. package/dist/index.d.ts +164 -14
  18. package/dist/index.js +7327 -5442
  19. package/dist/index.js.map +1 -1
  20. package/dist/insights.d.ts +9 -0
  21. package/dist/managed-auth-session-sets.js +3 -3
  22. package/dist/model-catalog.d.ts +153 -0
  23. package/dist/new-session-drafts.d.ts +12 -0
  24. package/dist/organization-membership-lifecycle.d.ts +8 -1
  25. package/dist/organization-model-providers.d.ts +101 -0
  26. package/dist/pack-components.d.ts +1 -0
  27. package/dist/pr-review.d.ts +9 -0
  28. package/dist/provision-roles.js +1 -1
  29. package/dist/runtime-posture.d.ts +7 -7
  30. package/dist/schema.d.ts +989 -2
  31. package/dist/schema.js +11 -1
  32. package/dist/session-background-commands.js +3 -3
  33. package/dist/session-control.d.ts +35 -0
  34. package/dist/session-queue-commands.d.ts +17 -0
  35. package/dist/session-tenancy.js +3 -3
  36. package/dist/video-generation.js +3 -3
  37. package/dist/xai-subscription.d.ts +8 -0
  38. package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
  39. package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
  40. package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
  41. package/drizzle/0390_organization_model_provider_connections.sql +197 -0
  42. package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
  43. package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
  44. package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
  45. package/drizzle/0394_session_selected_skill_activation.sql +20 -0
  46. package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
  47. package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
  48. package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
  49. package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
  50. package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
  51. package/package.json +6 -6
  52. package/src/automations.ts +9 -1
  53. package/src/connection-token-resolver.ts +12 -0
  54. package/src/index.ts +1551 -272
  55. package/src/insights-model-bundle.ts +54 -5
  56. package/src/insights.ts +53 -7
  57. package/src/model-catalog.ts +682 -0
  58. package/src/new-session-drafts.ts +66 -21
  59. package/src/organization-membership-lifecycle.ts +47 -5
  60. package/src/organization-model-providers.ts +614 -0
  61. package/src/pack-components.ts +15 -2
  62. package/src/pr-review.ts +19 -0
  63. package/src/provision-roles.ts +2 -0
  64. package/src/runtime-posture.ts +48 -0
  65. package/src/schema.ts +348 -32
  66. package/src/session-control.ts +170 -0
  67. package/src/session-queue-commands.ts +157 -82
  68. package/src/xai-subscription.ts +66 -14
  69. package/dist/chunk-4NAR7FBA.js.map +0 -1
  70. package/dist/chunk-O56DM5GZ.js.map +0 -1
  71. /package/dist/{chunk-SZAWDXQC.js.map → chunk-5S4BQAHX.js.map} +0 -0
  72. /package/dist/{chunk-S3TDDCS2.js.map → chunk-CITYUGMW.js.map} +0 -0
  73. /package/dist/{chunk-F6PBS6WE.js.map → chunk-HZV7O6JR.js.map} +0 -0
  74. /package/dist/{chunk-5NNCGFQY.js.map → chunk-RLQVPOVR.js.map} +0 -0
  75. /package/dist/{chunk-DZCYW2WT.js.map → chunk-UKOGHUGC.js.map} +0 -0
  76. /package/dist/{chunk-US4IS6IV.js.map → chunk-X6TO5HII.js.map} +0 -0
  77. /package/dist/{chunk-U4WPPEBH.js.map → chunk-Y53WEFYB.js.map} +0 -0
@@ -0,0 +1,241 @@
1
+ -- deployment-mode: maintenance
2
+ -- Migration 0389: deployment-owned model catalog authority, provider-scoped
3
+ -- workspace Vercel AI Gateway/OpenRouter slugs, and indexed pre-catalog prompt
4
+ -- receipt replay.
5
+ -- This changes the exact runtime-posture table/grant contract. Stop every API,
6
+ -- control worker, and turn worker before applying it, and never restart a
7
+ -- pre-0389 image after commit.
8
+
9
+ SET LOCAL lock_timeout = '5s';
10
+ SET LOCAL statement_timeout = '5min';
11
+
12
+ DO $model_catalog_runtime_drain_before$
13
+ DECLARE
14
+ configured_roles_text text := nullif(
15
+ current_setting('opengeni.migration_application_roles', true), ''
16
+ );
17
+ configured_roles jsonb;
18
+ BEGIN
19
+ IF configured_roles_text IS NULL THEN
20
+ RAISE EXCEPTION
21
+ '0389 model catalog activation requires an explicit application database role list'
22
+ USING ERRCODE = '55000';
23
+ END IF;
24
+ BEGIN
25
+ configured_roles := configured_roles_text::jsonb;
26
+ EXCEPTION WHEN OTHERS THEN
27
+ RAISE EXCEPTION
28
+ '0389 model catalog activation received a malformed application database role list'
29
+ USING ERRCODE = '55000';
30
+ END;
31
+ IF jsonb_typeof(configured_roles) <> 'array'
32
+ OR jsonb_array_length(configured_roles) NOT BETWEEN 1 AND 16
33
+ OR EXISTS (
34
+ SELECT 1 FROM jsonb_array_elements(configured_roles) AS roles(value)
35
+ WHERE jsonb_typeof(value) <> 'string'
36
+ OR btrim(value #>> '{}') = ''
37
+ OR octet_length(value #>> '{}') > 63
38
+ )
39
+ OR (
40
+ SELECT count(*) FROM jsonb_array_elements_text(configured_roles)
41
+ ) <> (
42
+ SELECT count(DISTINCT value)
43
+ FROM jsonb_array_elements_text(configured_roles) AS roles(value)
44
+ )
45
+ THEN
46
+ RAISE EXCEPTION
47
+ '0389 model catalog activation received an invalid application database role list'
48
+ USING ERRCODE = '55000';
49
+ END IF;
50
+ IF EXISTS (
51
+ SELECT 1 FROM pg_stat_activity activity
52
+ JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
53
+ ON roles.role_name = activity.usename
54
+ WHERE activity.datname = current_database()
55
+ AND activity.pid <> pg_backend_pid()
56
+ )
57
+ THEN
58
+ RAISE EXCEPTION
59
+ '0389 model catalog activation requires all configured OpenGeni application database sessions to be stopped'
60
+ USING ERRCODE = '55000';
61
+ END IF;
62
+ END
63
+ $model_catalog_runtime_drain_before$;
64
+
65
+ -- Exactly one deployment-global, secret-free catalog document. Ordinary API
66
+ -- and worker roles may only read it; the operator upsert command uses a
67
+ -- migration/admin credential. No seed or boot-time rewrite is intentional.
68
+ CREATE TABLE deployment_model_catalog (
69
+ singleton boolean PRIMARY KEY DEFAULT true,
70
+ document jsonb NOT NULL,
71
+ version bigint NOT NULL DEFAULT 1,
72
+ updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
73
+ CONSTRAINT deployment_model_catalog_singleton_chk CHECK (singleton),
74
+ CONSTRAINT deployment_model_catalog_document_chk CHECK (jsonb_typeof(document) = 'object'),
75
+ CONSTRAINT deployment_model_catalog_version_chk CHECK (version > 0)
76
+ );
77
+
78
+ COMMENT ON TABLE deployment_model_catalog IS
79
+ 'Deployment-global, secret-free model catalog singleton. Runtime is SELECT-only; operators update it explicitly.';
80
+
81
+ CREATE TABLE workspace_gateway_custom_models (
82
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
83
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
84
+ workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
85
+ provider_kind text NOT NULL,
86
+ upstream_model_id text NOT NULL,
87
+ label text,
88
+ version integer NOT NULL DEFAULT 1,
89
+ create_operation_id uuid NOT NULL,
90
+ create_request_hash text NOT NULL,
91
+ delete_operation_id uuid,
92
+ delete_request_hash text,
93
+ created_by_subject_id text NOT NULL,
94
+ retired_at timestamptz,
95
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
96
+ updated_at timestamptz NOT NULL DEFAULT clock_timestamp(),
97
+ CONSTRAINT workspace_gateway_custom_models_workspace_account_fk
98
+ FOREIGN KEY (workspace_id, account_id)
99
+ REFERENCES workspaces(id, account_id) ON DELETE CASCADE,
100
+ CONSTRAINT workspace_gateway_custom_models_provider_kind_chk CHECK (
101
+ provider_kind IN ('vercel_gateway', 'openrouter')
102
+ ),
103
+ CONSTRAINT workspace_gateway_custom_models_upstream_chk CHECK (
104
+ octet_length(upstream_model_id) BETWEEN 1 AND 238
105
+ AND upstream_model_id ~ '^[!-~]+$'
106
+ AND upstream_model_id !~ '[|]'
107
+ ),
108
+ CONSTRAINT workspace_gateway_custom_models_label_chk CHECK (
109
+ label IS NULL OR (
110
+ octet_length(label) BETWEEN 1 AND 128
111
+ AND label !~ '[\r\n|]'
112
+ )
113
+ ),
114
+ CONSTRAINT workspace_gateway_custom_models_actor_chk CHECK (
115
+ octet_length(created_by_subject_id) BETWEEN 1 AND 1024
116
+ ),
117
+ CONSTRAINT workspace_gateway_custom_models_version_chk CHECK (version > 0),
118
+ CONSTRAINT workspace_gateway_custom_models_create_hash_chk CHECK (
119
+ create_request_hash ~ '^[a-f0-9]{64}$'
120
+ ),
121
+ CONSTRAINT workspace_gateway_custom_models_delete_receipt_chk CHECK (
122
+ (delete_operation_id IS NULL AND delete_request_hash IS NULL)
123
+ OR (
124
+ delete_operation_id IS NOT NULL
125
+ AND delete_request_hash ~ '^[a-f0-9]{64}$'
126
+ AND retired_at IS NOT NULL
127
+ )
128
+ )
129
+ );
130
+
131
+ CREATE UNIQUE INDEX workspace_gateway_custom_models_workspace_upstream_uq
132
+ ON workspace_gateway_custom_models (workspace_id, provider_kind, upstream_model_id)
133
+ WHERE retired_at IS NULL;
134
+
135
+ CREATE UNIQUE INDEX workspace_gateway_custom_models_create_operation_uq
136
+ ON workspace_gateway_custom_models (workspace_id, provider_kind, create_operation_id);
137
+
138
+ CREATE UNIQUE INDEX workspace_gateway_custom_models_delete_operation_uq
139
+ ON workspace_gateway_custom_models (workspace_id, provider_kind, delete_operation_id)
140
+ WHERE delete_operation_id IS NOT NULL;
141
+
142
+ -- New Send/Steer receipts persist one parsed boundary-request fingerprint and
143
+ -- replay it before mutable model-catalog resolution. Legacy receipt identity
144
+ -- remains unchanged; this partial index keeps the actor/key compatibility
145
+ -- probe bounded even when the prior action or target session differs.
146
+ CREATE INDEX session_command_receipts_prompt_actor_operation_idx
147
+ ON session_command_receipts (
148
+ workspace_id,
149
+ actor_type,
150
+ actor_subject_id,
151
+ actor_attempt_id,
152
+ operation_key
153
+ )
154
+ WHERE action IN ('prompt.send', 'prompt.steer');
155
+
156
+ ALTER TABLE workspace_gateway_custom_models ENABLE ROW LEVEL SECURITY;
157
+ ALTER TABLE workspace_gateway_custom_models FORCE ROW LEVEL SECURITY;
158
+ CREATE POLICY workspace_isolation ON workspace_gateway_custom_models
159
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
160
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
161
+
162
+ -- Migration 0040 registers owner default privileges for the historical
163
+ -- opengeni_app role. Rotated-role deployments must not inherit that stale
164
+ -- grant on these new tables, and host defaults may name other obsolete
165
+ -- application roles. Strip every explicit non-owner table grantee. The
166
+ -- migration_application_roles list above is drain detection only; the
167
+ -- post-migration role provisioner grants only the deployment's current target
168
+ -- runtime role.
169
+ DO $model_catalog_table_acl_reset$
170
+ DECLARE
171
+ data_schema text := pg_catalog.current_schema();
172
+ relation_name text;
173
+ role_name text;
174
+ BEGIN
175
+ FOR relation_name IN
176
+ SELECT relation.value
177
+ FROM pg_catalog.unnest(
178
+ ARRAY[
179
+ 'deployment_model_catalog',
180
+ 'workspace_gateway_custom_models'
181
+ ]::text[]
182
+ ) AS relation(value)
183
+ LOOP
184
+ EXECUTE pg_catalog.format(
185
+ 'REVOKE ALL ON TABLE %I.%I FROM PUBLIC',
186
+ data_schema,
187
+ relation_name
188
+ );
189
+ FOR role_name IN
190
+ SELECT grantee_role.rolname
191
+ FROM pg_catalog.pg_class relation
192
+ CROSS JOIN LATERAL pg_catalog.aclexplode(
193
+ coalesce(
194
+ relation.relacl,
195
+ pg_catalog.acldefault('r', relation.relowner)
196
+ )
197
+ ) privilege
198
+ INNER JOIN pg_catalog.pg_roles grantee_role
199
+ ON grantee_role.oid = privilege.grantee
200
+ WHERE relation.oid = pg_catalog.to_regclass(
201
+ pg_catalog.format('%I.%I', data_schema, relation_name)
202
+ )
203
+ AND privilege.grantee <> 0
204
+ AND privilege.grantee <> relation.relowner
205
+ GROUP BY grantee_role.rolname
206
+ ORDER BY grantee_role.rolname COLLATE "C"
207
+ LOOP
208
+ EXECUTE pg_catalog.format(
209
+ 'REVOKE ALL ON TABLE %I.%I FROM %I',
210
+ data_schema,
211
+ relation_name,
212
+ role_name
213
+ );
214
+ END LOOP;
215
+ END LOOP;
216
+ END
217
+ $model_catalog_table_acl_reset$;
218
+
219
+ COMMENT ON TABLE workspace_gateway_custom_models IS
220
+ 'Workspace-owned Vercel AI Gateway and OpenRouter upstream slugs. Retired rows remain execution evidence; capabilities and billing are never stored here.';
221
+
222
+ DO $model_catalog_runtime_drain_after$
223
+ DECLARE
224
+ configured_roles jsonb := current_setting(
225
+ 'opengeni.migration_application_roles', false
226
+ )::jsonb;
227
+ BEGIN
228
+ IF EXISTS (
229
+ SELECT 1 FROM pg_stat_activity activity
230
+ JOIN jsonb_array_elements_text(configured_roles) roles(role_name)
231
+ ON roles.role_name = activity.usename
232
+ WHERE activity.datname = current_database()
233
+ AND activity.pid <> pg_backend_pid()
234
+ )
235
+ THEN
236
+ RAISE EXCEPTION
237
+ '0389 model catalog activation observed a configured OpenGeni application database session after schema installation'
238
+ USING ERRCODE = '55000';
239
+ END IF;
240
+ END
241
+ $model_catalog_runtime_drain_after$;
@@ -0,0 +1,197 @@
1
+ -- deployment-mode: maintenance
2
+ -- Organization-owned Vercel AI Gateway/OpenRouter credentials and model catalog.
3
+ -- Personal workspaces are deliberately excluded from runtime inheritance.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+
8
+ DO $scope_visibility$
9
+ DECLARE data_schema text := current_schema();
10
+ BEGIN
11
+ EXECUTE format($ddl$
12
+ CREATE OR REPLACE FUNCTION opengeni_private.organization_model_provider_scope_visible(
13
+ p_account_id uuid
14
+ ) RETURNS boolean
15
+ LANGUAGE plpgsql SECURITY DEFINER
16
+ SET search_path = pg_catalog
17
+ AS $function$
18
+ DECLARE
19
+ workspace_value uuid := opengeni_private.current_workspace_id();
20
+ subject_value text := opengeni_private.current_subject_id();
21
+ visible boolean := false;
22
+ previous_lifecycle text := pg_catalog.current_setting(
23
+ 'opengeni.organization_tenancy_lifecycle', true
24
+ );
25
+ BEGIN
26
+ IF p_account_id IS NULL
27
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
28
+ THEN RETURN false; END IF;
29
+
30
+ PERFORM pg_catalog.set_config(
31
+ 'opengeni.organization_tenancy_lifecycle',
32
+ 'organization_membership_lifecycle', true
33
+ );
34
+ IF workspace_value IS NOT NULL THEN
35
+ SELECT EXISTS (
36
+ SELECT 1 FROM %1$I.workspaces workspace
37
+ WHERE workspace.account_id = p_account_id AND workspace.id = workspace_value
38
+ ) AND NOT EXISTS (
39
+ SELECT 1 FROM %1$I.organization_memberships membership
40
+ WHERE membership.account_id = p_account_id
41
+ AND membership.personal_workspace_id = workspace_value
42
+ ) INTO visible;
43
+ ELSIF subject_value IS NOT NULL AND (
44
+ subject_value LIKE 'user:%%'
45
+ OR (
46
+ subject_value = 'dev'
47
+ AND EXISTS (
48
+ SELECT 1 FROM %1$I.managed_accounts account
49
+ WHERE account.id = p_account_id
50
+ AND account.external_source = 'opengeni:local'
51
+ AND account.external_id = 'default'
52
+ )
53
+ )
54
+ ) THEN
55
+ SELECT EXISTS (
56
+ SELECT 1 FROM %1$I.organization_memberships membership
57
+ WHERE membership.account_id = p_account_id
58
+ AND membership.subject_id = subject_value
59
+ AND membership.status = 'active'
60
+ AND membership.role IN ('owner', 'admin')
61
+ ) INTO visible;
62
+ END IF;
63
+ PERFORM pg_catalog.set_config(
64
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
65
+ );
66
+ RETURN coalesce(visible, false);
67
+ EXCEPTION WHEN OTHERS THEN
68
+ PERFORM pg_catalog.set_config(
69
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
70
+ );
71
+ RAISE;
72
+ END
73
+ $function$;
74
+ $ddl$, data_schema);
75
+ END
76
+ $scope_visibility$;
77
+
78
+ CREATE TABLE organization_model_provider_connections (
79
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
80
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
81
+ provider_kind text NOT NULL CHECK (provider_kind IN ('vercel_gateway', 'openrouter')),
82
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
83
+ credential_encrypted text NOT NULL,
84
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
85
+ operation_id uuid NOT NULL,
86
+ request_hash text NOT NULL CHECK (request_hash ~ '^[a-f0-9]{64}$'),
87
+ updated_by_subject_id text NOT NULL CHECK (octet_length(updated_by_subject_id) BETWEEN 1 AND 1024),
88
+ created_at timestamptz NOT NULL DEFAULT now(),
89
+ updated_at timestamptz NOT NULL DEFAULT now(),
90
+ UNIQUE (account_id, provider_kind),
91
+ UNIQUE (account_id, provider_kind, operation_id)
92
+ );
93
+
94
+ CREATE TABLE organization_model_provider_connection_operations (
95
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
96
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
97
+ provider_kind text NOT NULL CHECK (provider_kind IN ('vercel_gateway', 'openrouter')),
98
+ operation_id uuid NOT NULL,
99
+ request_hash text NOT NULL CHECK (request_hash ~ '^[a-f0-9]{64}$'),
100
+ result_status text NOT NULL CHECK (result_status IN ('active', 'revoked')),
101
+ result_version integer NOT NULL CHECK (result_version > 0),
102
+ result_created_at timestamptz NOT NULL,
103
+ result_updated_at timestamptz NOT NULL,
104
+ created_at timestamptz NOT NULL DEFAULT now(),
105
+ UNIQUE (account_id, provider_kind, operation_id)
106
+ );
107
+
108
+ CREATE TABLE organization_model_provider_custom_models (
109
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
110
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
111
+ provider_kind text NOT NULL CHECK (provider_kind IN ('vercel_gateway', 'openrouter')),
112
+ upstream_model_id text NOT NULL CHECK (
113
+ octet_length(upstream_model_id) BETWEEN 1 AND 238
114
+ AND upstream_model_id ~ '^[!-~]+$' AND upstream_model_id !~ '[|]'
115
+ ),
116
+ label text CHECK (
117
+ label IS NULL OR (octet_length(label) BETWEEN 1 AND 128 AND label !~ '[\r\n|]')
118
+ ),
119
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
120
+ create_operation_id uuid NOT NULL,
121
+ create_request_hash text NOT NULL CHECK (create_request_hash ~ '^[a-f0-9]{64}$'),
122
+ delete_operation_id uuid,
123
+ delete_request_hash text,
124
+ created_by_subject_id text NOT NULL CHECK (octet_length(created_by_subject_id) BETWEEN 1 AND 1024),
125
+ retired_at timestamptz,
126
+ created_at timestamptz NOT NULL DEFAULT now(),
127
+ updated_at timestamptz NOT NULL DEFAULT now(),
128
+ CHECK (
129
+ (delete_operation_id IS NULL AND delete_request_hash IS NULL)
130
+ OR (delete_operation_id IS NOT NULL AND delete_request_hash ~ '^[a-f0-9]{64}$' AND retired_at IS NOT NULL)
131
+ ),
132
+ UNIQUE (account_id, provider_kind, create_operation_id)
133
+ );
134
+ CREATE UNIQUE INDEX organization_model_provider_custom_models_active_uq
135
+ ON organization_model_provider_custom_models(account_id, provider_kind, upstream_model_id)
136
+ WHERE retired_at IS NULL;
137
+ CREATE UNIQUE INDEX organization_model_provider_custom_models_delete_operation_uq
138
+ ON organization_model_provider_custom_models(account_id, provider_kind, delete_operation_id)
139
+ WHERE delete_operation_id IS NOT NULL;
140
+
141
+ ALTER TABLE organization_model_provider_connections ENABLE ROW LEVEL SECURITY;
142
+ ALTER TABLE organization_model_provider_connections FORCE ROW LEVEL SECURITY;
143
+ ALTER TABLE organization_model_provider_connection_operations ENABLE ROW LEVEL SECURITY;
144
+ ALTER TABLE organization_model_provider_connection_operations FORCE ROW LEVEL SECURITY;
145
+ ALTER TABLE organization_model_provider_custom_models ENABLE ROW LEVEL SECURITY;
146
+ ALTER TABLE organization_model_provider_custom_models FORCE ROW LEVEL SECURITY;
147
+
148
+ CREATE POLICY organization_model_provider_scope
149
+ ON organization_model_provider_connections
150
+ USING (opengeni_private.organization_model_provider_scope_visible(account_id))
151
+ WITH CHECK (opengeni_private.organization_model_provider_scope_visible(account_id));
152
+ CREATE POLICY organization_model_provider_scope
153
+ ON organization_model_provider_connection_operations
154
+ USING (opengeni_private.organization_model_provider_scope_visible(account_id))
155
+ WITH CHECK (opengeni_private.organization_model_provider_scope_visible(account_id));
156
+ CREATE POLICY organization_model_provider_scope
157
+ ON organization_model_provider_custom_models
158
+ USING (opengeni_private.organization_model_provider_scope_visible(account_id))
159
+ WITH CHECK (opengeni_private.organization_model_provider_scope_visible(account_id));
160
+
161
+ REVOKE ALL ON organization_model_provider_connections FROM PUBLIC;
162
+ REVOKE ALL ON organization_model_provider_connection_operations FROM PUBLIC;
163
+ REVOKE ALL ON organization_model_provider_custom_models FROM PUBLIC;
164
+ REVOKE ALL ON FUNCTION opengeni_private.organization_model_provider_scope_visible(uuid) FROM PUBLIC;
165
+
166
+ DO $application_grants$
167
+ DECLARE
168
+ data_schema text := current_schema();
169
+ application_role text;
170
+ BEGIN
171
+ FOR application_role IN
172
+ SELECT role_value.rolname
173
+ FROM pg_catalog.jsonb_array_elements_text(
174
+ coalesce(nullif(current_setting('opengeni.migration_application_roles', true), ''), '[]')::jsonb
175
+ ) configured(value)
176
+ JOIN pg_catalog.pg_roles role_value ON role_value.rolname = configured.value
177
+ UNION SELECT 'opengeni_app'
178
+ WHERE EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app')
179
+ LOOP
180
+ EXECUTE format(
181
+ 'GRANT SELECT, INSERT, UPDATE ON TABLE %1$I.organization_model_provider_connections, %1$I.organization_model_provider_connection_operations, %1$I.organization_model_provider_custom_models TO %2$I',
182
+ data_schema, application_role
183
+ );
184
+ EXECUTE format(
185
+ 'GRANT EXECUTE ON FUNCTION opengeni_private.organization_model_provider_scope_visible(uuid) TO %I',
186
+ application_role
187
+ );
188
+ END LOOP;
189
+ END
190
+ $application_grants$;
191
+
192
+ COMMENT ON TABLE organization_model_provider_connections IS
193
+ 'Encrypted organization-owned Vercel AI Gateway/OpenRouter credentials inherited by shared workspaces only.';
194
+ COMMENT ON TABLE organization_model_provider_connection_operations IS
195
+ 'Durable secret-free idempotency receipts for organization provider connection mutations.';
196
+ COMMENT ON TABLE organization_model_provider_custom_models IS
197
+ 'Retained organization provider model generations; active rows are fresh-selection authority.';