@opengeni/db 3.2.0-canary.0 → 3.4.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 (81) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-SIBYBA2D.js → chunk-6DPJA36C.js} +30 -11
  3. package/dist/chunk-6DPJA36C.js.map +1 -0
  4. package/dist/{chunk-KRLPEMLH.js → chunk-C2AFP7SB.js} +184 -6
  5. package/dist/chunk-C2AFP7SB.js.map +1 -0
  6. package/dist/{chunk-AZHW7R5V.js → chunk-H7MXYG7R.js} +12 -8
  7. package/dist/chunk-H7MXYG7R.js.map +1 -0
  8. package/dist/{chunk-2PRS4J5Q.js → chunk-HY3F5Q4E.js} +3 -3
  9. package/dist/{chunk-NAB6FKFN.js → chunk-O6FTELKH.js} +3 -3
  10. package/dist/{chunk-UZNXG44K.js → chunk-PYMY3AJW.js} +324 -8
  11. package/dist/chunk-PYMY3AJW.js.map +1 -0
  12. package/dist/{chunk-6HMM2R5T.js → chunk-TRBOWUG3.js} +98 -4
  13. package/dist/chunk-TRBOWUG3.js.map +1 -0
  14. package/dist/{chunk-E6CLMKEE.js → chunk-V3TRUXFJ.js} +2 -2
  15. package/dist/{chunk-2EH4WRHL.js → chunk-ZWRIPCTR.js} +2 -2
  16. package/dist/editable-artifact-durable-export.js +2 -2
  17. package/dist/editable-artifacts.js +3 -3
  18. package/dist/governed-learning-activation.d.ts +16 -0
  19. package/dist/index.d.ts +108 -7
  20. package/dist/index.js +3605 -1417
  21. package/dist/index.js.map +1 -1
  22. package/dist/insights-model-bundle.d.ts +30 -0
  23. package/dist/insights-usage-bundle.d.ts +32 -0
  24. package/dist/insights.d.ts +10 -2
  25. package/dist/lossless-json.d.ts +1 -1
  26. package/dist/managed-user-setup.d.ts +82 -0
  27. package/dist/memory-domain.d.ts +2 -2
  28. package/dist/migrate.d.ts +3 -1
  29. package/dist/migrate.js +1 -1
  30. package/dist/new-session-drafts.d.ts +4 -4
  31. package/dist/organization-membership-lifecycle.d.ts +60 -1
  32. package/dist/pr-review.d.ts +44 -3
  33. package/dist/provision-roles.js +1 -1
  34. package/dist/runtime-posture.d.ts +12 -7
  35. package/dist/schema.d.ts +839 -101
  36. package/dist/schema.js +9 -1
  37. package/dist/session-background-commands.js +3 -3
  38. package/dist/session-tenancy.d.ts +12 -1
  39. package/dist/session-tenancy.js +3 -3
  40. package/dist/video-generation.js +3 -3
  41. package/drizzle/0348_named_signup_and_user_setup.sql +838 -0
  42. package/drizzle/0349_greenfield_session_tenancy_activation.sql +792 -0
  43. package/drizzle/0350_organization_shared_workspace_administration.sql +889 -0
  44. package/drizzle/0351_organization_user_setup_delivery.sql +978 -0
  45. package/drizzle/0352_session_variable_set_attachments.sql +1661 -0
  46. package/drizzle/0353_automatic_session_title_policy_fence.sql +476 -0
  47. package/drizzle/0354_automatic_session_title_quarantine_index.sql +9 -0
  48. package/drizzle/0355_automatic_session_title_quarantine.sql +92 -0
  49. package/drizzle/0356_set_based_insights_session_visibility.sql +237 -0
  50. package/drizzle/0357_rig_platform_base_only.sql +44 -0
  51. package/drizzle/0358_pr_review_managed_github_app.sql +108 -0
  52. package/drizzle/0359_insights_force_rls_read_capability.sql +936 -0
  53. package/drizzle/0360_organization_identity_confirmation_prompt.sql +103 -0
  54. package/drizzle/0361_remember_knowledge_memory_materialization.sql +249 -0
  55. package/package.json +6 -6
  56. package/src/database.ts +12 -8
  57. package/src/governed-learning-activation.ts +74 -2
  58. package/src/index.ts +879 -92
  59. package/src/insights-model-bundle.ts +681 -0
  60. package/src/insights-usage-bundle.ts +240 -0
  61. package/src/insights.ts +265 -152
  62. package/src/lossless-json.ts +2 -1
  63. package/src/managed-user-setup.ts +316 -0
  64. package/src/memory-domain.ts +3 -3
  65. package/src/migrate.ts +38 -14
  66. package/src/new-session-drafts.ts +22 -15
  67. package/src/organization-membership-lifecycle.ts +253 -1
  68. package/src/pr-review.ts +449 -6
  69. package/src/provision-roles.ts +92 -0
  70. package/src/runtime-posture.ts +247 -4
  71. package/src/schema.ts +210 -4
  72. package/src/session-tenancy.ts +120 -2
  73. package/dist/chunk-6HMM2R5T.js.map +0 -1
  74. package/dist/chunk-AZHW7R5V.js.map +0 -1
  75. package/dist/chunk-KRLPEMLH.js.map +0 -1
  76. package/dist/chunk-SIBYBA2D.js.map +0 -1
  77. package/dist/chunk-UZNXG44K.js.map +0 -1
  78. /package/dist/{chunk-2PRS4J5Q.js.map → chunk-HY3F5Q4E.js.map} +0 -0
  79. /package/dist/{chunk-NAB6FKFN.js.map → chunk-O6FTELKH.js.map} +0 -0
  80. /package/dist/{chunk-E6CLMKEE.js.map → chunk-V3TRUXFJ.js.map} +0 -0
  81. /package/dist/{chunk-2EH4WRHL.js.map → chunk-ZWRIPCTR.js.map} +0 -0
