@opengeni/db 3.6.0 → 3.7.3-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 (80) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-744DFC65.js → chunk-4NAR7FBA.js} +92 -6
  3. package/dist/chunk-4NAR7FBA.js.map +1 -0
  4. package/dist/{chunk-IW6O6G7R.js → chunk-5NNCGFQY.js} +2 -2
  5. package/dist/{chunk-G3732M3T.js → chunk-DZCYW2WT.js} +2 -2
  6. package/dist/{chunk-WW7AJ5IC.js → chunk-F6PBS6WE.js} +5 -7
  7. package/dist/chunk-F6PBS6WE.js.map +1 -0
  8. package/dist/{chunk-UJ2EW3QI.js → chunk-O56DM5GZ.js} +340 -85
  9. package/dist/chunk-O56DM5GZ.js.map +1 -0
  10. package/dist/{chunk-CDMMSUWJ.js → chunk-S3TDDCS2.js} +3 -3
  11. package/dist/{chunk-Q5YUGQVK.js → chunk-SZAWDXQC.js} +3 -3
  12. package/dist/{chunk-UGDVSQ6U.js → chunk-U4WPPEBH.js} +2 -2
  13. package/dist/{chunk-2NT476HD.js → chunk-US4IS6IV.js} +2 -2
  14. package/dist/codex-token-resolver.d.ts +12 -1
  15. package/dist/company-profile-agent-admin.d.ts +4 -2
  16. package/dist/company-profile-agent-policy.d.ts +20 -0
  17. package/dist/company-profile-schema.d.ts +573 -0
  18. package/dist/editable-artifact-durable-export.js +2 -2
  19. package/dist/editable-artifacts.js +3 -3
  20. package/dist/index.d.ts +382 -18
  21. package/dist/index.js +4513 -2065
  22. package/dist/index.js.map +1 -1
  23. package/dist/managed-auth-session-sets.js +3 -3
  24. package/dist/managed-human-provisioning.d.ts +3 -3
  25. package/dist/memory-domain.d.ts +5 -3
  26. package/dist/provision-roles.js +1 -1
  27. package/dist/runtime-posture.d.ts +5 -5
  28. package/dist/schema.d.ts +725 -4
  29. package/dist/schema.js +13 -1
  30. package/dist/session-background-commands.js +3 -3
  31. package/dist/session-control.d.ts +40 -5
  32. package/dist/session-queue-commands.d.ts +2 -0
  33. package/dist/session-tenancy.js +3 -3
  34. package/dist/user-resource-authority.d.ts +8 -0
  35. package/dist/video-generation.js +3 -3
  36. package/drizzle/0369_local_human_personal_authority.sql +350 -0
  37. package/drizzle/0370_workspace_member_management_scope.sql +98 -0
  38. package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
  39. package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
  40. package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
  41. package/drizzle/0374_session_event_cursors.sql +315 -0
  42. package/drizzle/0375_session_recovery_observability.sql +73 -0
  43. package/drizzle/0376_organization_workspace_inventory.sql +88 -0
  44. package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
  45. package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
  46. package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
  47. package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
  48. package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
  49. package/drizzle/0382_organization_api_key_provenance.sql +91 -0
  50. package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
  51. package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
  52. package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
  53. package/drizzle/0386_local_organization_administration.sql +157 -0
  54. package/package.json +6 -6
  55. package/src/codex-token-resolver.ts +26 -1
  56. package/src/company-brain-governed-writes.ts +30 -1
  57. package/src/company-profile-agent-admin.ts +68 -1
  58. package/src/company-profile-agent-policy.ts +126 -0
  59. package/src/company-profile-schema.ts +115 -1
  60. package/src/database.ts +3 -5
  61. package/src/index.ts +3626 -456
  62. package/src/managed-human-provisioning.ts +3 -3
  63. package/src/memory-domain.ts +9 -4
  64. package/src/organization-membership-lifecycle.ts +0 -1
  65. package/src/provision-roles.ts +83 -4
  66. package/src/runtime-posture.ts +22 -0
  67. package/src/schema.ts +161 -8
  68. package/src/session-control.ts +177 -59
  69. package/src/session-queue-commands.ts +146 -40
  70. package/src/session-tool-call-settlement.ts +34 -7
  71. package/src/user-resource-authority.ts +56 -0
  72. package/dist/chunk-744DFC65.js.map +0 -1
  73. package/dist/chunk-UJ2EW3QI.js.map +0 -1
  74. package/dist/chunk-WW7AJ5IC.js.map +0 -1
  75. /package/dist/{chunk-IW6O6G7R.js.map → chunk-5NNCGFQY.js.map} +0 -0
  76. /package/dist/{chunk-G3732M3T.js.map → chunk-DZCYW2WT.js.map} +0 -0
  77. /package/dist/{chunk-CDMMSUWJ.js.map → chunk-S3TDDCS2.js.map} +0 -0
  78. /package/dist/{chunk-Q5YUGQVK.js.map → chunk-SZAWDXQC.js.map} +0 -0
  79. /package/dist/{chunk-UGDVSQ6U.js.map → chunk-U4WPPEBH.js.map} +0 -0
  80. /package/dist/{chunk-2NT476HD.js.map → chunk-US4IS6IV.js.map} +0 -0
