@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.
- package/dist/automations.d.ts +4 -0
- package/dist/canonical-human-identities.js +3 -3
- package/dist/{chunk-UGDVSQ6U.js → chunk-3ZMN4EXC.js} +2 -2
- package/dist/{chunk-744DFC65.js → chunk-6FIVOJ3V.js} +96 -6
- package/dist/chunk-6FIVOJ3V.js.map +1 -0
- package/dist/{chunk-UJ2EW3QI.js → chunk-DXJ2WY6W.js} +427 -85
- package/dist/chunk-DXJ2WY6W.js.map +1 -0
- package/dist/{chunk-CDMMSUWJ.js → chunk-JP6IYGYI.js} +3 -3
- package/dist/{chunk-Q5YUGQVK.js → chunk-JS3AWDKZ.js} +3 -3
- package/dist/{chunk-G3732M3T.js → chunk-LHKB62GC.js} +2 -2
- package/dist/{chunk-2NT476HD.js → chunk-QL4AHBYD.js} +2 -2
- package/dist/{chunk-IW6O6G7R.js → chunk-VDXHLVDE.js} +2 -2
- package/dist/{chunk-WW7AJ5IC.js → chunk-ZTSJPFDH.js} +5 -7
- package/dist/chunk-ZTSJPFDH.js.map +1 -0
- package/dist/codex-token-resolver.d.ts +12 -1
- package/dist/company-profile-agent-admin.d.ts +4 -2
- package/dist/company-profile-agent-policy.d.ts +20 -0
- package/dist/company-profile-schema.d.ts +573 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +487 -20
- package/dist/index.js +9489 -6041
- package/dist/index.js.map +1 -1
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/managed-human-provisioning.d.ts +3 -3
- package/dist/memory-domain.d.ts +5 -3
- package/dist/model-catalog.d.ts +153 -0
- package/dist/pr-review.d.ts +9 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +7 -7
- package/dist/schema.d.ts +1069 -6
- package/dist/schema.js +17 -1
- package/dist/session-background-commands.js +3 -3
- package/dist/session-control.d.ts +75 -5
- package/dist/session-queue-commands.d.ts +19 -0
- package/dist/session-tenancy.js +3 -3
- package/dist/user-resource-authority.d.ts +8 -0
- package/dist/video-generation.js +3 -3
- package/dist/xai-subscription.d.ts +8 -0
- package/drizzle/0369_local_human_personal_authority.sql +350 -0
- package/drizzle/0370_workspace_member_management_scope.sql +98 -0
- package/drizzle/0371_workspace_member_candidate_inventory.sql +128 -0
- package/drizzle/0372_ordered_variable_set_runtime_authority.sql +287 -0
- package/drizzle/0373_sandbox_shared_preparation.sql +59 -0
- package/drizzle/0374_session_event_cursors.sql +315 -0
- package/drizzle/0375_session_recovery_observability.sql +73 -0
- package/drizzle/0376_organization_workspace_inventory.sql +88 -0
- package/drizzle/0377_scoped_rig_health_projection.sql +80 -0
- package/drizzle/0378_scoped_rig_health_audit_timestamp.sql +77 -0
- package/drizzle/0379_session_event_raw_lane_activation.sql +518 -0
- package/drizzle/0380_autonomous_company_profile_agent_policy.sql +829 -0
- package/drizzle/0381_organization_codex_subscription_inheritance.sql +720 -0
- package/drizzle/0382_organization_api_key_provenance.sql +91 -0
- package/drizzle/0383_codex_cooldown_reconciliation.sql +51 -0
- package/drizzle/0384_codex_cooldown_revision_guard_privileges.sql +6 -0
- package/drizzle/0385_connected_machine_legacy_tilde_workdirs.sql +25 -0
- package/drizzle/0386_local_organization_administration.sql +157 -0
- package/drizzle/0387_bound_host_export_session_payloads.sql +165 -0
- package/drizzle/0388_sandbox_provider_deadline_interactions.sql +491 -0
- package/drizzle/0389_model_catalog_and_gateway_custom_models.sql +241 -0
- package/package.json +6 -6
- package/src/automations.ts +9 -1
- package/src/codex-token-resolver.ts +26 -1
- package/src/company-brain-governed-writes.ts +30 -1
- package/src/company-profile-agent-admin.ts +68 -1
- package/src/company-profile-agent-policy.ts +126 -0
- package/src/company-profile-schema.ts +115 -1
- package/src/database.ts +3 -5
- package/src/index.ts +4473 -570
- package/src/insights.ts +21 -7
- package/src/managed-human-provisioning.ts +3 -3
- package/src/memory-domain.ts +9 -4
- package/src/model-catalog.ts +682 -0
- package/src/organization-membership-lifecycle.ts +0 -1
- package/src/pr-review.ts +19 -0
- package/src/provision-roles.ts +83 -4
- package/src/runtime-posture.ts +26 -0
- package/src/schema.ts +259 -10
- package/src/session-control.ts +347 -59
- package/src/session-queue-commands.ts +303 -122
- package/src/session-tool-call-settlement.ts +34 -7
- package/src/user-resource-authority.ts +56 -0
- package/src/xai-subscription.ts +66 -14
- package/dist/chunk-744DFC65.js.map +0 -1
- package/dist/chunk-UJ2EW3QI.js.map +0 -1
- package/dist/chunk-WW7AJ5IC.js.map +0 -1
- /package/dist/{chunk-UGDVSQ6U.js.map → chunk-3ZMN4EXC.js.map} +0 -0
- /package/dist/{chunk-CDMMSUWJ.js.map → chunk-JP6IYGYI.js.map} +0 -0
- /package/dist/{chunk-Q5YUGQVK.js.map → chunk-JS3AWDKZ.js.map} +0 -0
- /package/dist/{chunk-G3732M3T.js.map → chunk-LHKB62GC.js.map} +0 -0
- /package/dist/{chunk-2NT476HD.js.map → chunk-QL4AHBYD.js.map} +0 -0
- /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$;
|