@opengeni/db 3.6.0 → 3.7.4-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 (92) hide show
  1. package/dist/automations.d.ts +4 -0
  2. package/dist/canonical-human-identities.js +3 -3
  3. package/dist/{chunk-UGDVSQ6U.js → chunk-3ZMN4EXC.js} +2 -2
  4. package/dist/{chunk-744DFC65.js → chunk-6FIVOJ3V.js} +96 -6
  5. package/dist/chunk-6FIVOJ3V.js.map +1 -0
  6. package/dist/{chunk-UJ2EW3QI.js → chunk-DXJ2WY6W.js} +427 -85
  7. package/dist/chunk-DXJ2WY6W.js.map +1 -0
  8. package/dist/{chunk-CDMMSUWJ.js → chunk-JP6IYGYI.js} +3 -3
  9. package/dist/{chunk-Q5YUGQVK.js → chunk-JS3AWDKZ.js} +3 -3
  10. package/dist/{chunk-G3732M3T.js → chunk-LHKB62GC.js} +2 -2
  11. package/dist/{chunk-2NT476HD.js → chunk-QL4AHBYD.js} +2 -2
  12. package/dist/{chunk-IW6O6G7R.js → chunk-VDXHLVDE.js} +2 -2
  13. package/dist/{chunk-WW7AJ5IC.js → chunk-ZTSJPFDH.js} +5 -7
  14. package/dist/chunk-ZTSJPFDH.js.map +1 -0
  15. package/dist/codex-token-resolver.d.ts +12 -1
  16. package/dist/company-profile-agent-admin.d.ts +4 -2
  17. package/dist/company-profile-agent-policy.d.ts +20 -0
  18. package/dist/company-profile-schema.d.ts +573 -0
  19. package/dist/editable-artifact-durable-export.js +2 -2
  20. package/dist/editable-artifacts.js +3 -3
  21. package/dist/index.d.ts +487 -20
  22. package/dist/index.js +9489 -6041
  23. package/dist/index.js.map +1 -1
  24. package/dist/managed-auth-session-sets.js +3 -3
  25. package/dist/managed-human-provisioning.d.ts +3 -3
  26. package/dist/memory-domain.d.ts +5 -3
  27. package/dist/model-catalog.d.ts +153 -0
  28. package/dist/pr-review.d.ts +9 -0
  29. package/dist/provision-roles.js +1 -1
  30. package/dist/runtime-posture.d.ts +7 -7
  31. package/dist/schema.d.ts +1069 -6
  32. package/dist/schema.js +17 -1
  33. package/dist/session-background-commands.js +3 -3
  34. package/dist/session-control.d.ts +75 -5
  35. package/dist/session-queue-commands.d.ts +19 -0
  36. package/dist/session-tenancy.js +3 -3
  37. package/dist/user-resource-authority.d.ts +8 -0
  38. package/dist/video-generation.js +3 -3
  39. package/dist/xai-subscription.d.ts +8 -0
  40. package/drizzle/0369_local_human_personal_authority.sql +350 -0
  41. package/drizzle/0370_workspace_member_management_scope.sql +98 -0
  42. package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
  43. package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
  44. package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
  45. package/drizzle/0374_session_event_cursors.sql +315 -0
  46. package/drizzle/0375_session_recovery_observability.sql +73 -0
  47. package/drizzle/0376_organization_workspace_inventory.sql +88 -0
  48. package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
  49. package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
  50. package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
  51. package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
  52. package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
  53. package/drizzle/0382_organization_api_key_provenance.sql +91 -0
  54. package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
  55. package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
  56. package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
  57. package/drizzle/0386_local_organization_administration.sql +157 -0
  58. package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
  59. package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
  60. package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
  61. package/package.json +6 -6
  62. package/src/automations.ts +9 -1
  63. package/src/codex-token-resolver.ts +26 -1
  64. package/src/company-brain-governed-writes.ts +30 -1
  65. package/src/company-profile-agent-admin.ts +68 -1
  66. package/src/company-profile-agent-policy.ts +126 -0
  67. package/src/company-profile-schema.ts +115 -1
  68. package/src/database.ts +3 -5
  69. package/src/index.ts +4473 -570
  70. package/src/insights.ts +21 -7
  71. package/src/managed-human-provisioning.ts +3 -3
  72. package/src/memory-domain.ts +9 -4
  73. package/src/model-catalog.ts +682 -0
  74. package/src/organization-membership-lifecycle.ts +0 -1
  75. package/src/pr-review.ts +19 -0
  76. package/src/provision-roles.ts +83 -4
  77. package/src/runtime-posture.ts +26 -0
  78. package/src/schema.ts +259 -10
  79. package/src/session-control.ts +347 -59
  80. package/src/session-queue-commands.ts +303 -122
  81. package/src/session-tool-call-settlement.ts +34 -7
  82. package/src/user-resource-authority.ts +56 -0
  83. package/src/xai-subscription.ts +66 -14
  84. package/dist/chunk-744DFC65.js.map +0 -1
  85. package/dist/chunk-UJ2EW3QI.js.map +0 -1
  86. package/dist/chunk-WW7AJ5IC.js.map +0 -1
  87. /package/dist/{chunk-UGDVSQ6U.js.map → chunk-3ZMN4EXC.js.map} +0 -0
  88. /package/dist/{chunk-CDMMSUWJ.js.map → chunk-JP6IYGYI.js.map} +0 -0
  89. /package/dist/{chunk-Q5YUGQVK.js.map → chunk-JS3AWDKZ.js.map} +0 -0
  90. /package/dist/{chunk-G3732M3T.js.map → chunk-LHKB62GC.js.map} +0 -0
  91. /package/dist/{chunk-2NT476HD.js.map → chunk-QL4AHBYD.js.map} +0 -0
  92. /package/dist/{chunk-IW6O6G7R.js.map → chunk-VDXHLVDE.js.map} +0 -0