package/dist/schema.js CHANGED
@@ -46,6 +46,7 @@ import {
46
46
  companyBrainContextSelectionReceipts,
47
47
  companyBrainTurnContextSnapshots,
48
48
  companyProfileActivationEvents,
49
+ companyProfileAgentAutomaticActivationReceipts,
49
50
  companyProfileAgentConfirmationReceipts,
50
51
  companyProfileAgentProposalReceipts,
51
52
  companyProfileHeads,
@@ -160,6 +161,9 @@ import {
160
161
  nestedAgentDepthConfiguration,
161
162
  networkRoutes,
162
163
  newSessionDrafts,
164
+ organizationCodexRotationSettings,
165
+ organizationCompanyProfileAgentPolicies,
166
+ organizationCompanyProfileAgentPolicyEvents,
163
167
  organizationInvitationBindingEvents,
164
168
  organizationMembershipInvitations,
165
169
  organizationMembershipLifecycleEvents,
@@ -224,6 +228,7 @@ import {
224
228
  sessionAttemptToolCatalogs,
225
229
  sessionBackgroundCommands,
226
230
  sessionCommandReceipts,
231
+ sessionEventCursors,
227
232
  sessionEvents,
228
233
  sessionGoalRevisions,
229
234
  sessionGoals,
@@ -303,6 +308,7 @@ import {
303
308
  workspaceArtifactVersions,
304
309
  workspaceArtifacts,
305
310
  workspaceCaptures,
311
+ workspaceCodexSubscriptionPreferences,
306
312
  workspaceControlEvents,
307
313
  workspaceInferenceControls,
308
314
  workspaceInstructionPolicyActivationEvents,
@@ -331,7 +337,7 @@ import {
331
337
  xaiRotationSettings,
332
338
  xaiSessionAccountPins,
333
339
  xaiSubscriptionCredentials
334
- } from "./chunk-UJ2EW3QI.js";
340
+ } from "./chunk-O56DM5GZ.js";
335
341
  import "./chunk-PZ5AY32C.js";
336
342
  export {
337
343
  agentRunStates,
@@ -381,6 +387,7 @@ export {
381
387
  companyBrainContextSelectionReceipts,
382
388
  companyBrainTurnContextSnapshots,
383
389
  companyProfileActivationEvents,
390
+ companyProfileAgentAutomaticActivationReceipts,
384
391
  companyProfileAgentConfirmationReceipts,
385
392
  companyProfileAgentProposalReceipts,
386
393
  companyProfileHeads,
@@ -495,6 +502,9 @@ export {
495
502
  nestedAgentDepthConfiguration,
496
503
  networkRoutes,
497
504
  newSessionDrafts,
505
+ organizationCodexRotationSettings,
506
+ organizationCompanyProfileAgentPolicies,
507
+ organizationCompanyProfileAgentPolicyEvents,
498
508
  organizationInvitationBindingEvents,
499
509
  organizationMembershipInvitations,
500
510
  organizationMembershipLifecycleEvents,
@@ -559,6 +569,7 @@ export {
559
569
  sessionAttemptToolCatalogs,
560
570
  sessionBackgroundCommands,
561
571
  sessionCommandReceipts,
572
+ sessionEventCursors,
562
573
  sessionEvents,
563
574
  sessionGoalRevisions,
564
575
  sessionGoals,
@@ -638,6 +649,7 @@ export {
638
649
  workspaceArtifactVersions,
639
650
  workspaceArtifacts,
640
651
  workspaceCaptures,
652
+ workspaceCodexSubscriptionPreferences,
641
653
  workspaceControlEvents,
642
654
  workspaceInferenceControls,
643
655
  workspaceInstructionPolicyActivationEvents,
@@ -12,9 +12,9 @@ import {
12
12
  settleClaimedConnectedMachineBackgroundCommand,
13
13
  settleConnectedMachineSessionBackgroundCommand,
14
14
  settleSessionBackgroundCommandForRetainedProcess
15
- } from "./chunk-CDMMSUWJ.js";
16
- import "./chunk-WW7AJ5IC.js";
17
- import "./chunk-UJ2EW3QI.js";
15
+ } from "./chunk-S3TDDCS2.js";
16
+ import "./chunk-F6PBS6WE.js";
17
+ import "./chunk-O56DM5GZ.js";
18
18
  import "./chunk-PZ5AY32C.js";
19
19
  export {
20
20
  backgroundCommandActivityForSessions,
@@ -60,6 +60,17 @@ export type EffectiveSessionControl = {
60
60
  commandCount: number;
61
61
  } | null;
62
62
  };
63
+ /**
64
+ * The bounded control projection required by a locked activity-write fence.
65
+ * Settlement and background-command summaries are read-model concerns and must
66
+ * not extend the session-event critical section when admission only needs the
67
+ * effective run state and blocker scope.
68
+ */
69
+ export type SessionWriteAdmissionControl = {
70
+ state: EffectiveControlState;
71
+ controlVersion: number;
72
+ primaryBlockerKind: EffectiveControlBlocker["kind"] | null;
73
+ };
63
74
  export declare const SESSION_DISCOVERY_CONTROL_TITLE_MAX_CHARS = 200;
64
75
  export declare const SESSION_DISCOVERY_CONTROL_TARGET_LIMIT = 100;
65
76
  /**
@@ -272,6 +283,13 @@ export type SessionEventWriteLockInput = {
272
283
  controlLock: WorkspaceControlLockMode | "already_locked" | "none";
273
284
  /** Used only when a staged caller already established the workspace prefix. */
274
285
  workspaceLock?: "key_share" | "already_locked";
286
+ /**
287
+ * Semantic writers retain FOR NO KEY UPDATE. Cursor-authoritative raw-only
288
+ * appends need only the FK-compatible identity hold; KEY SHARE is compatible
289
+ * with semantic NO KEY UPDATE and therefore removes the wide session row as
290
+ * their serialization mutex without inverting the rolling lock order.
291
+ */
292
+ sessionLock?: "no_key_update" | "key_share";
275
293
  sessionIds?: string[];
276
294
  turnIds?: string[];
277
295
  attemptIds?: string[];
@@ -280,6 +298,7 @@ export type SessionEventWriteLocks = {
280
298
  control: WorkspaceControlRow | null;
281
299
  workspace: typeof schema.workspaces.$inferSelect | null;
282
300
  sessions: Array<typeof schema.sessions.$inferSelect>;
301
+ cursors: Array<typeof schema.sessionEventCursors.$inferSelect>;
283
302
  turns: Array<typeof schema.sessionTurns.$inferSelect>;
284
303
  attempts: Array<typeof schema.sessionTurnAttempts.$inferSelect>;
285
304
  };
@@ -289,7 +308,8 @@ export type SessionEventWriteLocks = {
289
308
  * workspace-control advisory lock + workspace_inference_controls row
290
309
  * (when control-aware; see `lockWorkspaceInferenceControl`)
291
310
  * -> actual workspaces row FOR KEY SHARE
292
- * -> session rows FOR NO KEY UPDATE, UUID ordered
311
+ * -> session rows FOR NO KEY UPDATE (semantic) or KEY SHARE (raw), UUID ordered
312
+ * -> session event cursor rows FOR UPDATE, UUID ordered
293
313
  * -> exact turn rows FOR UPDATE, UUID ordered
294
314
  * -> exact attempt rows FOR UPDATE, UUID ordered
295
315
  *
@@ -304,10 +324,12 @@ export type SessionEventWriteLocks = {
304
324
  * `FOR UPDATE` lock serialized unrelated sessions and inverted the activity
305
325
  * path's session -> implicit workspace-FK edge.
306
326
  *
307
- * `FOR NO KEY UPDATE` is equally deliberate for sessions. Session primary and
308
- * composite identity keys are immutable, so writers need to exclude only concurrent
309
- * non-key mutation. Keeping FK `FOR KEY SHARE` checks compatible prevents the
310
- * activity finalizer's workspace counter from participating in a lock cycle.
327
+ * `FOR NO KEY UPDATE` is equally deliberate for semantic session writers.
328
+ * Cursor-authoritative raw-only writers use `FOR KEY SHARE`: it preserves the
329
+ * session identity against deletion while remaining compatible with semantic
330
+ * `FOR NO KEY UPDATE`. Keeping this rolling session-before-cursor order avoids
331
+ * an inversion with old binaries and quiescent cascade deletion; the compact
332
+ * cursor row, not the wide session row, is the raw ordering mutex.
311
333
  *
312
334
  * Complex lifecycle transactions may acquire allocator/control locks before
313
335
  * this helper and may discover exact turn IDs only after locking the session.
@@ -379,6 +401,19 @@ export declare function evaluateSessionControls(db: Database, workspaceId: strin
379
401
  /** Reuse a control row already locked before workspace/session/turn rows. */
380
402
  workspaceControl?: WorkspaceControlRow | undefined;
381
403
  }): Promise<Map<string, EffectiveSessionControl>>;
404
+ /**
405
+ * Evaluate only the control facts needed to admit an already locked session
406
+ * write. The workspace-control row must be reused when the caller established
407
+ * the canonical prefix before session/turn/attempt locks.
408
+ *
409
+ * Deliberately omit settlementAttemptCounts and
410
+ * stoppingBackgroundCommandCounts: those recursive summaries do not affect
411
+ * effective pause state and their results are discarded by the write fence.
412
+ */
413
+ export declare function evaluateSessionWriteAdmissionControl(db: Database, workspaceId: string, sessionId: string, options?: {
414
+ lock?: WorkspaceControlLockMode;
415
+ workspaceControl?: WorkspaceControlRow | undefined;
416
+ }): Promise<SessionWriteAdmissionControl>;
382
417
  /**
383
418
  * Return one compact aggregate row per target for `sessions_list`.
384
419
  *
@@ -67,8 +67,10 @@ export type AgentInternalUpdateCommandResult = {
67
67
  };
68
68
  type SteerSupersessionResult = {
69
69
  interruptionCount: number;
70
+ steerTargetTurn: typeof schema.sessionTurns.$inferSelect | null;
70
71
  replacedTurn: typeof schema.sessionTurns.$inferSelect | null;
71
72
  liveCurrentTurnId: string | null;
73
+ deferredUntilCompaction: boolean;
72
74
  lastSequence: number;
73
75
  };
74
76
  /**
@@ -14,9 +14,9 @@ import {
14
14
  replayAppliedSessionFork,
15
15
  sessionTenancyProductActivated,
16
16
  transitionSessionVisibility
17
- } from "./chunk-IW6O6G7R.js";
18
- import "./chunk-WW7AJ5IC.js";
19
- import "./chunk-UJ2EW3QI.js";
17
+ } from "./chunk-5NNCGFQY.js";
18
+ import "./chunk-F6PBS6WE.js";
19
+ import "./chunk-O56DM5GZ.js";
20
20
  import "./chunk-PZ5AY32C.js";
21
21
  export {
22
22
  SessionTenancyAccessError,
@@ -71,6 +71,14 @@ export declare function issueSelfUserResourceGrant(db: Database, input: {
71
71
  expectedAuthorityEpoch?: number | null;
72
72
  workspaceSharedAcknowledged: boolean;
73
73
  }): Promise<UserResourceAuthorityGrant>;
74
+ export declare function issueSelfLocalConnectionUseGrant(db: Database, input: {
75
+ accountId: string;
76
+ workspaceId: string;
77
+ subjectId: string;
78
+ authorityId: string;
79
+ context: "user_private" | "workspace_shared";
80
+ workspaceSharedAcknowledged: boolean;
81
+ }): Promise<UserResourceAuthorityGrant>;
74
82
  export declare function revokeSelfUserResourceGrant(db: Database, input: {
75
83
  accountId: string;
76
84
  workspaceId: string;
@@ -29,9 +29,9 @@ import {
29
29
  settleVideoGenerationFailure,
30
30
  settleVideoGenerationReady,
31
31
  updateWorkspaceVideoGenerationPolicy
32
- } from "./chunk-Q5YUGQVK.js";
33
- import "./chunk-WW7AJ5IC.js";
34
- import "./chunk-UJ2EW3QI.js";
32
+ } from "./chunk-SZAWDXQC.js";
33
+ import "./chunk-F6PBS6WE.js";
34
+ import "./chunk-O56DM5GZ.js";
35
35
  import "./chunk-PZ5AY32C.js";
36
36
  export {
37
37
  ACTIVE_VIDEO_GENERATION_STATUSES,
@@ -0,0 +1,350 @@
1
+ -- deployment-mode: rolling
2
+ -- Give the explicit single-user local identity the same private authority
3
+ -- anchor as a managed human. This does not add runtime access to the hidden
4
+ -- personal workspace and does not authorize configured keys or host services.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ DO $local_human_personal_authority$
10
+ DECLARE
11
+ data_schema text := current_schema();
12
+ BEGIN
13
+ EXECUTE format($ddl$
14
+ CREATE OR REPLACE FUNCTION %1$I.ensure_managed_human_personal_workspace(
15
+ p_account_id uuid,
16
+ p_subject_id text,
17
+ p_personal_workspace_id uuid
18
+ )
19
+ RETURNS TABLE (
20
+ organization_membership_id uuid,
21
+ personal_workspace_id uuid
22
+ )
23
+ LANGUAGE plpgsql
24
+ SECURITY DEFINER
25
+ SET search_path = %1$I, pg_catalog
26
+ AS $body$
27
+ DECLARE
28
+ expected_external_id text;
29
+ managed_identity boolean := false;
30
+ local_identity boolean := false;
31
+ membership_row organization_memberships%%ROWTYPE;
32
+ workspace_row workspaces%%ROWTYPE;
33
+ previous_workspace_id text := pg_catalog.current_setting(
34
+ 'opengeni.workspace_id', true
35
+ );
36
+ previous_lifecycle_marker text := pg_catalog.current_setting(
37
+ 'opengeni.organization_tenancy_lifecycle', true
38
+ );
39
+ BEGIN
40
+ IF p_account_id IS NULL
41
+ OR p_subject_id IS NULL
42
+ OR p_personal_workspace_id IS NULL
43
+ OR p_subject_id <> pg_catalog.btrim(p_subject_id)
44
+ OR pg_catalog.length(p_subject_id) NOT BETWEEN 1 AND 1024
45
+ THEN
46
+ RAISE EXCEPTION 'human provisioning requires a valid complete identity'
47
+ USING ERRCODE = '42501';
48
+ END IF;
49
+
50
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id() THEN
51
+ RAISE EXCEPTION 'human provisioning account authority is invalid'
52
+ USING ERRCODE = '42501';
53
+ END IF;
54
+
55
+ IF p_subject_id LIKE 'user:%%' THEN
56
+ expected_external_id := pg_catalog.substr(p_subject_id, 6);
57
+ SELECT EXISTS (
58
+ SELECT 1
59
+ FROM managed_accounts account_row
60
+ WHERE account_row.id = p_account_id
61
+ AND account_row.external_source = 'better-auth:user'
62
+ AND account_row.external_id = expected_external_id
63
+ ) INTO managed_identity;
64
+ END IF;
65
+ SELECT EXISTS (
66
+ SELECT 1
67
+ FROM managed_accounts account_row
68
+ WHERE account_row.id = p_account_id
69
+ AND account_row.external_source = 'opengeni:local'
70
+ AND account_row.external_id = 'default'
71
+ AND p_subject_id = 'dev'
72
+ AND opengeni_private.current_subject_id() = p_subject_id
73
+ ) INTO local_identity;
74
+ IF NOT managed_identity AND NOT local_identity THEN
75
+ RAISE EXCEPTION 'human provisioning account identity is invalid'
76
+ USING ERRCODE = '42501';
77
+ END IF;
78
+
79
+ IF NOT EXISTS (
80
+ SELECT 1
81
+ FROM workspace_memberships owner_membership
82
+ INNER JOIN workspaces owner_workspace
83
+ ON owner_workspace.id = owner_membership.workspace_id
84
+ AND owner_workspace.account_id = owner_membership.account_id
85
+ WHERE owner_membership.account_id = p_account_id
86
+ AND owner_membership.subject_id = p_subject_id
87
+ AND owner_membership.role = 'owner'
88
+ AND (
89
+ NOT local_identity
90
+ OR (
91
+ owner_workspace.external_source = 'opengeni:local'
92
+ AND owner_workspace.external_id = 'default'
93
+ )
94
+ )
95
+ ) THEN
96
+ RAISE EXCEPTION 'human provisioning owner membership is invalid'
97
+ USING ERRCODE = '42501';
98
+ END IF;
99
+
100
+ SELECT * INTO workspace_row
101
+ FROM workspaces candidate_workspace
102
+ WHERE candidate_workspace.id = p_personal_workspace_id
103
+ FOR UPDATE;
104
+ IF NOT FOUND
105
+ OR workspace_row.account_id IS DISTINCT FROM p_account_id
106
+ OR workspace_row.external_source IS DISTINCT FROM 'opengeni:organization-membership'
107
+ OR workspace_row.external_id IS DISTINCT FROM (
108
+ p_account_id::text || ':' || p_subject_id
109
+ )
110
+ THEN
111
+ RAISE EXCEPTION 'human provisioning personal workspace identity is invalid'
112
+ USING ERRCODE = '42501';
113
+ END IF;
114
+
115
+ IF EXISTS (
116
+ SELECT 1 FROM workspaces conflicting_workspace
117
+ WHERE conflicting_workspace.external_source = 'opengeni:organization-membership'
118
+ AND conflicting_workspace.external_id = p_account_id::text || ':' || p_subject_id
119
+ AND conflicting_workspace.id <> p_personal_workspace_id
120
+ ) THEN
121
+ RAISE EXCEPTION 'human provisioning personal workspace identity conflicts'
122
+ USING ERRCODE = '23505';
123
+ END IF;
124
+
125
+ IF EXISTS (
126
+ SELECT 1 FROM workspace_memberships personal_access
127
+ WHERE personal_access.workspace_id = p_personal_workspace_id
128
+ ) THEN
129
+ RAISE EXCEPTION 'human personal workspace already has runtime access'
130
+ USING ERRCODE = '42501';
131
+ END IF;
132
+
133
+ PERFORM pg_catalog.set_config(
134
+ 'opengeni.workspace_id', p_personal_workspace_id::text, true
135
+ );
136
+ IF NOT EXISTS (
137
+ SELECT 1 FROM workspace_inference_controls control_row
138
+ WHERE control_row.workspace_id = p_personal_workspace_id
139
+ AND control_row.account_id = p_account_id
140
+ ) THEN
141
+ RAISE EXCEPTION 'human personal workspace control is missing'
142
+ USING ERRCODE = '42501';
143
+ END IF;
144
+
145
+ PERFORM pg_catalog.set_config(
146
+ 'opengeni.organization_tenancy_lifecycle',
147
+ 'managed_human_provisioning',
148
+ true
149
+ );
150
+ INSERT INTO organization_memberships (
151
+ account_id, subject_id, status, personal_workspace_id
152
+ ) VALUES (
153
+ p_account_id, p_subject_id, 'active', p_personal_workspace_id
154
+ ) ON CONFLICT (account_id, subject_id) DO NOTHING;
155
+
156
+ SELECT * INTO membership_row
157
+ FROM organization_memberships candidate_membership
158
+ WHERE candidate_membership.account_id = p_account_id
159
+ AND candidate_membership.subject_id = p_subject_id
160
+ FOR UPDATE;
161
+ IF NOT FOUND THEN
162
+ RAISE EXCEPTION 'human organization membership was not created'
163
+ USING ERRCODE = 'P0002';
164
+ END IF;
165
+ IF membership_row.status IN ('suspended', 'revoked') THEN
166
+ RAISE EXCEPTION 'human organization membership is not active'
167
+ USING ERRCODE = '42501';
168
+ END IF;
169
+ IF membership_row.personal_workspace_id IS NOT NULL
170
+ AND membership_row.personal_workspace_id IS DISTINCT FROM p_personal_workspace_id
171
+ THEN
172
+ RAISE EXCEPTION 'human organization membership personal workspace conflicts'
173
+ USING ERRCODE = '23505';
174
+ END IF;
175
+ IF membership_row.status = 'provisioning' THEN
176
+ UPDATE organization_memberships
177
+ SET status = 'active',
178
+ personal_workspace_id = p_personal_workspace_id,
179
+ updated_at = pg_catalog.clock_timestamp()
180
+ WHERE id = membership_row.id;
181
+ membership_row.status := 'active';
182
+ membership_row.personal_workspace_id := p_personal_workspace_id;
183
+ END IF;
184
+ IF membership_row.status <> 'active'
185
+ OR membership_row.personal_workspace_id IS DISTINCT FROM p_personal_workspace_id
186
+ THEN
187
+ RAISE EXCEPTION 'human organization membership did not converge'
188
+ USING ERRCODE = '42501';
189
+ END IF;
190
+
191
+ PERFORM pg_catalog.set_config(
192
+ 'opengeni.workspace_id', coalesce(previous_workspace_id, ''), true
193
+ );
194
+ PERFORM pg_catalog.set_config(
195
+ 'opengeni.organization_tenancy_lifecycle',
196
+ coalesce(previous_lifecycle_marker, ''),
197
+ true
198
+ );
199
+ organization_membership_id := membership_row.id;
200
+ personal_workspace_id := membership_row.personal_workspace_id;
201
+ RETURN NEXT;
202
+ EXCEPTION WHEN OTHERS THEN
203
+ PERFORM pg_catalog.set_config(
204
+ 'opengeni.workspace_id', coalesce(previous_workspace_id, ''), true
205
+ );
206
+ PERFORM pg_catalog.set_config(
207
+ 'opengeni.organization_tenancy_lifecycle',
208
+ coalesce(previous_lifecycle_marker, ''),
209
+ true
210
+ );
211
+ RAISE;
212
+ END;
213
+ $body$;
214
+ $ddl$, data_schema);
215
+
216
+ EXECUTE format(
217
+ 'REVOKE ALL ON FUNCTION %I.ensure_managed_human_personal_workspace(uuid,text,uuid) FROM PUBLIC',
218
+ data_schema
219
+ );
220
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
221
+ EXECUTE format(
222
+ 'GRANT EXECUTE ON FUNCTION %I.ensure_managed_human_personal_workspace(uuid,text,uuid) TO opengeni_app',
223
+ data_schema
224
+ );
225
+ END IF;
226
+ END
227
+ $local_human_personal_authority$;
228
+
229
+ COMMENT ON FUNCTION ensure_managed_human_personal_workspace(uuid, text, uuid) IS
230
+ 'Lifecycle-only managed or explicit local-human organization membership and personal-workspace provisioning; no workspace access grant.';
231
+
232
+ CREATE FUNCTION issue_self_local_connection_use_grant(
233
+ p_account_id uuid,
234
+ p_authority_id uuid,
235
+ p_workspace_id uuid,
236
+ p_context text,
237
+ p_workspace_shared_acknowledged boolean DEFAULT false
238
+ ) RETURNS TABLE (
239
+ grant_id uuid, organization_id uuid, authority_generation bigint,
240
+ target_workspace_id uuid, target_session_id uuid, action text,
241
+ grant_mode text, grant_context text, authority_epoch integer,
242
+ grant_generation bigint, grant_status text, expires_at timestamptz
243
+ )
244
+ LANGUAGE plpgsql
245
+ SECURITY DEFINER
246
+ SET search_path FROM CURRENT
247
+ AS $body$
248
+ #variable_conflict use_column
249
+ DECLARE
250
+ caller_subject text := nullif(current_setting('opengeni.subject_id', true), '');
251
+ owner_membership_id uuid;
252
+ BEGIN
253
+ PERFORM set_config(
254
+ 'opengeni.organization_tenancy_lifecycle', 'personal_resource_grant_management', true
255
+ );
256
+ IF p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
257
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
258
+ OR caller_subject IS DISTINCT FROM 'dev'
259
+ OR p_context NOT IN ('user_private', 'workspace_shared')
260
+ OR (p_context = 'workspace_shared' AND p_workspace_shared_acknowledged IS NOT TRUE)
261
+ OR NOT EXISTS (
262
+ SELECT 1 FROM managed_accounts account_row
263
+ WHERE account_row.id = p_account_id
264
+ AND account_row.external_source = 'opengeni:local'
265
+ AND account_row.external_id = 'default'
266
+ )
267
+ THEN
268
+ RAISE EXCEPTION 'local connection grant authority is invalid'
269
+ USING ERRCODE = '42501';
270
+ END IF;
271
+
272
+ SELECT membership.id INTO owner_membership_id
273
+ FROM organization_memberships membership
274
+ WHERE membership.account_id = p_account_id
275
+ AND membership.subject_id = caller_subject
276
+ AND membership.status = 'active'
277
+ AND membership.revoked_at IS NULL
278
+ FOR SHARE;
279
+ IF NOT FOUND OR NOT EXISTS (
280
+ SELECT 1 FROM workspace_memberships workspace_membership
281
+ WHERE workspace_membership.account_id = p_account_id
282
+ AND workspace_membership.workspace_id = p_workspace_id
283
+ AND workspace_membership.subject_id = caller_subject
284
+ AND workspace_membership.role = 'owner'
285
+ ) THEN
286
+ RAISE EXCEPTION 'local connection grant owner is invalid'
287
+ USING ERRCODE = '42501';
288
+ END IF;
289
+
290
+ SELECT authority.generation INTO authority_generation
291
+ FROM organization_user_resource_authorities authority
292
+ WHERE authority.id = p_authority_id
293
+ AND authority.account_id = p_account_id
294
+ AND authority.organization_membership_id = owner_membership_id
295
+ AND authority.resource_kind = 'connection'
296
+ AND authority.status = 'active'
297
+ AND authority.revoked_at IS NULL
298
+ FOR UPDATE;
299
+ IF NOT FOUND THEN
300
+ RAISE EXCEPTION 'local connection authority is unavailable'
301
+ USING ERRCODE = '42501';
302
+ END IF;
303
+
304
+ INSERT INTO organization_user_resource_grants (
305
+ account_id, authority_id, owner_organization_membership_id, workspace_id,
306
+ session_id, action, mode, context, authority_epoch, generation, status
307
+ ) VALUES (
308
+ p_account_id, p_authority_id, owner_membership_id, p_workspace_id,
309
+ NULL, 'connection.use', 'always', p_context, NULL, 1, 'active'
310
+ )
311
+ ON CONFLICT (account_id, authority_id, workspace_id, action, mode, context,
312
+ (coalesce(session_id, '00000000-0000-0000-0000-000000000000'::uuid)),
313
+ (coalesce(authority_epoch, 0))) WHERE status = 'active'
314
+ DO UPDATE SET updated_at = organization_user_resource_grants.updated_at
315
+ RETURNING id, workspace_id, session_id, organization_user_resource_grants.action,
316
+ mode, context, organization_user_resource_grants.authority_epoch, generation,
317
+ status, organization_user_resource_grants.expires_at
318
+ INTO grant_id, target_workspace_id, target_session_id, action, grant_mode,
319
+ grant_context, authority_epoch, grant_generation, grant_status, expires_at;
320
+ organization_id := p_account_id;
321
+ RETURN NEXT;
322
+ END
323
+ $body$;
324
+
325
+ DO $local_connection_grant_search_path$
326
+ DECLARE
327
+ data_schema text := current_schema();
328
+ BEGIN
329
+ EXECUTE format(
330
+ 'ALTER FUNCTION %I.issue_self_local_connection_use_grant(uuid,uuid,uuid,text,boolean) SET search_path = pg_catalog, %I, pg_temp',
331
+ data_schema, data_schema
332
+ );
333
+ END
334
+ $local_connection_grant_search_path$;
335
+ REVOKE ALL ON FUNCTION issue_self_local_connection_use_grant(
336
+ uuid, uuid, uuid, text, boolean
337
+ ) FROM PUBLIC;
338
+ DO $local_connection_grant_role$
339
+ BEGIN
340
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
341
+ GRANT EXECUTE ON FUNCTION issue_self_local_connection_use_grant(
342
+ uuid, uuid, uuid, text, boolean
343
+ ) TO opengeni_app;
344
+ END IF;
345
+ END
346
+ $local_connection_grant_role$;
347
+
348
+ COMMENT ON FUNCTION issue_self_local_connection_use_grant(
349
+ uuid, uuid, uuid, text, boolean
350
+ ) IS 'Idempotently issues standing connection.use only for the explicit local dev human and exact local owner workspace.';
@@ -0,0 +1,98 @@
1
+ -- deployment-mode: rolling
2
+ -- Workspace administrators may manage access to their exact shared workspace,
3
+ -- but they may only add active human members of the owning organization.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '10min';
7
+
8
+ CREATE FUNCTION assert_workspace_member_management_candidate(
9
+ p_account_id uuid,
10
+ p_workspace_id uuid,
11
+ p_actor_subject_id text,
12
+ p_target_subject_id text
13
+ ) RETURNS void
14
+ LANGUAGE plpgsql SECURITY DEFINER
15
+ SET search_path FROM CURRENT
16
+ AS $body$
17
+ DECLARE
18
+ previous_lifecycle text := current_setting('opengeni.organization_tenancy_lifecycle', true);
19
+ BEGIN
20
+ IF p_account_id IS NULL OR p_workspace_id IS NULL
21
+ OR nullif(pg_catalog.btrim(p_actor_subject_id), '') IS NULL
22
+ OR nullif(pg_catalog.btrim(p_target_subject_id), '') IS NULL
23
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
24
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
25
+ OR p_actor_subject_id IS DISTINCT FROM opengeni_private.current_subject_id()
26
+ THEN
27
+ RAISE EXCEPTION 'workspace member management authority required' USING ERRCODE = '42501';
28
+ END IF;
29
+
30
+ PERFORM pg_catalog.set_config(
31
+ 'opengeni.organization_tenancy_lifecycle',
32
+ 'organization_membership_lifecycle', true
33
+ );
34
+
35
+ IF NOT EXISTS (
36
+ SELECT 1 FROM workspaces workspace
37
+ WHERE workspace.id = p_workspace_id AND workspace.account_id = p_account_id
38
+ ) THEN
39
+ RAISE EXCEPTION 'workspace not found' USING ERRCODE = 'P0002';
40
+ END IF;
41
+
42
+ IF EXISTS (
43
+ SELECT 1 FROM organization_memberships membership
44
+ WHERE membership.account_id = p_account_id
45
+ AND membership.personal_workspace_id = p_workspace_id
46
+ ) THEN
47
+ RAISE EXCEPTION 'Personal workspace membership is owner-only' USING ERRCODE = '42501';
48
+ END IF;
49
+
50
+ IF NOT EXISTS (
51
+ SELECT 1
52
+ FROM organization_memberships organization_membership
53
+ JOIN workspace_memberships workspace_membership
54
+ ON workspace_membership.account_id = organization_membership.account_id
55
+ AND workspace_membership.subject_id = organization_membership.subject_id
56
+ AND workspace_membership.workspace_id = p_workspace_id
57
+ WHERE organization_membership.account_id = p_account_id
58
+ AND organization_membership.subject_id = p_actor_subject_id
59
+ AND organization_membership.status = 'active'
60
+ AND (
61
+ workspace_membership.permissions ? 'members:manage'
62
+ OR workspace_membership.permissions ? 'workspace:admin'
63
+ )
64
+ ) THEN
65
+ RAISE EXCEPTION 'workspace member management authority required' USING ERRCODE = '42501';
66
+ END IF;
67
+
68
+ IF NOT EXISTS (
69
+ SELECT 1 FROM organization_memberships target
70
+ WHERE target.account_id = p_account_id
71
+ AND target.subject_id = p_target_subject_id
72
+ AND target.status = 'active'
73
+ ) THEN
74
+ RAISE EXCEPTION 'active organization member not found' USING ERRCODE = 'P0002';
75
+ END IF;
76
+
77
+ PERFORM pg_catalog.set_config(
78
+ 'opengeni.organization_tenancy_lifecycle', COALESCE(previous_lifecycle, ''), true
79
+ );
80
+ EXCEPTION WHEN OTHERS THEN
81
+ PERFORM pg_catalog.set_config(
82
+ 'opengeni.organization_tenancy_lifecycle', COALESCE(previous_lifecycle, ''), true
83
+ );
84
+ RAISE;
85
+ END
86
+ $body$;
87
+
88
+ REVOKE ALL ON FUNCTION assert_workspace_member_management_candidate(uuid, uuid, text, text)
89
+ FROM PUBLIC;
90
+ DO $workspace_member_management_role$
91
+ BEGIN
92
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
93
+ GRANT EXECUTE ON FUNCTION assert_workspace_member_management_candidate(
94
+ uuid, uuid, text, text
95
+ ) TO opengeni_app;
96
+ END IF;
97
+ END
98
+ $workspace_member_management_role$;