@@ -0,0 +1,792 @@
1
+ -- deployment-mode: rolling
2
+ -- Automatically activates only a newly inserted self-service organization
3
+ -- after the deployment has crossed the irreversible session-tenancy boundary.
4
+ -- Existing organizations, including the explicit 0348 orphan-account adoption
5
+ -- path, remain on the drained operator inventory/parity/backfill procedure.
6
+
7
+ SET LOCAL lock_timeout = '5s';
8
+ SET LOCAL statement_timeout = '10min';
9
+
10
+ CREATE TABLE session_tenancy_greenfield_activation_evidence (
11
+ account_id uuid PRIMARY KEY
12
+ REFERENCES session_tenancy_activations(account_id) ON DELETE RESTRICT,
13
+ eligibility_version integer NOT NULL,
14
+ setup_auth_user_id text NOT NULL UNIQUE
15
+ REFERENCES self_service_organization_setup_receipts(auth_user_id) ON DELETE RESTRICT,
16
+ setup_operation_id uuid NOT NULL UNIQUE
17
+ REFERENCES self_service_organization_setup_receipts(operation_id) ON DELETE RESTRICT,
18
+ setup_request_fingerprint text NOT NULL,
19
+ personal_workspace_id uuid NOT NULL UNIQUE
20
+ REFERENCES self_service_organization_setup_receipts(personal_workspace_id)
21
+ ON DELETE RESTRICT,
22
+ organization_membership_id uuid NOT NULL UNIQUE
23
+ REFERENCES self_service_organization_setup_receipts(organization_membership_id)
24
+ ON DELETE RESTRICT,
25
+ boundary_witness_account_id uuid NOT NULL
26
+ REFERENCES session_tenancy_activations(account_id) ON DELETE RESTRICT,
27
+ boundary_witness_activated_at timestamptz NOT NULL,
28
+ graph_evidence jsonb NOT NULL,
29
+ graph_digest text NOT NULL,
30
+ activation_parity_digest text NOT NULL,
31
+ private_setting_version bigint NOT NULL,
32
+ private_setting_event_id uuid NOT NULL UNIQUE
33
+ REFERENCES organization_private_session_setting_events(id) ON DELETE RESTRICT,
34
+ activated_at timestamptz NOT NULL,
35
+ created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
36
+ CONSTRAINT session_tenancy_greenfield_eligibility_version_check CHECK (
37
+ eligibility_version = 1
38
+ ),
39
+ CONSTRAINT session_tenancy_greenfield_fingerprint_check CHECK (
40
+ setup_request_fingerprint ~ '^[0-9a-f]{64}$'
41
+ ),
42
+ CONSTRAINT session_tenancy_greenfield_digests_check CHECK (
43
+ graph_digest ~ '^[0-9a-f]{64}$'
44
+ AND activation_parity_digest ~ '^[0-9a-f]{64}$'
45
+ ),
46
+ CONSTRAINT session_tenancy_greenfield_witness_check CHECK (
47
+ boundary_witness_account_id <> account_id
48
+ ),
49
+ CONSTRAINT session_tenancy_greenfield_graph_check CHECK (
50
+ pg_catalog.jsonb_typeof(graph_evidence) = 'object'
51
+ ),
52
+ CONSTRAINT session_tenancy_greenfield_setting_version_check CHECK (
53
+ private_setting_version > 0
54
+ )
55
+ );
56
+ ALTER TABLE session_tenancy_greenfield_activation_evidence ENABLE ROW LEVEL SECURITY;
57
+ ALTER TABLE session_tenancy_greenfield_activation_evidence FORCE ROW LEVEL SECURITY;
58
+ REVOKE ALL ON TABLE session_tenancy_greenfield_activation_evidence FROM PUBLIC;
59
+ CREATE POLICY session_tenancy_greenfield_activation_evidence_lifecycle
60
+ ON session_tenancy_greenfield_activation_evidence
61
+ USING (current_setting('opengeni.organization_tenancy_lifecycle', true)
62
+ = 'session_tenancy_greenfield_activation')
63
+ WITH CHECK (current_setting('opengeni.organization_tenancy_lifecycle', true)
64
+ = 'session_tenancy_greenfield_activation');
65
+
66
+ -- The activation table is FORCE-RLS, so its owner is policy-bound in the
67
+ -- documented production posture. This value-free witness policy is open only
68
+ -- while an owner-executed SECURITY DEFINER lifecycle holds the exact marker.
69
+ -- The runtime role cannot satisfy current_user even if it forges the GUC.
70
+ DO $greenfield_activation_witness_policy$
71
+ DECLARE
72
+ data_schema text := pg_catalog.current_schema();
73
+ migration_owner text := current_user;
74
+ BEGIN
75
+ EXECUTE pg_catalog.format(
76
+ 'CREATE POLICY session_tenancy_greenfield_activation_witness_read '
77
+ || 'ON %I.session_tenancy_activations FOR SELECT USING ('
78
+ || 'current_user = %L AND current_setting('
79
+ || '''opengeni.organization_tenancy_lifecycle'', true) '
80
+ || '= ''session_tenancy_greenfield_activation'')',
81
+ data_schema, migration_owner
82
+ );
83
+ END
84
+ $greenfield_activation_witness_policy$;
85
+
86
+ -- Repair the existing value-free deployment witness for the same real owner
87
+ -- posture. Its signature and boolean contract stay unchanged; the marker is
88
+ -- restored on every exit and grants no row projection.
89
+ CREATE OR REPLACE FUNCTION session_tenancy_any_product_activation()
90
+ RETURNS boolean
91
+ LANGUAGE plpgsql VOLATILE SECURITY DEFINER
92
+ SET search_path FROM CURRENT
93
+ AS $witness$
94
+ DECLARE
95
+ previous_lifecycle text := pg_catalog.current_setting(
96
+ 'opengeni.organization_tenancy_lifecycle', true
97
+ );
98
+ activated boolean;
99
+ BEGIN
100
+ PERFORM pg_catalog.set_config(
101
+ 'opengeni.organization_tenancy_lifecycle',
102
+ 'session_tenancy_greenfield_activation', true
103
+ );
104
+ SELECT EXISTS (
105
+ SELECT 1 FROM session_tenancy_activations
106
+ ) INTO activated;
107
+ PERFORM pg_catalog.set_config(
108
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
109
+ );
110
+ RETURN activated;
111
+ EXCEPTION WHEN OTHERS THEN
112
+ PERFORM pg_catalog.set_config(
113
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
114
+ );
115
+ RAISE;
116
+ END
117
+ $witness$;
118
+ REVOKE ALL ON FUNCTION session_tenancy_any_product_activation() FROM PUBLIC;
119
+
120
+ -- Owner-only greenfield lifecycle. It is called only by the fresh INSERT
121
+ -- branch of complete_self_service_organization_setup, after the canonical
122
+ -- graph and setup receipt exist. It verifies that the account row itself was
123
+ -- inserted by this transaction and has never been updated, proving the 0348
124
+ -- adoption UPDATE cannot cross this boundary even if a future refactor moved
125
+ -- the call accidentally.
126
+ CREATE FUNCTION activate_greenfield_session_tenancy_from_setup(
127
+ p_auth_user_id text
128
+ ) RETURNS boolean
129
+ LANGUAGE plpgsql VOLATILE SECURITY DEFINER
130
+ SET search_path FROM CURRENT
131
+ AS $activation$
132
+ DECLARE
133
+ previous_lifecycle text := pg_catalog.current_setting(
134
+ 'opengeni.organization_tenancy_lifecycle', true
135
+ );
136
+ actor_subject text := 'user:' || p_auth_user_id;
137
+ setup self_service_organization_setup_receipts%ROWTYPE;
138
+ account_row managed_accounts%ROWTYPE;
139
+ membership organization_memberships%ROWTYPE;
140
+ workspace workspaces%ROWTYPE;
141
+ witness session_tenancy_activations%ROWTYPE;
142
+ inserted_activation session_tenancy_activations%ROWTYPE;
143
+ existing_evidence session_tenancy_greenfield_activation_evidence%ROWTYPE;
144
+ account_inserted_in_current_transaction boolean;
145
+ account_never_updated boolean;
146
+ membership_count integer;
147
+ workspace_count integer;
148
+ workspace_control_count integer;
149
+ workspace_membership_count integer;
150
+ graph_evidence_value jsonb;
151
+ graph_digest_value text;
152
+ parity_digest_value text;
153
+ BEGIN
154
+ IF p_auth_user_id IS NULL
155
+ OR pg_catalog.octet_length(pg_catalog.convert_to(p_auth_user_id, 'UTF8'))
156
+ NOT BETWEEN 1 AND 1019
157
+ OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
158
+ THEN
159
+ RAISE EXCEPTION 'greenfield session tenancy setup authority required'
160
+ USING ERRCODE = '42501';
161
+ END IF;
162
+
163
+ SELECT * INTO setup
164
+ FROM self_service_organization_setup_receipts receipt
165
+ WHERE receipt.auth_user_id = p_auth_user_id
166
+ FOR KEY SHARE;
167
+ IF NOT FOUND
168
+ OR setup.account_id IS DISTINCT FROM opengeni_private.current_account_id()
169
+ OR setup.result ->> 'status' IS DISTINCT FROM 'complete'
170
+ OR setup.result ->> 'organizationId' IS DISTINCT FROM setup.account_id::text
171
+ OR setup.result ->> 'personalWorkspaceId' IS DISTINCT FROM setup.personal_workspace_id::text
172
+ OR setup.result ->> 'workspaceId' IS DISTINCT FROM setup.personal_workspace_id::text
173
+ THEN
174
+ RAISE EXCEPTION 'greenfield session tenancy setup receipt is invalid'
175
+ USING ERRCODE = '55000';
176
+ END IF;
177
+
178
+ SELECT * INTO account_row
179
+ FROM managed_accounts account
180
+ WHERE account.id = setup.account_id
181
+ FOR KEY SHARE;
182
+ IF NOT FOUND
183
+ OR account_row.external_source IS DISTINCT FROM 'better-auth:user'
184
+ OR account_row.external_id IS DISTINCT FROM p_auth_user_id
185
+ OR account_row.name IS DISTINCT FROM setup.organization_name
186
+ THEN
187
+ RAISE EXCEPTION 'greenfield session tenancy requires a newly inserted account'
188
+ USING ERRCODE = '55000';
189
+ END IF;
190
+ SELECT account.xmin::text::bigint
191
+ = pg_catalog.pg_current_xact_id()::text::bigint,
192
+ account.created_at = account.updated_at
193
+ INTO account_inserted_in_current_transaction, account_never_updated
194
+ FROM managed_accounts account
195
+ WHERE account.id = setup.account_id;
196
+ IF account_inserted_in_current_transaction IS NOT TRUE
197
+ OR account_never_updated IS NOT TRUE
198
+ THEN
199
+ RAISE EXCEPTION 'greenfield session tenancy requires a newly inserted account'
200
+ USING ERRCODE = '55000';
201
+ END IF;
202
+
203
+ SELECT * INTO membership
204
+ FROM organization_memberships candidate
205
+ WHERE candidate.id = setup.organization_membership_id
206
+ AND candidate.account_id = setup.account_id
207
+ FOR KEY SHARE;
208
+ SELECT count(*)::integer INTO membership_count
209
+ FROM organization_memberships candidate
210
+ WHERE candidate.account_id = setup.account_id;
211
+ IF membership.id IS NULL
212
+ OR membership_count <> 1
213
+ OR membership.subject_id IS DISTINCT FROM actor_subject
214
+ OR membership.role IS DISTINCT FROM 'owner'
215
+ OR membership.status IS DISTINCT FROM 'active'
216
+ OR membership.personal_workspace_id IS DISTINCT FROM setup.personal_workspace_id
217
+ THEN
218
+ RAISE EXCEPTION 'greenfield session tenancy membership graph is invalid'
219
+ USING ERRCODE = '55000';
220
+ END IF;
221
+
222
+ SELECT * INTO workspace
223
+ FROM workspaces candidate
224
+ WHERE candidate.id = setup.personal_workspace_id
225
+ AND candidate.account_id = setup.account_id
226
+ FOR KEY SHARE;
227
+ SELECT count(*)::integer INTO workspace_count
228
+ FROM workspaces candidate WHERE candidate.account_id = setup.account_id;
229
+ SELECT count(*)::integer INTO workspace_control_count
230
+ FROM workspace_inference_controls control
231
+ WHERE control.account_id = setup.account_id
232
+ AND control.workspace_id = setup.personal_workspace_id;
233
+ SELECT count(*)::integer INTO workspace_membership_count
234
+ FROM workspace_memberships candidate
235
+ WHERE candidate.account_id = setup.account_id;
236
+ IF workspace.id IS NULL
237
+ OR workspace_count <> 1
238
+ OR workspace_control_count <> 1
239
+ OR workspace_membership_count <> 0
240
+ OR workspace.name IS DISTINCT FROM 'Personal workspace'
241
+ OR workspace.slug IS NOT NULL
242
+ OR workspace.external_source IS DISTINCT FROM 'opengeni:organization-membership'
243
+ OR workspace.external_id IS DISTINCT FROM
244
+ setup.account_id::text || ':' || actor_subject
245
+ THEN
246
+ RAISE EXCEPTION 'greenfield session tenancy Personal workspace graph is invalid'
247
+ USING ERRCODE = '55000';
248
+ END IF;
249
+
250
+ graph_evidence_value := pg_catalog.jsonb_build_object(
251
+ 'schemaVersion', 1,
252
+ 'kind', 'self_service_personal_only',
253
+ 'accountId', setup.account_id,
254
+ 'setupAuthUserId', setup.auth_user_id,
255
+ 'setupOperationId', setup.operation_id,
256
+ 'personalWorkspaceId', setup.personal_workspace_id,
257
+ 'organizationMembershipId', setup.organization_membership_id,
258
+ 'organizationMembershipCount', membership_count,
259
+ 'workspaceCount', workspace_count,
260
+ 'workspaceControlCount', workspace_control_count,
261
+ 'workspaceMembershipCount', workspace_membership_count
262
+ );
263
+ graph_digest_value := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
264
+ graph_evidence_value::text, 'UTF8'
265
+ )), 'hex');
266
+
267
+ -- Graph first, deployment boundary last. Operator activation takes every
268
+ -- source-table lock before this same fence, so either setup commits before
269
+ -- the first witness or it waits and observes the committed witness.
270
+ PERFORM lock_session_tenancy_activation_boundary();
271
+ PERFORM pg_catalog.set_config(
272
+ 'opengeni.organization_tenancy_lifecycle',
273
+ 'session_tenancy_greenfield_activation', true
274
+ );
275
+
276
+ SELECT * INTO existing_evidence
277
+ FROM session_tenancy_greenfield_activation_evidence evidence
278
+ WHERE evidence.account_id = setup.account_id;
279
+ IF FOUND THEN
280
+ IF existing_evidence.setup_auth_user_id IS DISTINCT FROM setup.auth_user_id
281
+ OR existing_evidence.setup_operation_id IS DISTINCT FROM setup.operation_id
282
+ OR existing_evidence.setup_request_fingerprint IS DISTINCT FROM
283
+ setup.request_fingerprint
284
+ OR existing_evidence.personal_workspace_id IS DISTINCT FROM
285
+ setup.personal_workspace_id
286
+ OR existing_evidence.organization_membership_id IS DISTINCT FROM
287
+ setup.organization_membership_id
288
+ OR existing_evidence.graph_digest IS DISTINCT FROM graph_digest_value
289
+ THEN
290
+ RAISE EXCEPTION 'greenfield session tenancy evidence conflicts with durable setup'
291
+ USING ERRCODE = '23505';
292
+ END IF;
293
+ PERFORM pg_catalog.set_config(
294
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
295
+ );
296
+ RETURN true;
297
+ END IF;
298
+
299
+ SELECT * INTO witness
300
+ FROM session_tenancy_activations activation
301
+ WHERE activation.account_id <> setup.account_id
302
+ ORDER BY activation.activated_at, activation.account_id
303
+ LIMIT 1;
304
+ IF NOT FOUND THEN
305
+ PERFORM pg_catalog.set_config(
306
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
307
+ );
308
+ RETURN false;
309
+ END IF;
310
+
311
+ IF EXISTS (
312
+ SELECT 1 FROM session_tenancy_activations activation
313
+ WHERE activation.account_id = setup.account_id
314
+ ) OR EXISTS (
315
+ SELECT 1 FROM organization_private_session_settings setting
316
+ WHERE setting.account_id = setup.account_id
317
+ ) THEN
318
+ RAISE EXCEPTION 'greenfield session tenancy authority already exists without evidence'
319
+ USING ERRCODE = '55000';
320
+ END IF;
321
+
322
+ parity_digest_value := pg_catalog.encode(pg_catalog.sha256(pg_catalog.convert_to(
323
+ pg_catalog.jsonb_build_object(
324
+ 'schemaVersion', 1,
325
+ 'graphDigest', graph_digest_value,
326
+ 'boundaryWitnessAccountId', witness.account_id,
327
+ 'boundaryWitnessActivatedAt', witness.activated_at
328
+ )::text,
329
+ 'UTF8'
330
+ )), 'hex');
331
+
332
+ PERFORM pg_catalog.set_config(
333
+ 'opengeni.organization_tenancy_lifecycle', 'session_tenancy_activation', true
334
+ );
335
+ INSERT INTO session_tenancy_activations (
336
+ account_id, activation_version, inventory_digest, parity_digest,
337
+ activated_by, backfill_receipt_ids
338
+ ) VALUES (
339
+ setup.account_id, 1, graph_digest_value, parity_digest_value,
340
+ 'opengeni:greenfield-organization-setup:v1', ARRAY[]::uuid[]
341
+ ) RETURNING * INTO inserted_activation;
342
+
343
+ PERFORM pg_catalog.set_config(
344
+ 'opengeni.organization_tenancy_lifecycle',
345
+ 'organization_private_session_settings', true
346
+ );
347
+ INSERT INTO organization_private_session_settings (
348
+ account_id, enabled, version, updated_by_membership_id, updated_at
349
+ ) VALUES (
350
+ setup.account_id, true, 1, setup.organization_membership_id,
351
+ inserted_activation.activated_at
352
+ );
353
+ INSERT INTO organization_private_session_setting_events (
354
+ id, account_id, actor_membership_id, requested_enabled,
355
+ expected_version, result_enabled, result_version, result_updated_at, changed
356
+ ) VALUES (
357
+ setup.operation_id, setup.account_id, setup.organization_membership_id, true,
358
+ 0, true, 1, inserted_activation.activated_at, true
359
+ );
360
+
361
+ PERFORM pg_catalog.set_config(
362
+ 'opengeni.organization_tenancy_lifecycle',
363
+ 'session_tenancy_greenfield_activation', true
364
+ );
365
+ INSERT INTO session_tenancy_greenfield_activation_evidence (
366
+ account_id, eligibility_version, setup_auth_user_id, setup_operation_id,
367
+ setup_request_fingerprint, personal_workspace_id,
368
+ organization_membership_id, boundary_witness_account_id,
369
+ boundary_witness_activated_at, graph_evidence, graph_digest,
370
+ activation_parity_digest, private_setting_version,
371
+ private_setting_event_id, activated_at
372
+ ) VALUES (
373
+ setup.account_id, 1, setup.auth_user_id, setup.operation_id,
374
+ setup.request_fingerprint, setup.personal_workspace_id,
375
+ setup.organization_membership_id, witness.account_id,
376
+ witness.activated_at, graph_evidence_value, graph_digest_value,
377
+ parity_digest_value, 1, setup.operation_id, inserted_activation.activated_at
378
+ );
379
+
380
+ PERFORM pg_catalog.set_config(
381
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
382
+ );
383
+ RETURN true;
384
+ EXCEPTION WHEN OTHERS THEN
385
+ PERFORM pg_catalog.set_config(
386
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
387
+ );
388
+ RAISE;
389
+ END
390
+ $activation$;
391
+ REVOKE ALL ON FUNCTION activate_greenfield_session_tenancy_from_setup(text) FROM PUBLIC;
392
+
393
+ -- Both private-create membership readers predate the real migration-owner
394
+ -- harness and were silently blind on organization_memberships under FORCE RLS.
395
+ -- Open an owner-only, tenant-and-subject-fenced read/row-lock window so the
396
+ -- newly activated Personal workspace is usable immediately in production,
397
+ -- not only in superuser-backed tests. The exact marker is restored by both
398
+ -- SECURITY DEFINER routines before returning or raising.
399
+ DO $private_create_membership_policies$
400
+ DECLARE
401
+ data_schema text := pg_catalog.current_schema();
402
+ migration_owner text := current_user;
403
+ marker text :=
404
+ 'current_user = ' || pg_catalog.quote_literal(migration_owner)
405
+ || ' AND current_setting(''opengeni.organization_tenancy_lifecycle'', true) '
406
+ || '= ''private_session_create_authority'' AND account_id = nullif('
407
+ || 'current_setting(''opengeni.account_id'', true), '''')::uuid '
408
+ || 'AND subject_id = opengeni_private.current_subject_id()';
409
+ BEGIN
410
+ EXECUTE pg_catalog.format(
411
+ 'CREATE POLICY private_session_create_membership_read '
412
+ || 'ON %I.organization_memberships FOR SELECT USING (%s)',
413
+ data_schema, marker
414
+ );
415
+ EXECUTE pg_catalog.format(
416
+ 'CREATE POLICY private_session_create_membership_lock '
417
+ || 'ON %I.organization_memberships FOR UPDATE USING (%s)',
418
+ data_schema, marker
419
+ );
420
+ END
421
+ $private_create_membership_policies$;
422
+
423
+ CREATE OR REPLACE FUNCTION get_private_session_create_policy(
424
+ p_account_id uuid,
425
+ p_workspace_id uuid,
426
+ p_actor_subject_id text
427
+ ) RETURNS TABLE (
428
+ personal_workspace boolean,
429
+ platform_available boolean,
430
+ organization_enabled boolean
431
+ )
432
+ LANGUAGE plpgsql STABLE SECURITY DEFINER
433
+ SET search_path FROM CURRENT
434
+ AS $policy$
435
+ DECLARE
436
+ actor organization_memberships%ROWTYPE;
437
+ previous_lifecycle text := pg_catalog.current_setting(
438
+ 'opengeni.organization_tenancy_lifecycle', true
439
+ );
440
+ BEGIN
441
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_actor_subject_id IS NULL
442
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
443
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
444
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
445
+ THEN
446
+ RAISE EXCEPTION 'private session create policy authority required'
447
+ USING ERRCODE = '42501';
448
+ END IF;
449
+ PERFORM pg_catalog.set_config(
450
+ 'opengeni.organization_tenancy_lifecycle',
451
+ 'private_session_create_authority', true
452
+ );
453
+ SELECT membership.* INTO actor FROM organization_memberships membership
454
+ WHERE membership.account_id = p_account_id
455
+ AND membership.subject_id = p_actor_subject_id
456
+ AND membership.status = 'active';
457
+ IF NOT FOUND OR NOT EXISTS (
458
+ SELECT 1 FROM workspaces workspace
459
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
460
+ ) OR NOT (
461
+ actor.personal_workspace_id = p_workspace_id
462
+ OR EXISTS (
463
+ SELECT 1 FROM workspace_memberships access
464
+ WHERE access.account_id = p_account_id
465
+ AND access.workspace_id = p_workspace_id
466
+ AND access.subject_id = p_actor_subject_id
467
+ )
468
+ ) THEN
469
+ RAISE EXCEPTION 'private session create policy authority required'
470
+ USING ERRCODE = '42501';
471
+ END IF;
472
+ personal_workspace := actor.personal_workspace_id = p_workspace_id;
473
+ platform_available := session_tenancy_product_activated(p_account_id, 1);
474
+ organization_enabled := organization_private_sessions_enabled(p_account_id);
475
+ PERFORM pg_catalog.set_config(
476
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
477
+ );
478
+ RETURN NEXT;
479
+ EXCEPTION WHEN OTHERS THEN
480
+ PERFORM pg_catalog.set_config(
481
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
482
+ );
483
+ RAISE;
484
+ END
485
+ $policy$;
486
+ REVOKE ALL ON FUNCTION get_private_session_create_policy(uuid,uuid,text) FROM PUBLIC;
487
+
488
+ CREATE OR REPLACE FUNCTION open_private_session_create_capability(
489
+ p_account_id uuid,
490
+ p_workspace_id uuid,
491
+ p_session_id uuid,
492
+ p_actor_subject_id text
493
+ ) RETURNS TABLE (capability_id uuid, owner_membership_id uuid)
494
+ LANGUAGE plpgsql SECURITY DEFINER
495
+ SET search_path FROM CURRENT
496
+ AS $private_create$
497
+ DECLARE
498
+ new_capability_id uuid := pg_catalog.gen_random_uuid();
499
+ actor_membership_id uuid;
500
+ actor_personal_workspace boolean;
501
+ workspace_access_id uuid;
502
+ previous_lifecycle text := pg_catalog.current_setting(
503
+ 'opengeni.organization_tenancy_lifecycle', true
504
+ );
505
+ BEGIN
506
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_session_id IS NULL
507
+ OR p_actor_subject_id IS NULL OR p_actor_subject_id NOT LIKE 'user:%'
508
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
509
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
510
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
511
+ OR NOT session_tenancy_product_activated(p_account_id, 1)
512
+ THEN
513
+ RAISE EXCEPTION 'private session create authority required' USING ERRCODE = '42501';
514
+ END IF;
515
+ PERFORM pg_catalog.pg_try_advisory_xact_lock(pg_catalog.hashtextextended(
516
+ 'organization-membership:' || p_account_id::text, 0
517
+ ));
518
+ PERFORM 1 FROM workspace_inference_controls control
519
+ WHERE control.account_id = p_account_id AND control.workspace_id = p_workspace_id
520
+ FOR SHARE;
521
+ PERFORM 1 FROM workspaces workspace
522
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
523
+ FOR KEY SHARE;
524
+ PERFORM pg_catalog.set_config(
525
+ 'opengeni.organization_tenancy_lifecycle',
526
+ 'private_session_create_authority', true
527
+ );
528
+ SELECT membership.id, membership.personal_workspace_id = p_workspace_id
529
+ INTO actor_membership_id, actor_personal_workspace
530
+ FROM organization_memberships membership
531
+ WHERE membership.account_id = p_account_id
532
+ AND membership.subject_id = p_actor_subject_id
533
+ AND membership.status = 'active'
534
+ FOR KEY SHARE;
535
+ IF NOT FOUND THEN
536
+ RAISE EXCEPTION 'private session create authority required' USING ERRCODE = '42501';
537
+ END IF;
538
+ IF NOT actor_personal_workspace THEN
539
+ SELECT access.id INTO workspace_access_id
540
+ FROM workspace_memberships access
541
+ WHERE access.account_id = p_account_id
542
+ AND access.workspace_id = p_workspace_id
543
+ AND access.subject_id = p_actor_subject_id
544
+ FOR KEY SHARE;
545
+ IF NOT FOUND THEN
546
+ RAISE EXCEPTION 'private session create authority required' USING ERRCODE = '42501';
547
+ END IF;
548
+ IF NOT organization_private_sessions_enabled(p_account_id) THEN
549
+ RAISE EXCEPTION
550
+ 'private sessions are not enabled for this organization''s shared workspaces'
551
+ USING ERRCODE = '55000';
552
+ END IF;
553
+ END IF;
554
+ PERFORM pg_catalog.set_config(
555
+ 'opengeni.private_session_create_lifecycle', 'private_session_create', true
556
+ );
557
+ INSERT INTO private_session_create_capabilities (
558
+ backend_pid, transaction_id, capability_id, account_id, workspace_id,
559
+ session_id, actor_subject_id, owner_membership_id
560
+ ) VALUES (
561
+ pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(),
562
+ new_capability_id, p_account_id, p_workspace_id, p_session_id,
563
+ p_actor_subject_id, actor_membership_id
564
+ );
565
+ PERFORM pg_catalog.set_config(
566
+ 'opengeni.private_session_create_capability', new_capability_id::text, true
567
+ );
568
+ capability_id := new_capability_id;
569
+ owner_membership_id := actor_membership_id;
570
+ PERFORM pg_catalog.set_config(
571
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
572
+ );
573
+ RETURN NEXT;
574
+ EXCEPTION WHEN OTHERS THEN
575
+ PERFORM pg_catalog.set_config(
576
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
577
+ );
578
+ RAISE;
579
+ END
580
+ $private_create$;
581
+ REVOKE ALL ON FUNCTION open_private_session_create_capability(uuid,uuid,uuid,text) FROM PUBLIC;
582
+
583
+ -- Replace only the 0348 setup lifecycle body so the greenfield call is inside
584
+ -- the same transaction. The adopted-account branch remains byte-for-byte
585
+ -- semantically identical and never invokes the lifecycle.
586
+ CREATE OR REPLACE FUNCTION complete_self_service_organization_setup(p_command jsonb)
587
+ RETURNS jsonb
588
+ LANGUAGE plpgsql
589
+ SECURITY DEFINER
590
+ SET search_path FROM CURRENT
591
+ AS $body$
592
+ DECLARE
593
+ auth_user_id_value text := p_command ->> 'authUserId';
594
+ actor_subject text := p_command ->> 'actorSubjectId';
595
+ organization_name_value text := nullif(btrim(p_command ->> 'organizationName'), '');
596
+ operation_id_value uuid := nullif(p_command ->> 'operationId', '')::uuid;
597
+ fingerprint_value text := p_command ->> 'requestFingerprint';
598
+ auth_user auth_users%ROWTYPE;
599
+ account_row managed_accounts%ROWTYPE;
600
+ existing_receipt self_service_organization_setup_receipts%ROWTYPE;
601
+ adopted_account managed_accounts%ROWTYPE;
602
+ adopt_existing_account boolean := false;
603
+ account_id_value uuid := gen_random_uuid();
604
+ workspace_id_value uuid := gen_random_uuid();
605
+ membership_id_value uuid;
606
+ existing_membership_count integer;
607
+ public_result jsonb;
608
+ BEGIN
609
+ IF auth_user_id_value IS NULL
610
+ OR octet_length(convert_to(auth_user_id_value, 'UTF8')) NOT BETWEEN 1 AND 1019
611
+ OR actor_subject IS NULL
612
+ OR actor_subject IS DISTINCT FROM opengeni_private.current_subject_id()
613
+ OR actor_subject IS DISTINCT FROM 'user:' || auth_user_id_value
614
+ OR organization_name_value IS NULL
615
+ OR octet_length(convert_to(organization_name_value, 'UTF8')) NOT BETWEEN 1 AND 120
616
+ OR operation_id_value IS NULL
617
+ OR fingerprint_value !~ '^[0-9a-f]{64}$'
618
+ THEN
619
+ RAISE EXCEPTION 'self-service organization setup input is invalid'
620
+ USING ERRCODE = '22023';
621
+ END IF;
622
+
623
+ PERFORM pg_catalog.pg_advisory_xact_lock(
624
+ pg_catalog.hashtextextended('self-service-organization:' || auth_user_id_value, 0)
625
+ );
626
+ SELECT * INTO auth_user FROM auth_users candidate
627
+ WHERE candidate.id = auth_user_id_value FOR UPDATE;
628
+ IF NOT FOUND
629
+ OR auth_user.email_verified IS NOT TRUE
630
+ OR lower(btrim(auth_user.email)) = ''
631
+ THEN
632
+ RAISE EXCEPTION 'verified managed user required' USING ERRCODE = '42501';
633
+ END IF;
634
+
635
+ PERFORM pg_catalog.set_config(
636
+ 'opengeni.organization_tenancy_lifecycle',
637
+ 'organization_membership_lifecycle', true
638
+ );
639
+ PERFORM bind_pending_organization_invitations_for_verified_email(
640
+ actor_subject, auth_user.email
641
+ );
642
+
643
+ SELECT * INTO existing_receipt
644
+ FROM self_service_organization_setup_receipts receipt
645
+ WHERE receipt.auth_user_id = auth_user_id_value
646
+ FOR UPDATE;
647
+ IF FOUND THEN
648
+ IF existing_receipt.operation_id IS DISTINCT FROM operation_id_value
649
+ OR existing_receipt.request_fingerprint IS DISTINCT FROM fingerprint_value
650
+ OR existing_receipt.organization_name IS DISTINCT FROM organization_name_value
651
+ THEN
652
+ RAISE EXCEPTION 'self-service organization setup was already completed'
653
+ USING ERRCODE = '23505';
654
+ END IF;
655
+ RETURN existing_receipt.result;
656
+ END IF;
657
+
658
+ SELECT count(*)::integer INTO existing_membership_count
659
+ FROM organization_memberships membership
660
+ WHERE membership.subject_id = actor_subject;
661
+ IF existing_membership_count <> 0
662
+ OR has_pending_organization_invitation_for_subject(actor_subject)
663
+ THEN
664
+ RAISE EXCEPTION 'organization setup is no longer available'
665
+ USING ERRCODE = '55000';
666
+ END IF;
667
+
668
+ SELECT * INTO adopted_account FROM managed_accounts account
669
+ WHERE account.external_source = 'better-auth:user'
670
+ AND account.external_id = auth_user_id_value;
671
+ IF FOUND THEN
672
+ IF EXISTS (
673
+ SELECT 1 FROM organization_memberships membership
674
+ WHERE membership.account_id = adopted_account.id
675
+ ) THEN
676
+ RAISE EXCEPTION 'organization setup is no longer available'
677
+ USING ERRCODE = '55000';
678
+ END IF;
679
+ account_id_value := adopted_account.id;
680
+ adopt_existing_account := true;
681
+ END IF;
682
+
683
+ PERFORM pg_catalog.pg_advisory_xact_lock(
684
+ pg_catalog.hashtextextended('organization-membership:' || account_id_value::text, 0)
685
+ );
686
+ IF NOT adopt_existing_account THEN
687
+ INSERT INTO managed_accounts (id, name, external_source, external_id)
688
+ VALUES (
689
+ account_id_value, organization_name_value,
690
+ 'better-auth:user', auth_user_id_value
691
+ ) RETURNING * INTO account_row;
692
+ END IF;
693
+ PERFORM pg_catalog.set_config('opengeni.account_id', account_id_value::text, true);
694
+ PERFORM pg_catalog.set_config('opengeni.workspace_id', workspace_id_value::text, true);
695
+
696
+ INSERT INTO workspaces (
697
+ id, account_id, name, slug, external_source, external_id
698
+ ) VALUES (
699
+ workspace_id_value, account_id_value, 'Personal workspace', NULL,
700
+ 'opengeni:organization-membership', account_id_value::text || ':' || actor_subject
701
+ );
702
+ INSERT INTO workspace_inference_controls (workspace_id, account_id)
703
+ VALUES (workspace_id_value, account_id_value);
704
+ INSERT INTO organization_memberships (
705
+ account_id, subject_id, role, status, personal_workspace_id
706
+ ) VALUES (
707
+ account_id_value, actor_subject, 'owner', 'active', workspace_id_value
708
+ ) RETURNING id INTO membership_id_value;
709
+
710
+ IF adopt_existing_account THEN
711
+ UPDATE managed_accounts SET
712
+ name = organization_name_value,
713
+ updated_at = pg_catalog.clock_timestamp()
714
+ WHERE id = account_id_value
715
+ RETURNING * INTO account_row;
716
+ END IF;
717
+
718
+ public_result := pg_catalog.jsonb_build_object(
719
+ 'status', 'complete',
720
+ 'organizationId', account_id_value,
721
+ 'personalWorkspaceId', workspace_id_value,
722
+ 'organization', pg_catalog.jsonb_build_object(
723
+ 'id', account_row.id,
724
+ 'name', account_row.name,
725
+ 'createdAt', account_row.created_at,
726
+ 'updatedAt', account_row.updated_at
727
+ ),
728
+ 'workspaceId', workspace_id_value
729
+ );
730
+ INSERT INTO self_service_organization_setup_receipts (
731
+ auth_user_id, operation_id, request_fingerprint, account_id,
732
+ personal_workspace_id, organization_membership_id,
733
+ organization_name, result
734
+ ) VALUES (
735
+ auth_user_id_value, operation_id_value, fingerprint_value, account_id_value,
736
+ workspace_id_value, membership_id_value, organization_name_value, public_result
737
+ );
738
+
739
+ IF NOT adopt_existing_account THEN
740
+ PERFORM activate_greenfield_session_tenancy_from_setup(auth_user_id_value);
741
+ END IF;
742
+ RETURN public_result;
743
+ END
744
+ $body$;
745
+
746
+ DO $posture$
747
+ DECLARE
748
+ data_schema text := pg_catalog.current_schema();
749
+ BEGIN
750
+ EXECUTE pg_catalog.format(
751
+ 'ALTER FUNCTION %I.session_tenancy_any_product_activation() '
752
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
753
+ data_schema, data_schema
754
+ );
755
+ EXECUTE pg_catalog.format(
756
+ 'ALTER FUNCTION %I.activate_greenfield_session_tenancy_from_setup(text) '
757
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
758
+ data_schema, data_schema
759
+ );
760
+ EXECUTE pg_catalog.format(
761
+ 'ALTER FUNCTION %I.complete_self_service_organization_setup(jsonb) '
762
+ || 'SET search_path = pg_catalog, %I, opengeni_private, pg_temp',
763
+ data_schema, data_schema
764
+ );
765
+ EXECUTE pg_catalog.format(
766
+ 'ALTER FUNCTION %I.get_private_session_create_policy(uuid,uuid,text) '
767
+ || 'SET search_path = pg_catalog, %I, pg_temp',
768
+ data_schema, data_schema
769
+ );
770
+ EXECUTE pg_catalog.format(
771
+ 'ALTER FUNCTION %I.open_private_session_create_capability(uuid,uuid,uuid,text) '
772
+ || 'SET search_path = pg_catalog, %I, pg_temp',
773
+ data_schema, data_schema
774
+ );
775
+ END
776
+ $posture$;
777
+
778
+ DO $runtime_acl$
779
+ BEGIN
780
+ IF EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app') THEN
781
+ REVOKE ALL ON TABLE session_tenancy_greenfield_activation_evidence
782
+ FROM opengeni_app;
783
+ REVOKE ALL ON FUNCTION activate_greenfield_session_tenancy_from_setup(text)
784
+ FROM opengeni_app;
785
+ END IF;
786
+ END
787
+ $runtime_acl$;
788
+
789
+ COMMENT ON TABLE session_tenancy_greenfield_activation_evidence IS
790
+ 'Immutable exact-setup, Personal-only graph, boundary-witness, private-setting, and v1 activation evidence for eligible newly inserted organizations.';
791
+ COMMENT ON FUNCTION activate_greenfield_session_tenancy_from_setup(text) IS
792
+ 'Owner-only atomic greenfield activation lifecycle; existing and adopted accounts remain operator-activated.';