@@ -0,0 +1,287 @@
1
+ -- deployment-mode: rolling
2
+ -- Migration 0352 activated ordered session Variable Set selections and extended
3
+ -- accepted personal-resource grants, but four runtime authorization seams
4
+ -- still consulted only the legacy final-entry `sessions.variable_set_id` alias.
5
+ -- A selected set earlier in `variable_set_ids` could therefore be accepted and
6
+ -- snapshotted correctly, then rejected during materialization or an exact agent
7
+ -- secret read. Keep every public signature, return type, grant, search path, and
8
+ -- audit behavior unchanged while switching those selection checks to the full
9
+ -- ordered session selection.
10
+
11
+ SET LOCAL lock_timeout = '5s';
12
+ SET LOCAL statement_timeout = '5min';
13
+
14
+ DO $repair_ordered_variable_set_runtime_authority$
15
+ DECLARE
16
+ data_schema text := pg_catalog.current_schema();
17
+ patch_record record;
18
+ function_oid regprocedure;
19
+ definition text;
20
+ patched text;
21
+ occurrences integer;
22
+ BEGIN
23
+ FOR patch_record IN
24
+ SELECT * FROM (VALUES
25
+ (
26
+ 'materialize_scoped_variable_set_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid)',
27
+ 'session_value.variable_set_id = variable_set_row.id',
28
+ 'coalesce(session_value.variable_set_ids, ''[]''::jsonb) ? variable_set_row.id::text'
29
+ ),
30
+ (
31
+ 'read_scoped_variable_set_secret(uuid,uuid,uuid,text,text,text,uuid,uuid,uuid,integer)',
32
+ 'session_value.variable_set_id = variable_set_row.id',
33
+ 'coalesce(session_value.variable_set_ids, ''[]''::jsonb) ? variable_set_row.id::text'
34
+ ),
35
+ (
36
+ 'authorize_session_attempt_personal_resource_reads(uuid,uuid,uuid)',
37
+ 'variable_set.id = session_value.variable_set_id',
38
+ 'coalesce(session_value.variable_set_ids, ''[]''::jsonb) ? variable_set.id::text'
39
+ )
40
+ ) AS patches(function_signature, old_anchor, new_anchor)
41
+ LOOP
42
+ function_oid := pg_catalog.to_regprocedure(
43
+ pg_catalog.quote_ident(data_schema) || '.' || patch_record.function_signature
44
+ );
45
+ IF function_oid IS NULL THEN
46
+ RAISE EXCEPTION '0372 required function is unavailable: %',
47
+ patch_record.function_signature
48
+ USING ERRCODE = '55000';
49
+ END IF;
50
+
51
+ definition := pg_catalog.pg_get_functiondef(function_oid);
52
+ occurrences := (
53
+ pg_catalog.length(definition)
54
+ - pg_catalog.length(pg_catalog.replace(definition, patch_record.old_anchor, ''))
55
+ ) / pg_catalog.length(patch_record.old_anchor);
56
+ IF occurrences <> 1 OR pg_catalog.strpos(definition, patch_record.new_anchor) > 0 THEN
57
+ RAISE EXCEPTION '0372 ordered Variable Set authority definition drift: %',
58
+ patch_record.function_signature
59
+ USING ERRCODE = '55000';
60
+ END IF;
61
+
62
+ patched := pg_catalog.replace(
63
+ definition,
64
+ patch_record.old_anchor,
65
+ patch_record.new_anchor
66
+ );
67
+ IF pg_catalog.strpos(patched, patch_record.new_anchor) = 0
68
+ OR pg_catalog.strpos(patched, patch_record.old_anchor) > 0
69
+ THEN
70
+ RAISE EXCEPTION '0372 ordered Variable Set authority patch failed: %',
71
+ patch_record.function_signature
72
+ USING ERRCODE = '55000';
73
+ END IF;
74
+ EXECUTE patched;
75
+ END LOOP;
76
+ END
77
+ $repair_ordered_variable_set_runtime_authority$;
78
+
79
+ -- The direct session-attach lane has no attempt snapshot to resolve. Admit a
80
+ -- user-scoped set only through the exact live owner membership, resource
81
+ -- authority, and session/always grant that accepted work issued for this
82
+ -- session. Every mutable authority row is locked before ciphertext egress so a
83
+ -- concurrent revocation wins before a later attach or attach retry.
84
+ CREATE OR REPLACE FUNCTION materialize_scoped_variable_set_for_session(
85
+ p_account_id uuid,
86
+ p_workspace_id uuid,
87
+ p_session_id uuid,
88
+ p_variable_set_id uuid
89
+ ) RETURNS TABLE (
90
+ variable_set_id uuid,
91
+ variable_set_name text,
92
+ variable_set_description text,
93
+ authority_scope text,
94
+ variable_set_generation bigint,
95
+ variable_name text,
96
+ value_encrypted text
97
+ )
98
+ LANGUAGE plpgsql
99
+ SECURITY DEFINER
100
+ SET search_path FROM CURRENT
101
+ AS $$
102
+ DECLARE
103
+ selected_row record;
104
+ variable_set_row workspace_variable_sets%ROWTYPE;
105
+ session_authority_epoch integer;
106
+ session_authority_visibility text;
107
+ session_owner_membership uuid;
108
+ audit_subject text;
109
+ causal_human text;
110
+ BEGIN
111
+ IF p_account_id IS DISTINCT FROM nullif(
112
+ pg_catalog.current_setting('opengeni.account_id', true), ''
113
+ )::uuid
114
+ OR p_workspace_id IS DISTINCT FROM nullif(
115
+ pg_catalog.current_setting('opengeni.workspace_id', true), ''
116
+ )::uuid
117
+ THEN
118
+ RAISE EXCEPTION 'variable-set session materialization scope mismatch'
119
+ USING ERRCODE = '42501';
120
+ END IF;
121
+
122
+ audit_subject := coalesce(
123
+ nullif(pg_catalog.current_setting('opengeni.subject_id', true), ''),
124
+ 'service:session'
125
+ );
126
+ causal_human := coalesce(
127
+ nullif(pg_catalog.current_setting('opengeni.initiating_human_subject_id', true), ''),
128
+ CASE WHEN audit_subject LIKE 'user:%' THEN audit_subject END
129
+ );
130
+
131
+ INSERT INTO opengeni_private.variable_set_authority_capabilities (
132
+ backend_pid, transaction_id, capability_kind
133
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'materialize')
134
+ ON CONFLICT DO NOTHING;
135
+ INSERT INTO opengeni_private.personal_resource_delegation_capabilities (
136
+ backend_pid, transaction_id, capability_kind
137
+ ) VALUES (pg_catalog.pg_backend_pid(), pg_catalog.pg_current_xact_id(), 'resolve')
138
+ ON CONFLICT DO NOTHING;
139
+
140
+ SELECT variable_set AS selected_set,
141
+ session_value.authority_epoch AS session_epoch,
142
+ session_value.visibility AS session_visibility,
143
+ session_value.owner_organization_membership_id AS session_owner_membership
144
+ INTO STRICT selected_row
145
+ FROM sessions session_value
146
+ JOIN workspace_variable_sets variable_set
147
+ ON variable_set.id = p_variable_set_id
148
+ AND variable_set.account_id = session_value.account_id
149
+ WHERE session_value.id = p_session_id
150
+ AND session_value.account_id = p_account_id
151
+ AND session_value.workspace_id = p_workspace_id
152
+ AND coalesce(session_value.variable_set_ids, '[]'::jsonb) ? p_variable_set_id::text
153
+ AND session_value.status IN (
154
+ 'queued', 'running', 'idle', 'requires_action', 'recovering', 'waiting_capacity'
155
+ )
156
+ AND variable_set.status = 'active'
157
+ AND (
158
+ variable_set.authority_scope IN ('organization', 'user')
159
+ OR (
160
+ variable_set.authority_scope = 'workspace'
161
+ AND variable_set.workspace_id = p_workspace_id
162
+ )
163
+ )
164
+ FOR SHARE OF session_value, variable_set;
165
+ variable_set_row := selected_row.selected_set;
166
+ session_authority_epoch := selected_row.session_epoch;
167
+ session_authority_visibility := selected_row.session_visibility;
168
+ session_owner_membership := selected_row.session_owner_membership;
169
+
170
+ IF variable_set_row.authority_scope = 'user' THEN
171
+ PERFORM 1
172
+ FROM organization_memberships membership
173
+ JOIN organization_user_resource_authorities authority
174
+ ON authority.id = variable_set_row.authority_id
175
+ AND authority.account_id = membership.account_id
176
+ AND authority.organization_membership_id = membership.id
177
+ AND authority.resource_kind = 'variable_set'
178
+ AND authority.resource_id = variable_set_row.id
179
+ AND authority.origin_workspace_id IS NOT DISTINCT FROM variable_set_row.origin_workspace_id
180
+ AND authority.generation = variable_set_row.generation
181
+ AND authority.status = 'active'
182
+ AND authority.revoked_at IS NULL
183
+ JOIN organization_user_resource_grants grant_value
184
+ ON grant_value.account_id = authority.account_id
185
+ AND grant_value.authority_id = authority.id
186
+ AND grant_value.owner_organization_membership_id = membership.id
187
+ AND grant_value.workspace_id = p_workspace_id
188
+ AND grant_value.action = 'variable_set.use'
189
+ AND grant_value.context = session_authority_visibility
190
+ AND grant_value.status = 'active'
191
+ AND (grant_value.expires_at IS NULL OR grant_value.expires_at > clock_timestamp())
192
+ AND (
193
+ (
194
+ grant_value.mode = 'session'
195
+ AND grant_value.session_id = p_session_id
196
+ AND grant_value.authority_epoch = session_authority_epoch
197
+ )
198
+ OR (
199
+ grant_value.mode = 'always'
200
+ AND grant_value.session_id IS NULL
201
+ AND grant_value.authority_epoch IS NULL
202
+ )
203
+ )
204
+ WHERE membership.id = variable_set_row.owner_organization_membership_id
205
+ AND membership.account_id = p_account_id
206
+ AND membership.subject_id = causal_human
207
+ AND membership.status = 'active'
208
+ AND membership.revoked_at IS NULL
209
+ AND membership.authorization_revision > 0
210
+ AND (
211
+ membership.personal_workspace_id = p_workspace_id
212
+ OR EXISTS (
213
+ SELECT 1
214
+ FROM workspace_memberships workspace_membership
215
+ WHERE workspace_membership.account_id = membership.account_id
216
+ AND workspace_membership.workspace_id = p_workspace_id
217
+ AND workspace_membership.subject_id = membership.subject_id
218
+ )
219
+ )
220
+ AND (
221
+ session_authority_visibility = 'workspace_shared'
222
+ OR session_owner_membership = membership.id
223
+ )
224
+ ORDER BY grant_value.id
225
+ FOR SHARE OF membership, authority, grant_value;
226
+ IF NOT FOUND THEN
227
+ RAISE EXCEPTION 'personal variable-set session grant is not exact or current'
228
+ USING ERRCODE = '42501';
229
+ END IF;
230
+ END IF;
231
+
232
+ INSERT INTO audit_events (
233
+ account_id, workspace_id, subject_id, action, target_type, target_id,
234
+ metadata, metadata_codec_version
235
+ ) VALUES (
236
+ p_account_id, p_workspace_id, audit_subject,
237
+ 'variable_set.materialized', 'workspace_variable_set',
238
+ variable_set_row.id::text,
239
+ pg_catalog.jsonb_build_object(
240
+ 'variableSetId', variable_set_row.id,
241
+ 'scope', variable_set_row.authority_scope,
242
+ 'generation', variable_set_row.generation,
243
+ 'actorKind', 'session_attach',
244
+ 'sessionId', p_session_id,
245
+ 'causalHumanSubjectId', causal_human,
246
+ 'authorityEpoch', session_authority_epoch,
247
+ 'authorityVisibility', session_authority_visibility,
248
+ 'authorityOwnerOrganizationMembershipId', session_owner_membership,
249
+ 'ownerAuthorityId', variable_set_row.authority_id,
250
+ 'ownerOrganizationMembershipId', variable_set_row.owner_organization_membership_id,
251
+ 'originWorkspaceId', variable_set_row.origin_workspace_id
252
+ ),
253
+ 1
254
+ );
255
+
256
+ RETURN QUERY
257
+ SELECT selected.id, selected.name, selected.description,
258
+ selected.authority_scope, selected.generation,
259
+ variable.name, variable.value_encrypted
260
+ FROM workspace_variable_sets selected
261
+ LEFT JOIN workspace_variable_set_variables variable
262
+ ON variable.account_id = selected.account_id
263
+ AND variable.variable_set_id = selected.id
264
+ WHERE selected.id = variable_set_row.id
265
+ ORDER BY variable.name;
266
+
267
+ DELETE FROM opengeni_private.personal_resource_delegation_capabilities
268
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
269
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
270
+ AND capability_kind = 'resolve';
271
+ DELETE FROM opengeni_private.variable_set_authority_capabilities
272
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
273
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
274
+ AND capability_kind = 'materialize';
275
+ RETURN;
276
+ EXCEPTION WHEN OTHERS THEN
277
+ DELETE FROM opengeni_private.personal_resource_delegation_capabilities
278
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
279
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
280
+ AND capability_kind = 'resolve';
281
+ DELETE FROM opengeni_private.variable_set_authority_capabilities
282
+ WHERE backend_pid = pg_catalog.pg_backend_pid()
283
+ AND transaction_id = pg_catalog.pg_current_xact_id_if_assigned()
284
+ AND capability_kind = 'materialize';
285
+ RAISE;
286
+ END
287
+ $$;
@@ -0,0 +1,59 @@
1
+ -- deployment-mode: rolling
2
+ -- Durable single-flight coordination for immutable, lease-scoped sandbox setup.
3
+
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '10min';
6
+
7
+ ALTER TABLE sandbox_leases
8
+ ADD COLUMN shared_preparation_lease_epoch integer,
9
+ ADD COLUMN shared_preparation_instance_id text,
10
+ ADD COLUMN shared_preparation_spec_hash text,
11
+ ADD COLUMN shared_preparation_status text,
12
+ ADD COLUMN shared_preparation_claim_id uuid,
13
+ ADD COLUMN shared_preparation_owner_attempt_id uuid,
14
+ ADD COLUMN shared_preparation_attempt integer,
15
+ ADD COLUMN shared_preparation_revision integer NOT NULL DEFAULT 0,
16
+ ADD COLUMN shared_preparation_started_at timestamptz,
17
+ ADD COLUMN shared_preparation_deadline_at timestamptz,
18
+ ADD COLUMN shared_preparation_settled_at timestamptz;
19
+
20
+ ALTER TABLE sandbox_leases
21
+ ADD CONSTRAINT sandbox_leases_shared_preparation_check CHECK (
22
+ (
23
+ shared_preparation_status IS NULL
24
+ AND shared_preparation_lease_epoch IS NULL
25
+ AND shared_preparation_instance_id IS NULL
26
+ AND shared_preparation_spec_hash IS NULL
27
+ AND shared_preparation_claim_id IS NULL
28
+ AND shared_preparation_owner_attempt_id IS NULL
29
+ AND shared_preparation_attempt IS NULL
30
+ AND shared_preparation_started_at IS NULL
31
+ AND shared_preparation_deadline_at IS NULL
32
+ AND shared_preparation_settled_at IS NULL
33
+ ) OR (
34
+ shared_preparation_status IN ('running', 'completed', 'failed')
35
+ AND shared_preparation_lease_epoch IS NOT NULL
36
+ AND shared_preparation_lease_epoch >= 0
37
+ AND nullif(btrim(shared_preparation_instance_id), '') IS NOT NULL
38
+ AND shared_preparation_spec_hash ~ '^sha256:[0-9a-f]{64}$'
39
+ AND shared_preparation_claim_id IS NOT NULL
40
+ AND shared_preparation_owner_attempt_id IS NOT NULL
41
+ AND shared_preparation_attempt IS NOT NULL
42
+ AND shared_preparation_attempt > 0
43
+ AND shared_preparation_started_at IS NOT NULL
44
+ AND shared_preparation_deadline_at > shared_preparation_started_at
45
+ AND (
46
+ (shared_preparation_status = 'running' AND shared_preparation_settled_at IS NULL)
47
+ OR (shared_preparation_status IN ('completed', 'failed')
48
+ AND shared_preparation_settled_at IS NOT NULL)
49
+ )
50
+ )
51
+ ) NOT VALID;
52
+
53
+ ALTER TABLE sandbox_leases
54
+ VALIDATE CONSTRAINT sandbox_leases_shared_preparation_check;
55
+
56
+ COMMENT ON COLUMN sandbox_leases.shared_preparation_spec_hash IS
57
+ 'Non-secret immutable setup specification coordinated once for the exact lease epoch and provider instance.';
58
+ COMMENT ON COLUMN sandbox_leases.shared_preparation_revision IS
59
+ 'Monotonic durable join cursor. Waiters treat notifications as hints and always re-read this lease row.';
@@ -0,0 +1,315 @@
1
+ -- deployment-mode: rolling
2
+ -- Phase A of the narrow session-event sequencer: mirror and verify every
3
+ -- committed append before any writer stops using sessions.last_sequence.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '10min';
7
+
8
+ CREATE TABLE session_event_cursors (
9
+ session_id uuid PRIMARY KEY REFERENCES sessions(id) ON DELETE CASCADE,
10
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
11
+ workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
12
+ last_sequence integer NOT NULL DEFAULT 0,
13
+ revision bigint NOT NULL DEFAULT 0,
14
+ created_at timestamptz NOT NULL DEFAULT now(),
15
+ updated_at timestamptz NOT NULL DEFAULT now(),
16
+ CONSTRAINT session_event_cursors_sequence_check CHECK (last_sequence >= 0),
17
+ CONSTRAINT session_event_cursors_revision_check CHECK (revision >= 0),
18
+ CONSTRAINT session_event_cursors_workspace_account_fk
19
+ FOREIGN KEY (workspace_id, account_id)
20
+ REFERENCES workspaces(id, account_id)
21
+ ON DELETE CASCADE,
22
+ CONSTRAINT session_event_cursors_workspace_session_fk
23
+ FOREIGN KEY (workspace_id, session_id)
24
+ REFERENCES sessions(workspace_id, id)
25
+ ON DELETE CASCADE
26
+ );
27
+
28
+ CREATE UNIQUE INDEX session_event_cursors_workspace_session_idx
29
+ ON session_event_cursors(workspace_id, session_id);
30
+
31
+ COMMENT ON TABLE session_event_cursors IS
32
+ 'Narrow exact per-session event cursor. Migration 0374 mirrors sessions.last_sequence; a later activation may make this row the allocator and admission gate.';
33
+ COMMENT ON COLUMN session_event_cursors.revision IS
34
+ 'Monotonic count of successful event-insert statements that advanced this cursor.';
35
+
36
+ ALTER TABLE session_event_cursors ENABLE ROW LEVEL SECURITY;
37
+ ALTER TABLE session_event_cursors FORCE ROW LEVEL SECURITY;
38
+
39
+ CREATE POLICY workspace_isolation ON session_event_cursors
40
+ USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
41
+ WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
42
+
43
+ -- The cursor is a direct session reference and must preserve private-session
44
+ -- visibility just like every other FORCE-RLS session-owned runtime table.
45
+ CREATE POLICY session_visibility_isolation ON session_event_cursors AS RESTRICTIVE
46
+ USING (session_reference_visible(account_id, workspace_id, session_id))
47
+ WITH CHECK (session_reference_visible(account_id, workspace_id, session_id));
48
+
49
+ DO $session_event_cursor_owner_policies$
50
+ DECLARE
51
+ target_schema text := pg_catalog.current_schema();
52
+ target_schema_oid oid := pg_catalog.current_schema()::pg_catalog.regnamespace;
53
+ migration_owner text := current_user;
54
+ BEGIN
55
+ EXECUTE pg_catalog.format(
56
+ 'CREATE POLICY session_tenancy_fence_inventory_read ON %I.session_event_cursors '
57
+ || 'FOR SELECT USING ('
58
+ || '%I.session_tenancy_fence_owner_policy_active('
59
+ || 'current_user, %L, %s::oid, workspace_id, true))',
60
+ target_schema,
61
+ target_schema,
62
+ migration_owner,
63
+ target_schema_oid
64
+ );
65
+ EXECUTE pg_catalog.format(
66
+ 'CREATE POLICY session_tenancy_fenced_owner_write ON %I.session_event_cursors '
67
+ || 'FOR ALL USING ('
68
+ || '%I.session_tenancy_fence_owner_policy_active('
69
+ || 'current_user, %L, %s::oid, workspace_id, false)) '
70
+ || 'WITH CHECK ('
71
+ || '%I.session_tenancy_fence_owner_policy_active('
72
+ || 'current_user, %L, %s::oid, workspace_id, false))',
73
+ target_schema,
74
+ target_schema,
75
+ migration_owner,
76
+ target_schema_oid,
77
+ target_schema,
78
+ migration_owner,
79
+ target_schema_oid
80
+ );
81
+ END
82
+ $session_event_cursor_owner_policies$;
83
+
84
+ -- FORCE RLS remains enabled for runtime roles. Temporarily let the table owner
85
+ -- inspect all existing rows while the migration transaction holds the DDL
86
+ -- locks, then restore FORCE before commit.
87
+ ALTER TABLE sessions NO FORCE ROW LEVEL SECURITY;
88
+ ALTER TABLE session_events NO FORCE ROW LEVEL SECURITY;
89
+ ALTER TABLE session_event_cursors NO FORCE ROW LEVEL SECURITY;
90
+
91
+ DO $session_event_cursor_parity$
92
+ DECLARE
93
+ mismatch record;
94
+ BEGIN
95
+ SELECT
96
+ sessions.id AS session_id,
97
+ sessions.last_sequence AS stored_sequence,
98
+ COUNT(session_events.sequence)::integer AS event_count,
99
+ MIN(session_events.sequence)::integer AS first_event_sequence,
100
+ MAX(session_events.sequence)::integer AS last_event_sequence
101
+ INTO mismatch
102
+ FROM sessions
103
+ LEFT JOIN session_events
104
+ ON session_events.workspace_id = sessions.workspace_id
105
+ AND session_events.session_id = sessions.id
106
+ GROUP BY sessions.id, sessions.last_sequence
107
+ HAVING COUNT(session_events.sequence)::integer <> sessions.last_sequence
108
+ OR (
109
+ sessions.last_sequence = 0
110
+ AND (
111
+ MIN(session_events.sequence) IS NOT NULL
112
+ OR MAX(session_events.sequence) IS NOT NULL
113
+ )
114
+ )
115
+ OR (
116
+ sessions.last_sequence > 0
117
+ AND (
118
+ MIN(session_events.sequence)::integer <> 1
119
+ OR MAX(session_events.sequence)::integer <> sessions.last_sequence
120
+ )
121
+ )
122
+ LIMIT 1;
123
+
124
+ IF FOUND THEN
125
+ RAISE EXCEPTION USING
126
+ ERRCODE = '55000',
127
+ MESSAGE = 'session event cursor backfill refused because sessions.last_sequence diverges from durable event history',
128
+ DETAIL = pg_catalog.format(
129
+ 'session_id=%s stored_sequence=%s event_count=%s first_event_sequence=%s last_event_sequence=%s',
130
+ mismatch.session_id,
131
+ mismatch.stored_sequence,
132
+ mismatch.event_count,
133
+ mismatch.first_event_sequence,
134
+ mismatch.last_event_sequence
135
+ );
136
+ END IF;
137
+ END
138
+ $session_event_cursor_parity$;
139
+
140
+ INSERT INTO session_event_cursors (
141
+ session_id,
142
+ account_id,
143
+ workspace_id,
144
+ last_sequence
145
+ )
146
+ SELECT id, account_id, workspace_id, last_sequence
147
+ FROM sessions;
148
+
149
+ ALTER TABLE session_event_cursors FORCE ROW LEVEL SECURITY;
150
+ ALTER TABLE session_events FORCE ROW LEVEL SECURITY;
151
+ ALTER TABLE sessions FORCE ROW LEVEL SECURITY;
152
+
153
+ CREATE FUNCTION initialize_session_event_cursors_for_inserted_sessions()
154
+ RETURNS trigger
155
+ LANGUAGE plpgsql
156
+ SET search_path FROM CURRENT
157
+ AS $initialize_session_event_cursors$
158
+ BEGIN
159
+ INSERT INTO session_event_cursors (
160
+ session_id,
161
+ account_id,
162
+ workspace_id,
163
+ last_sequence
164
+ )
165
+ -- A session row must exist before any event can reference it, so a newly
166
+ -- inserted session has no durable event history yet. Some lifecycle
167
+ -- functions pre-seed sessions.last_sequence for events they insert later in
168
+ -- the same transaction; copying that future projection here would make the
169
+ -- first real event look like a duplicate.
170
+ SELECT id, account_id, workspace_id, 0
171
+ FROM inserted_sessions
172
+ ON CONFLICT (session_id) DO NOTHING;
173
+ RETURN NULL;
174
+ END
175
+ $initialize_session_event_cursors$;
176
+
177
+ CREATE TRIGGER sessions_initialize_event_cursors
178
+ AFTER INSERT ON sessions
179
+ REFERENCING NEW TABLE AS inserted_sessions
180
+ FOR EACH STATEMENT
181
+ EXECUTE FUNCTION initialize_session_event_cursors_for_inserted_sessions();
182
+
183
+ CREATE FUNCTION advance_session_event_cursors_for_inserted_events()
184
+ RETURNS trigger
185
+ LANGUAGE plpgsql
186
+ SECURITY DEFINER
187
+ SET search_path FROM CURRENT
188
+ AS $advance_session_event_cursors$
189
+ DECLARE
190
+ fenced_access_capability_id uuid;
191
+ inserted_group record;
192
+ current_sequence integer;
193
+ BEGIN
194
+ -- Runtime writers already hold the exact workspace tenancy fence before an
195
+ -- event insert. Open only the transaction-local owner capability needed for
196
+ -- this trigger to inspect and advance the FORCE-RLS cursor under that fence.
197
+ fenced_access_capability_id :=
198
+ opengeni_private.open_session_tenancy_fenced_access(
199
+ session_tenancy_fence_target_schema()
200
+ );
201
+ FOR inserted_group IN
202
+ SELECT
203
+ account_id,
204
+ workspace_id,
205
+ session_id,
206
+ MIN(sequence)::integer AS first_sequence,
207
+ MAX(sequence)::integer AS last_sequence,
208
+ COUNT(*)::integer AS sequence_count,
209
+ COUNT(DISTINCT sequence)::integer AS distinct_sequence_count
210
+ FROM inserted_session_events
211
+ GROUP BY account_id, workspace_id, session_id
212
+ ORDER BY workspace_id, session_id
213
+ LOOP
214
+ SELECT last_sequence
215
+ INTO current_sequence
216
+ FROM session_event_cursors
217
+ WHERE account_id = inserted_group.account_id
218
+ AND workspace_id = inserted_group.workspace_id
219
+ AND session_id = inserted_group.session_id
220
+ FOR UPDATE;
221
+
222
+ IF NOT FOUND THEN
223
+ RAISE EXCEPTION USING
224
+ ERRCODE = '55000',
225
+ MESSAGE = 'session event cursor is missing for an inserted event',
226
+ DETAIL = pg_catalog.format(
227
+ 'workspace_id=%s session_id=%s',
228
+ inserted_group.workspace_id,
229
+ inserted_group.session_id
230
+ );
231
+ END IF;
232
+
233
+ IF inserted_group.distinct_sequence_count <> inserted_group.sequence_count
234
+ OR inserted_group.first_sequence <> current_sequence + 1
235
+ OR inserted_group.last_sequence <> current_sequence + inserted_group.sequence_count
236
+ THEN
237
+ RAISE EXCEPTION USING
238
+ ERRCODE = '55000',
239
+ MESSAGE = 'session event insert does not exactly continue the durable cursor',
240
+ DETAIL = pg_catalog.format(
241
+ 'workspace_id=%s session_id=%s current=%s first=%s last=%s count=%s distinct=%s',
242
+ inserted_group.workspace_id,
243
+ inserted_group.session_id,
244
+ current_sequence,
245
+ inserted_group.first_sequence,
246
+ inserted_group.last_sequence,
247
+ inserted_group.sequence_count,
248
+ inserted_group.distinct_sequence_count
249
+ );
250
+ END IF;
251
+
252
+ UPDATE session_event_cursors
253
+ SET last_sequence = inserted_group.last_sequence,
254
+ revision = revision + 1,
255
+ updated_at = now()
256
+ WHERE account_id = inserted_group.account_id
257
+ AND workspace_id = inserted_group.workspace_id
258
+ AND session_id = inserted_group.session_id
259
+ AND last_sequence = current_sequence;
260
+
261
+ IF NOT FOUND THEN
262
+ RAISE EXCEPTION USING
263
+ ERRCODE = '40001',
264
+ MESSAGE = 'session event cursor changed while applying an inserted event statement';
265
+ END IF;
266
+ END LOOP;
267
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
268
+ fenced_access_capability_id
269
+ );
270
+ RETURN NULL;
271
+ EXCEPTION WHEN OTHERS THEN
272
+ PERFORM opengeni_private.close_session_tenancy_fenced_access(
273
+ fenced_access_capability_id
274
+ );
275
+ RAISE;
276
+ END
277
+ $advance_session_event_cursors$;
278
+
279
+ -- Trigger functions may resolve target-schema relations, but pg_temp must not
280
+ -- precede those relations. In particular, the SECURITY DEFINER event trigger
281
+ -- must not be redirected through caller-owned temporary objects.
282
+ DO $session_event_cursor_function_paths$
283
+ DECLARE
284
+ target_schema text := pg_catalog.current_schema();
285
+ BEGIN
286
+ EXECUTE pg_catalog.format(
287
+ 'ALTER FUNCTION %I.initialize_session_event_cursors_for_inserted_sessions() '
288
+ || 'SET search_path = pg_catalog, %I, pg_temp',
289
+ target_schema,
290
+ target_schema
291
+ );
292
+ EXECUTE pg_catalog.format(
293
+ 'ALTER FUNCTION %I.advance_session_event_cursors_for_inserted_events() '
294
+ || 'SET search_path = pg_catalog, %I, pg_temp',
295
+ target_schema,
296
+ target_schema
297
+ );
298
+ END
299
+ $session_event_cursor_function_paths$;
300
+
301
+ REVOKE ALL ON FUNCTION advance_session_event_cursors_for_inserted_events() FROM PUBLIC;
302
+
303
+ CREATE TRIGGER session_events_advance_event_cursors
304
+ AFTER INSERT ON session_events
305
+ REFERENCING NEW TABLE AS inserted_session_events
306
+ FOR EACH STATEMENT
307
+ EXECUTE FUNCTION advance_session_event_cursors_for_inserted_events();
308
+
309
+ DO $session_event_cursor_runtime_grants$
310
+ BEGIN
311
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
312
+ GRANT SELECT, INSERT, UPDATE, DELETE ON session_event_cursors TO opengeni_app;
313
+ END IF;
314
+ END
315
+ $session_event_cursor_runtime_grants$;