@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,691 @@
1
+ -- deployment-mode: rolling
2
+ -- Migration 0399 follows organization workspace management activation.
3
+ -- Lets an already-onboarded, verified managed human create another independent
4
+ -- organization. One transaction creates the organization, its Personal
5
+ -- workspace, its first shared workspace, and both access anchors. The original
6
+ -- first-sign-in setup remains a separate one-shot lifecycle so invitations and
7
+ -- legacy-account adoption keep their existing precedence and semantics.
8
+
9
+ SET LOCAL lock_timeout = '5s';
10
+ SET LOCAL statement_timeout = '10min';
11
+
12
+ CREATE TABLE additional_organization_creation_receipts (
13
+ operation_id uuid PRIMARY KEY,
14
+ actor_subject_id text NOT NULL,
15
+ account_id uuid NOT NULL UNIQUE
16
+ REFERENCES managed_accounts(id) ON DELETE RESTRICT,
17
+ organization_membership_id uuid NOT NULL UNIQUE
18
+ REFERENCES organization_memberships(id) ON DELETE RESTRICT,
19
+ personal_workspace_id uuid NOT NULL UNIQUE
20
+ REFERENCES workspaces(id) ON DELETE RESTRICT,
21
+ shared_workspace_id uuid NOT NULL UNIQUE
22
+ REFERENCES workspaces(id) ON DELETE RESTRICT,
23
+ organization_name text NOT NULL,
24
+ workspace_name text NOT NULL,
25
+ result jsonb NOT NULL,
26
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
27
+ CONSTRAINT additional_organization_creation_actor_check CHECK (
28
+ actor_subject_id LIKE 'user:%'
29
+ AND octet_length(convert_to(actor_subject_id, 'UTF8')) BETWEEN 6 AND 1024
30
+ ),
31
+ CONSTRAINT additional_organization_creation_names_check CHECK (
32
+ organization_name = btrim(organization_name)
33
+ AND char_length(organization_name) BETWEEN 1 AND 120
34
+ AND octet_length(convert_to(organization_name, 'UTF8')) BETWEEN 1 AND 480
35
+ AND workspace_name = btrim(workspace_name)
36
+ AND char_length(workspace_name) BETWEEN 1 AND 120
37
+ AND octet_length(convert_to(workspace_name, 'UTF8')) BETWEEN 1 AND 480
38
+ ),
39
+ CONSTRAINT additional_organization_creation_distinct_workspaces_check CHECK (
40
+ personal_workspace_id <> shared_workspace_id
41
+ ),
42
+ CONSTRAINT additional_organization_creation_result_check CHECK (
43
+ jsonb_typeof(result) = 'object'
44
+ )
45
+ );
46
+ CREATE INDEX additional_organization_creation_receipts_actor_subject_idx
47
+ ON additional_organization_creation_receipts (actor_subject_id);
48
+ ALTER TABLE additional_organization_creation_receipts ENABLE ROW LEVEL SECURITY;
49
+ ALTER TABLE additional_organization_creation_receipts FORCE ROW LEVEL SECURITY;
50
+ REVOKE ALL ON TABLE additional_organization_creation_receipts FROM PUBLIC;
51
+ CREATE POLICY additional_organization_creation_lifecycle
52
+ ON additional_organization_creation_receipts
53
+ USING (current_setting('opengeni.organization_tenancy_lifecycle', true)
54
+ = 'additional_organization_creation')
55
+ WITH CHECK (current_setting('opengeni.organization_tenancy_lifecycle', true)
56
+ = 'additional_organization_creation');
57
+
58
+ CREATE TRIGGER additional_organization_creation_receipts_immutable
59
+ BEFORE UPDATE OR DELETE ON additional_organization_creation_receipts
60
+ FOR EACH ROW EXECUTE FUNCTION opengeni_private.organization_membership_history_immutable();
61
+
62
+ CREATE TABLE session_tenancy_additional_organization_activation_evidence (
63
+ account_id uuid PRIMARY KEY
64
+ REFERENCES session_tenancy_activations(account_id) ON DELETE RESTRICT,
65
+ eligibility_version integer NOT NULL,
66
+ creation_operation_id uuid NOT NULL UNIQUE
67
+ REFERENCES additional_organization_creation_receipts(operation_id) ON DELETE RESTRICT,
68
+ actor_subject_id text NOT NULL,
69
+ personal_workspace_id uuid NOT NULL UNIQUE,
70
+ shared_workspace_id uuid NOT NULL UNIQUE,
71
+ organization_membership_id uuid NOT NULL UNIQUE,
72
+ boundary_witness_account_id uuid NOT NULL
73
+ REFERENCES session_tenancy_activations(account_id) ON DELETE RESTRICT,
74
+ boundary_witness_activated_at timestamptz NOT NULL,
75
+ graph_evidence jsonb NOT NULL,
76
+ graph_digest text NOT NULL,
77
+ activation_parity_digest text NOT NULL,
78
+ private_setting_version bigint NOT NULL,
79
+ private_setting_event_id uuid NOT NULL UNIQUE
80
+ REFERENCES organization_private_session_setting_events(id) ON DELETE RESTRICT,
81
+ activated_at timestamptz NOT NULL,
82
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
83
+ CONSTRAINT session_tenancy_additional_organization_eligibility_check CHECK (
84
+ eligibility_version = 1
85
+ ),
86
+ CONSTRAINT session_tenancy_additional_organization_actor_check CHECK (
87
+ actor_subject_id LIKE 'user:%'
88
+ ),
89
+ CONSTRAINT session_tenancy_additional_organization_workspaces_check CHECK (
90
+ personal_workspace_id <> shared_workspace_id
91
+ ),
92
+ CONSTRAINT session_tenancy_additional_organization_digests_check CHECK (
93
+ graph_digest ~ '^[0-9a-f]{64}$'
94
+ AND activation_parity_digest ~ '^[0-9a-f]{64}$'
95
+ ),
96
+ CONSTRAINT session_tenancy_additional_organization_witness_check CHECK (
97
+ boundary_witness_account_id <> account_id
98
+ ),
99
+ CONSTRAINT session_tenancy_additional_organization_graph_check CHECK (
100
+ jsonb_typeof(graph_evidence) = 'object'
101
+ ),
102
+ CONSTRAINT session_tenancy_additional_organization_setting_version_check CHECK (
103
+ private_setting_version > 0
104
+ ),
105
+ CONSTRAINT session_tenancy_additional_organization_personal_workspace_fk
106
+ FOREIGN KEY (personal_workspace_id)
107
+ REFERENCES additional_organization_creation_receipts(personal_workspace_id)
108
+ ON DELETE RESTRICT,
109
+ CONSTRAINT session_tenancy_additional_organization_shared_workspace_fk
110
+ FOREIGN KEY (shared_workspace_id)
111
+ REFERENCES additional_organization_creation_receipts(shared_workspace_id)
112
+ ON DELETE RESTRICT,
113
+ CONSTRAINT session_tenancy_additional_organization_membership_fk
114
+ FOREIGN KEY (organization_membership_id)
115
+ REFERENCES additional_organization_creation_receipts(organization_membership_id)
116
+ ON DELETE RESTRICT
117
+ );
118
+ ALTER TABLE session_tenancy_additional_organization_activation_evidence
119
+ ENABLE ROW LEVEL SECURITY;
120
+ ALTER TABLE session_tenancy_additional_organization_activation_evidence
121
+ FORCE ROW LEVEL SECURITY;
122
+ REVOKE ALL ON TABLE session_tenancy_additional_organization_activation_evidence FROM PUBLIC;
123
+ CREATE POLICY session_tenancy_additional_organization_activation_lifecycle
124
+ ON session_tenancy_additional_organization_activation_evidence
125
+ USING (current_setting('opengeni.organization_tenancy_lifecycle', true)
126
+ = 'session_tenancy_greenfield_activation')
127
+ WITH CHECK (current_setting('opengeni.organization_tenancy_lifecycle', true)
128
+ = 'session_tenancy_greenfield_activation');
129
+
130
+ CREATE FUNCTION activate_session_tenancy_from_additional_organization(
131
+ p_operation_id uuid
132
+ ) RETURNS boolean
133
+ LANGUAGE plpgsql VOLATILE SECURITY DEFINER
134
+ SET search_path FROM CURRENT
135
+ AS $activation$
136
+ DECLARE
137
+ previous_lifecycle text := pg_catalog.current_setting(
138
+ 'opengeni.organization_tenancy_lifecycle', true
139
+ );
140
+ creation additional_organization_creation_receipts%ROWTYPE;
141
+ account_row managed_accounts%ROWTYPE;
142
+ organization_membership organization_memberships%ROWTYPE;
143
+ personal_workspace workspaces%ROWTYPE;
144
+ shared_workspace workspaces%ROWTYPE;
145
+ shared_access workspace_memberships%ROWTYPE;
146
+ witness session_tenancy_activations%ROWTYPE;
147
+ inserted_activation session_tenancy_activations%ROWTYPE;
148
+ existing_evidence session_tenancy_additional_organization_activation_evidence%ROWTYPE;
149
+ account_inserted_in_current_transaction boolean;
150
+ account_never_updated boolean;
151
+ organization_membership_count integer;
152
+ workspace_count integer;
153
+ workspace_control_count integer;
154
+ personal_workspace_control_count integer;
155
+ shared_workspace_control_count integer;
156
+ workspace_membership_count integer;
157
+ graph_evidence_value jsonb;
158
+ graph_digest_value text;
159
+ parity_digest_value text;
160
+ private_setting_event_id_value uuid := pg_catalog.gen_random_uuid();
161
+ BEGIN
162
+ IF p_operation_id IS NULL THEN
163
+ RAISE EXCEPTION 'additional organization activation operation is invalid'
164
+ USING ERRCODE = '22023';
165
+ END IF;
166
+
167
+ SELECT * INTO creation
168
+ FROM additional_organization_creation_receipts receipt
169
+ WHERE receipt.operation_id = p_operation_id
170
+ FOR KEY SHARE;
171
+ IF NOT FOUND
172
+ OR creation.actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
173
+ OR creation.account_id IS DISTINCT FROM opengeni_private.current_account_id()
174
+ OR creation.result ->> 'organizationId' IS DISTINCT FROM creation.account_id::text
175
+ OR creation.result ->> 'personalWorkspaceId'
176
+ IS DISTINCT FROM creation.personal_workspace_id::text
177
+ OR creation.result ->> 'workspaceId' IS DISTINCT FROM creation.shared_workspace_id::text
178
+ THEN
179
+ RAISE EXCEPTION 'additional organization activation receipt is invalid'
180
+ USING ERRCODE = '55000';
181
+ END IF;
182
+
183
+ PERFORM pg_catalog.set_config(
184
+ 'opengeni.organization_tenancy_lifecycle',
185
+ 'organization_membership_lifecycle', true
186
+ );
187
+
188
+ SELECT * INTO account_row
189
+ FROM managed_accounts account
190
+ WHERE account.id = creation.account_id
191
+ FOR KEY SHARE;
192
+ IF NOT FOUND
193
+ OR account_row.external_source IS DISTINCT FROM 'opengeni:additional-organization'
194
+ OR account_row.external_id IS DISTINCT FROM creation.operation_id::text
195
+ OR account_row.name IS DISTINCT FROM creation.organization_name
196
+ THEN
197
+ RAISE EXCEPTION 'additional organization activation requires its exact new account'
198
+ USING ERRCODE = '55000';
199
+ END IF;
200
+ SELECT
201
+ account.xmin::text::bigint = pg_catalog.pg_current_xact_id()::text::bigint,
202
+ account.created_at = account.updated_at
203
+ INTO account_inserted_in_current_transaction, account_never_updated
204
+ FROM managed_accounts account
205
+ WHERE account.id = creation.account_id;
206
+ IF account_inserted_in_current_transaction IS NOT TRUE
207
+ OR account_never_updated IS NOT TRUE
208
+ THEN
209
+ RAISE EXCEPTION 'additional organization activation requires a newly inserted account'
210
+ USING ERRCODE = '55000';
211
+ END IF;
212
+
213
+ SELECT * INTO organization_membership
214
+ FROM organization_memberships membership
215
+ WHERE membership.id = creation.organization_membership_id
216
+ AND membership.account_id = creation.account_id
217
+ FOR KEY SHARE;
218
+ SELECT count(*)::integer INTO organization_membership_count
219
+ FROM organization_memberships membership
220
+ WHERE membership.account_id = creation.account_id;
221
+ IF organization_membership.id IS NULL
222
+ OR organization_membership_count <> 1
223
+ OR organization_membership.subject_id IS DISTINCT FROM creation.actor_subject_id
224
+ OR organization_membership.role IS DISTINCT FROM 'owner'
225
+ OR organization_membership.status IS DISTINCT FROM 'active'
226
+ OR organization_membership.personal_workspace_id
227
+ IS DISTINCT FROM creation.personal_workspace_id
228
+ THEN
229
+ RAISE EXCEPTION 'additional organization membership graph is invalid'
230
+ USING ERRCODE = '55000';
231
+ END IF;
232
+
233
+ PERFORM pg_catalog.set_config(
234
+ 'opengeni.workspace_id', creation.personal_workspace_id::text, true
235
+ );
236
+ SELECT * INTO personal_workspace
237
+ FROM workspaces workspace
238
+ WHERE workspace.id = creation.personal_workspace_id
239
+ AND workspace.account_id = creation.account_id
240
+ FOR KEY SHARE;
241
+ SELECT count(*)::integer INTO personal_workspace_control_count
242
+ FROM workspace_inference_controls control
243
+ WHERE control.account_id = creation.account_id
244
+ AND control.workspace_id = creation.personal_workspace_id;
245
+
246
+ PERFORM pg_catalog.set_config(
247
+ 'opengeni.workspace_id', creation.shared_workspace_id::text, true
248
+ );
249
+ SELECT * INTO shared_workspace
250
+ FROM workspaces workspace
251
+ WHERE workspace.id = creation.shared_workspace_id
252
+ AND workspace.account_id = creation.account_id
253
+ FOR KEY SHARE;
254
+ SELECT count(*)::integer INTO workspace_count
255
+ FROM workspaces workspace WHERE workspace.account_id = creation.account_id;
256
+ SELECT count(*)::integer INTO shared_workspace_control_count
257
+ FROM workspace_inference_controls control
258
+ WHERE control.account_id = creation.account_id
259
+ AND control.workspace_id = creation.shared_workspace_id;
260
+ workspace_control_count :=
261
+ personal_workspace_control_count + shared_workspace_control_count;
262
+ SELECT count(*)::integer INTO workspace_membership_count
263
+ FROM workspace_memberships membership
264
+ WHERE membership.account_id = creation.account_id;
265
+ SELECT * INTO shared_access
266
+ FROM workspace_memberships membership
267
+ WHERE membership.account_id = creation.account_id
268
+ AND membership.workspace_id = creation.shared_workspace_id
269
+ AND membership.subject_id = creation.actor_subject_id
270
+ FOR KEY SHARE;
271
+ IF personal_workspace.id IS NULL
272
+ OR shared_workspace.id IS NULL
273
+ OR shared_access.id IS NULL
274
+ OR workspace_count <> 2
275
+ OR workspace_control_count <> 2
276
+ OR workspace_membership_count <> 1
277
+ OR personal_workspace.name IS DISTINCT FROM 'Personal workspace'
278
+ OR personal_workspace.slug IS NOT NULL
279
+ OR personal_workspace.external_source IS DISTINCT FROM
280
+ 'opengeni:organization-membership'
281
+ OR personal_workspace.external_id IS DISTINCT FROM
282
+ creation.account_id::text || ':' || creation.actor_subject_id
283
+ OR shared_workspace.name IS DISTINCT FROM creation.workspace_name
284
+ OR shared_workspace.slug IS NOT NULL
285
+ OR shared_workspace.external_source IS DISTINCT FROM
286
+ 'opengeni:additional-organization-default'
287
+ OR shared_workspace.external_id IS DISTINCT FROM creation.account_id::text
288
+ OR shared_access.role IS DISTINCT FROM 'admin'
289
+ OR shared_access.permissions IS DISTINCT FROM
290
+ opengeni_private.workspace_member_role_permissions('admin')
291
+ THEN
292
+ RAISE EXCEPTION 'additional organization workspace graph is invalid'
293
+ USING ERRCODE = '55000';
294
+ END IF;
295
+
296
+ graph_evidence_value := pg_catalog.jsonb_build_object(
297
+ 'schemaVersion', 1,
298
+ 'kind', 'additional_organization_with_first_shared_workspace',
299
+ 'accountId', creation.account_id,
300
+ 'operationId', creation.operation_id,
301
+ 'actorSubjectId', creation.actor_subject_id,
302
+ 'organizationMembershipId', creation.organization_membership_id,
303
+ 'personalWorkspaceId', creation.personal_workspace_id,
304
+ 'sharedWorkspaceId', creation.shared_workspace_id,
305
+ 'organizationMembershipCount', organization_membership_count,
306
+ 'workspaceCount', workspace_count,
307
+ 'workspaceControlCount', workspace_control_count,
308
+ 'workspaceMembershipCount', workspace_membership_count
309
+ );
310
+ graph_digest_value := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
311
+ graph_evidence_value::text, 'UTF8'
312
+ )), 'hex');
313
+
314
+ -- The exact graph is locked before the same deployment-wide fence used by
315
+ -- operator and first-signup activation. A creation that wins the fence stays
316
+ -- operator-managed; one after the first product witness activates atomically.
317
+ PERFORM lock_session_tenancy_activation_boundary();
318
+ PERFORM pg_catalog.set_config(
319
+ 'opengeni.organization_tenancy_lifecycle',
320
+ 'session_tenancy_greenfield_activation', true
321
+ );
322
+
323
+ SELECT * INTO existing_evidence
324
+ FROM session_tenancy_additional_organization_activation_evidence evidence
325
+ WHERE evidence.account_id = creation.account_id;
326
+ IF FOUND THEN
327
+ IF existing_evidence.creation_operation_id IS DISTINCT FROM creation.operation_id
328
+ OR existing_evidence.actor_subject_id IS DISTINCT FROM creation.actor_subject_id
329
+ OR existing_evidence.personal_workspace_id IS DISTINCT FROM creation.personal_workspace_id
330
+ OR existing_evidence.shared_workspace_id IS DISTINCT FROM creation.shared_workspace_id
331
+ OR existing_evidence.organization_membership_id
332
+ IS DISTINCT FROM creation.organization_membership_id
333
+ OR existing_evidence.graph_digest IS DISTINCT FROM graph_digest_value
334
+ THEN
335
+ RAISE EXCEPTION 'additional organization activation evidence conflicts with creation'
336
+ USING ERRCODE = '23505';
337
+ END IF;
338
+ PERFORM pg_catalog.set_config(
339
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
340
+ );
341
+ RETURN true;
342
+ END IF;
343
+
344
+ SELECT * INTO witness
345
+ FROM session_tenancy_activations activation
346
+ WHERE activation.account_id <> creation.account_id
347
+ ORDER BY activation.activated_at, activation.account_id
348
+ LIMIT 1;
349
+ IF NOT FOUND THEN
350
+ PERFORM pg_catalog.set_config(
351
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
352
+ );
353
+ RETURN false;
354
+ END IF;
355
+
356
+ IF EXISTS (
357
+ SELECT 1 FROM session_tenancy_activations activation
358
+ WHERE activation.account_id = creation.account_id
359
+ ) OR EXISTS (
360
+ SELECT 1 FROM organization_private_session_settings setting
361
+ WHERE setting.account_id = creation.account_id
362
+ ) THEN
363
+ RAISE EXCEPTION 'additional organization session tenancy already exists without evidence'
364
+ USING ERRCODE = '55000';
365
+ END IF;
366
+
367
+ parity_digest_value := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
368
+ pg_catalog.jsonb_build_object(
369
+ 'schemaVersion', 1,
370
+ 'graphDigest', graph_digest_value,
371
+ 'boundaryWitnessAccountId', witness.account_id,
372
+ 'boundaryWitnessActivatedAt', witness.activated_at
373
+ )::text,
374
+ 'UTF8'
375
+ )), 'hex');
376
+
377
+ PERFORM pg_catalog.set_config(
378
+ 'opengeni.organization_tenancy_lifecycle', 'session_tenancy_activation', true
379
+ );
380
+ INSERT INTO session_tenancy_activations (
381
+ account_id, activation_version, inventory_digest, parity_digest,
382
+ activated_by, backfill_receipt_ids
383
+ ) VALUES (
384
+ creation.account_id, 1, graph_digest_value, parity_digest_value,
385
+ 'opengeni:additional-organization-creation:v1', ARRAY[]::uuid[]
386
+ ) RETURNING * INTO inserted_activation;
387
+
388
+ PERFORM pg_catalog.set_config(
389
+ 'opengeni.organization_tenancy_lifecycle',
390
+ 'organization_private_session_settings', true
391
+ );
392
+ INSERT INTO organization_private_session_settings (
393
+ account_id, enabled, version, updated_by_membership_id, updated_at
394
+ ) VALUES (
395
+ creation.account_id, true, 1, creation.organization_membership_id,
396
+ inserted_activation.activated_at
397
+ );
398
+ INSERT INTO organization_private_session_setting_events (
399
+ id, account_id, actor_membership_id, requested_enabled,
400
+ expected_version, result_enabled, result_version, result_updated_at, changed
401
+ ) VALUES (
402
+ private_setting_event_id_value, creation.account_id,
403
+ creation.organization_membership_id, true, 0, true, 1,
404
+ inserted_activation.activated_at, true
405
+ );
406
+
407
+ PERFORM pg_catalog.set_config(
408
+ 'opengeni.organization_tenancy_lifecycle',
409
+ 'session_tenancy_greenfield_activation', true
410
+ );
411
+ INSERT INTO session_tenancy_additional_organization_activation_evidence (
412
+ account_id, eligibility_version, creation_operation_id, actor_subject_id,
413
+ personal_workspace_id, shared_workspace_id, organization_membership_id,
414
+ boundary_witness_account_id, boundary_witness_activated_at,
415
+ graph_evidence, graph_digest, activation_parity_digest,
416
+ private_setting_version, private_setting_event_id, activated_at
417
+ ) VALUES (
418
+ creation.account_id, 1, creation.operation_id, creation.actor_subject_id,
419
+ creation.personal_workspace_id, creation.shared_workspace_id,
420
+ creation.organization_membership_id, witness.account_id, witness.activated_at,
421
+ graph_evidence_value, graph_digest_value, parity_digest_value,
422
+ 1, private_setting_event_id_value, inserted_activation.activated_at
423
+ );
424
+
425
+ PERFORM pg_catalog.set_config(
426
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
427
+ );
428
+ RETURN true;
429
+ EXCEPTION WHEN OTHERS THEN
430
+ PERFORM pg_catalog.set_config(
431
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
432
+ );
433
+ RAISE;
434
+ END
435
+ $activation$;
436
+ REVOKE ALL ON FUNCTION activate_session_tenancy_from_additional_organization(uuid)
437
+ FROM PUBLIC;
438
+
439
+ CREATE FUNCTION create_additional_managed_organization(
440
+ p_subject_id text,
441
+ p_subject_label text,
442
+ p_organization_name text,
443
+ p_workspace_name text,
444
+ p_operation_id uuid
445
+ ) RETURNS jsonb
446
+ LANGUAGE plpgsql VOLATILE SECURITY DEFINER
447
+ SET search_path FROM CURRENT
448
+ AS $creation$
449
+ DECLARE
450
+ additional_organization_limit constant integer := 10;
451
+ auth_user_id_value text := substring(p_subject_id from length('user:') + 1);
452
+ organization_name_value text := nullif(pg_catalog.btrim(p_organization_name), '');
453
+ workspace_name_value text := nullif(pg_catalog.btrim(p_workspace_name), '');
454
+ subject_label_value text := nullif(pg_catalog.btrim(p_subject_label), '');
455
+ auth_user auth_users%ROWTYPE;
456
+ prior additional_organization_creation_receipts%ROWTYPE;
457
+ account_row managed_accounts%ROWTYPE;
458
+ organization_membership organization_memberships%ROWTYPE;
459
+ personal_workspace workspaces%ROWTYPE;
460
+ shared_workspace workspaces%ROWTYPE;
461
+ created_organization_count integer;
462
+ public_result jsonb;
463
+ BEGIN
464
+ IF p_subject_id IS NULL
465
+ OR p_subject_id NOT LIKE 'user:%'
466
+ OR p_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
467
+ OR pg_catalog.octet_length(pg_catalog.convert_to(p_subject_id, 'UTF8'))
468
+ NOT BETWEEN 6 AND 1024
469
+ OR organization_name_value IS NULL
470
+ OR pg_catalog.char_length(organization_name_value) > 120
471
+ OR pg_catalog.octet_length(pg_catalog.convert_to(organization_name_value, 'UTF8')) > 480
472
+ OR workspace_name_value IS NULL
473
+ OR pg_catalog.char_length(workspace_name_value) > 120
474
+ OR pg_catalog.octet_length(pg_catalog.convert_to(workspace_name_value, 'UTF8')) > 480
475
+ OR (subject_label_value IS NOT NULL AND
476
+ pg_catalog.octet_length(pg_catalog.convert_to(subject_label_value, 'UTF8')) > 1024)
477
+ OR p_operation_id IS NULL
478
+ THEN
479
+ RAISE EXCEPTION 'additional organization creation input is invalid'
480
+ USING ERRCODE = '22023';
481
+ END IF;
482
+
483
+ SELECT * INTO auth_user
484
+ FROM auth_users candidate
485
+ WHERE candidate.id = auth_user_id_value
486
+ FOR KEY SHARE;
487
+ IF NOT FOUND OR auth_user.email_verified IS NOT TRUE THEN
488
+ RAISE EXCEPTION 'verified managed user required' USING ERRCODE = '42501';
489
+ END IF;
490
+
491
+ PERFORM pg_catalog.set_config(
492
+ 'opengeni.organization_tenancy_lifecycle',
493
+ 'organization_membership_lifecycle', true
494
+ );
495
+ IF NOT EXISTS (
496
+ SELECT 1 FROM organization_memberships membership
497
+ WHERE membership.subject_id = p_subject_id
498
+ AND membership.status = 'active'
499
+ ) THEN
500
+ RAISE EXCEPTION 'initial organization setup must be completed first'
501
+ USING ERRCODE = '42501';
502
+ END IF;
503
+
504
+ PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
505
+ 'additional-organization-creation:' || p_operation_id::text, 0
506
+ ));
507
+ PERFORM pg_catalog.set_config(
508
+ 'opengeni.organization_tenancy_lifecycle',
509
+ 'additional_organization_creation', true
510
+ );
511
+ SELECT * INTO prior
512
+ FROM additional_organization_creation_receipts receipt
513
+ WHERE receipt.operation_id = p_operation_id
514
+ FOR UPDATE;
515
+ IF FOUND THEN
516
+ IF prior.actor_subject_id IS DISTINCT FROM p_subject_id
517
+ OR prior.organization_name IS DISTINCT FROM organization_name_value
518
+ OR prior.workspace_name IS DISTINCT FROM workspace_name_value
519
+ THEN
520
+ RAISE EXCEPTION 'additional organization operation key was reused'
521
+ USING ERRCODE = '23505';
522
+ END IF;
523
+ RETURN prior.result;
524
+ END IF;
525
+
526
+ -- Fresh operation ids must not turn one verified login into an unbounded
527
+ -- tenant allocator. Serialize every create for the canonical subject, then
528
+ -- count only organizations allocated through this lifecycle. Invitations do
529
+ -- not consume the self-service allowance, and an exact committed retry above
530
+ -- continues to replay after the allowance is full.
531
+ PERFORM pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended(
532
+ 'additional-organization-creation-subject:' || p_subject_id, 0
533
+ ));
534
+ SELECT count(*)::integer INTO created_organization_count
535
+ FROM additional_organization_creation_receipts receipt
536
+ WHERE receipt.actor_subject_id = p_subject_id;
537
+ IF created_organization_count >= additional_organization_limit THEN
538
+ RAISE EXCEPTION 'additional organization limit reached'
539
+ USING ERRCODE = '54000';
540
+ END IF;
541
+
542
+ PERFORM pg_catalog.set_config(
543
+ 'opengeni.organization_tenancy_lifecycle',
544
+ 'organization_membership_lifecycle', true
545
+ );
546
+
547
+ INSERT INTO managed_accounts (name, external_source, external_id)
548
+ VALUES (
549
+ organization_name_value,
550
+ 'opengeni:additional-organization',
551
+ p_operation_id::text
552
+ ) RETURNING * INTO account_row;
553
+
554
+ PERFORM pg_catalog.pg_advisory_xact_lock(
555
+ pg_catalog.hashtextextended(
556
+ 'organization-membership:' || account_row.id::text, 0
557
+ )
558
+ );
559
+ PERFORM pg_catalog.set_config('opengeni.account_id', account_row.id::text, true);
560
+
561
+ personal_workspace.id := pg_catalog.gen_random_uuid();
562
+ PERFORM pg_catalog.set_config(
563
+ 'opengeni.workspace_id', personal_workspace.id::text, true
564
+ );
565
+ INSERT INTO workspaces (
566
+ id, account_id, name, slug, external_source, external_id
567
+ ) VALUES (
568
+ personal_workspace.id, account_row.id, 'Personal workspace', NULL,
569
+ 'opengeni:organization-membership',
570
+ account_row.id::text || ':' || p_subject_id
571
+ ) RETURNING * INTO personal_workspace;
572
+ INSERT INTO workspace_inference_controls (workspace_id, account_id)
573
+ VALUES (personal_workspace.id, account_row.id);
574
+
575
+ INSERT INTO organization_memberships (
576
+ account_id, subject_id, role, status, personal_workspace_id
577
+ ) VALUES (
578
+ account_row.id, p_subject_id, 'owner', 'active', personal_workspace.id
579
+ ) RETURNING * INTO organization_membership;
580
+
581
+ shared_workspace.id := pg_catalog.gen_random_uuid();
582
+ PERFORM pg_catalog.set_config(
583
+ 'opengeni.workspace_id', shared_workspace.id::text, true
584
+ );
585
+ INSERT INTO workspaces (
586
+ id, account_id, name, slug, external_source, external_id
587
+ ) VALUES (
588
+ shared_workspace.id, account_row.id, workspace_name_value, NULL,
589
+ 'opengeni:additional-organization-default', account_row.id::text
590
+ ) RETURNING * INTO shared_workspace;
591
+ INSERT INTO workspace_inference_controls (workspace_id, account_id)
592
+ VALUES (shared_workspace.id, account_row.id);
593
+ INSERT INTO workspace_memberships (
594
+ account_id, workspace_id, subject_id, subject_label, role, permissions
595
+ ) VALUES (
596
+ account_row.id, shared_workspace.id, p_subject_id, subject_label_value,
597
+ 'admin', opengeni_private.workspace_member_role_permissions('admin')
598
+ );
599
+
600
+ public_result := pg_catalog.jsonb_build_object(
601
+ 'organizationId', account_row.id,
602
+ 'organization', pg_catalog.jsonb_build_object(
603
+ 'id', account_row.id,
604
+ 'name', account_row.name,
605
+ 'createdAt', account_row.created_at,
606
+ 'updatedAt', account_row.updated_at
607
+ ),
608
+ 'workspaceId', shared_workspace.id,
609
+ 'personalWorkspaceId', personal_workspace.id
610
+ );
611
+ PERFORM pg_catalog.set_config(
612
+ 'opengeni.organization_tenancy_lifecycle',
613
+ 'additional_organization_creation', true
614
+ );
615
+ INSERT INTO additional_organization_creation_receipts (
616
+ operation_id, actor_subject_id, account_id, organization_membership_id,
617
+ personal_workspace_id, shared_workspace_id, organization_name,
618
+ workspace_name, result
619
+ ) VALUES (
620
+ p_operation_id, p_subject_id, account_row.id, organization_membership.id,
621
+ personal_workspace.id, shared_workspace.id, organization_name_value,
622
+ workspace_name_value, public_result
623
+ );
624
+
625
+ PERFORM activate_session_tenancy_from_additional_organization(p_operation_id);
626
+ RETURN public_result;
627
+ END
628
+ $creation$;
629
+
630
+ REVOKE ALL ON FUNCTION create_additional_managed_organization(
631
+ text, text, text, text, uuid
632
+ ) FROM PUBLIC;
633
+
634
+ DO $posture$
635
+ DECLARE
636
+ data_schema text := pg_catalog.current_schema();
637
+ BEGIN
638
+ EXECUTE pg_catalog.format(
639
+ 'ALTER FUNCTION %I.activate_session_tenancy_from_additional_organization(uuid) '
640
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
641
+ data_schema, data_schema
642
+ );
643
+ EXECUTE pg_catalog.format(
644
+ 'ALTER FUNCTION %I.create_additional_managed_organization(text,text,text,text,uuid) '
645
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
646
+ data_schema, data_schema
647
+ );
648
+ END
649
+ $posture$;
650
+
651
+ DO $application_grants$
652
+ DECLARE
653
+ data_schema text := pg_catalog.current_schema();
654
+ application_role text;
655
+ BEGIN
656
+ FOR application_role IN
657
+ SELECT role_value.rolname
658
+ FROM pg_catalog.jsonb_array_elements_text(
659
+ coalesce(nullif(current_setting('opengeni.migration_application_roles', true), ''), '[]')::jsonb
660
+ ) configured(value)
661
+ JOIN pg_catalog.pg_roles role_value ON role_value.rolname = configured.value
662
+ UNION SELECT 'opengeni_app'
663
+ WHERE EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app')
664
+ LOOP
665
+ EXECUTE pg_catalog.format(
666
+ 'GRANT EXECUTE ON FUNCTION %1$I.create_additional_managed_organization(text,text,text,text,uuid) TO %2$I',
667
+ data_schema, application_role
668
+ );
669
+ EXECUTE pg_catalog.format(
670
+ 'REVOKE ALL ON TABLE %1$I.additional_organization_creation_receipts, %1$I.session_tenancy_additional_organization_activation_evidence FROM %2$I',
671
+ data_schema, application_role
672
+ );
673
+ EXECUTE pg_catalog.format(
674
+ 'REVOKE ALL ON FUNCTION %1$I.activate_session_tenancy_from_additional_organization(uuid) FROM %2$I',
675
+ data_schema, application_role
676
+ );
677
+ END LOOP;
678
+ END
679
+ $application_grants$;
680
+
681
+ COMMENT ON TABLE additional_organization_creation_receipts IS
682
+ 'Immutable exact-command receipts for additional organization creation by an already-onboarded managed human.';
683
+ COMMENT ON TABLE session_tenancy_additional_organization_activation_evidence IS
684
+ 'Immutable graph, boundary-witness, private-setting, and activation evidence for a newly created additional organization.';
685
+ COMMENT ON FUNCTION create_additional_managed_organization(text, text, text, text, uuid) IS
686
+ 'Creates an additional organization, Personal workspace, first shared workspace, owner membership, and creator access atomically.';
687
+ COMMENT ON FUNCTION activate_session_tenancy_from_additional_organization(uuid) IS
688
+ 'Owner-only atomic activation for the exact fresh graph created by create_additional_managed_organization.';
689
+
690
+ RESET statement_timeout;
691
+ RESET lock_timeout;