@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.
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-SIBYBA2D.js → chunk-6DPJA36C.js} +30 -11
- package/dist/chunk-6DPJA36C.js.map +1 -0
- package/dist/{chunk-KRLPEMLH.js → chunk-C2AFP7SB.js} +184 -6
- package/dist/chunk-C2AFP7SB.js.map +1 -0
- package/dist/{chunk-AZHW7R5V.js → chunk-H7MXYG7R.js} +12 -8
- package/dist/chunk-H7MXYG7R.js.map +1 -0
- package/dist/{chunk-2PRS4J5Q.js → chunk-HY3F5Q4E.js} +3 -3
- package/dist/{chunk-NAB6FKFN.js → chunk-O6FTELKH.js} +3 -3
- package/dist/{chunk-UZNXG44K.js → chunk-PYMY3AJW.js} +324 -8
- package/dist/chunk-PYMY3AJW.js.map +1 -0
- package/dist/{chunk-6HMM2R5T.js → chunk-TRBOWUG3.js} +98 -4
- package/dist/chunk-TRBOWUG3.js.map +1 -0
- package/dist/{chunk-E6CLMKEE.js → chunk-V3TRUXFJ.js} +2 -2
- package/dist/{chunk-2EH4WRHL.js → chunk-ZWRIPCTR.js} +2 -2
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/governed-learning-activation.d.ts +16 -0
- package/dist/index.d.ts +108 -7
- package/dist/index.js +3605 -1417
- package/dist/index.js.map +1 -1
- package/dist/insights-model-bundle.d.ts +30 -0
- package/dist/insights-usage-bundle.d.ts +32 -0
- package/dist/insights.d.ts +10 -2
- package/dist/lossless-json.d.ts +1 -1
- package/dist/managed-user-setup.d.ts +82 -0
- package/dist/memory-domain.d.ts +2 -2
- package/dist/migrate.d.ts +3 -1
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +4 -4
- package/dist/organization-membership-lifecycle.d.ts +60 -1
- package/dist/pr-review.d.ts +44 -3
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +12 -7
- package/dist/schema.d.ts +839 -101
- package/dist/schema.js +9 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-tenancy.d.ts +12 -1
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/drizzle/0348_named_signup_and_user_setup.sql +838 -0
- package/drizzle/0349_greenfield_session_tenancy_activation.sql +792 -0
- package/drizzle/0350_organization_shared_workspace_administration.sql +889 -0
- package/drizzle/0351_organization_user_setup_delivery.sql +978 -0
- package/drizzle/0352_session_variable_set_attachments.sql +1661 -0
- package/drizzle/0353_automatic_session_title_policy_fence.sql +476 -0
- package/drizzle/0354_automatic_session_title_quarantine_index.sql +9 -0
- package/drizzle/0355_automatic_session_title_quarantine.sql +92 -0
- package/drizzle/0356_set_based_insights_session_visibility.sql +237 -0
- package/drizzle/0357_rig_platform_base_only.sql +44 -0
- package/drizzle/0358_pr_review_managed_github_app.sql +108 -0
- package/drizzle/0359_insights_force_rls_read_capability.sql +936 -0
- package/drizzle/0360_organization_identity_confirmation_prompt.sql +103 -0
- package/drizzle/0361_remember_knowledge_memory_materialization.sql +249 -0
- package/package.json +6 -6
- package/src/database.ts +12 -8
- package/src/governed-learning-activation.ts +74 -2
- package/src/index.ts +879 -92
- package/src/insights-model-bundle.ts +681 -0
- package/src/insights-usage-bundle.ts +240 -0
- package/src/insights.ts +265 -152
- package/src/lossless-json.ts +2 -1
- package/src/managed-user-setup.ts +316 -0
- package/src/memory-domain.ts +3 -3
- package/src/migrate.ts +38 -14
- package/src/new-session-drafts.ts +22 -15
- package/src/organization-membership-lifecycle.ts +253 -1
- package/src/pr-review.ts +449 -6
- package/src/provision-roles.ts +92 -0
- package/src/runtime-posture.ts +247 -4
- package/src/schema.ts +210 -4
- package/src/session-tenancy.ts +120 -2
- package/dist/chunk-6HMM2R5T.js.map +0 -1
- package/dist/chunk-AZHW7R5V.js.map +0 -1
- package/dist/chunk-KRLPEMLH.js.map +0 -1
- package/dist/chunk-SIBYBA2D.js.map +0 -1
- package/dist/chunk-UZNXG44K.js.map +0 -1
- /package/dist/{chunk-2PRS4J5Q.js.map → chunk-HY3F5Q4E.js.map} +0 -0
- /package/dist/{chunk-NAB6FKFN.js.map → chunk-O6FTELKH.js.map} +0 -0
- /package/dist/{chunk-E6CLMKEE.js.map → chunk-V3TRUXFJ.js.map} +0 -0
- /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.';
|