@opengeni/db 3.7.4-canary.0 → 3.8.2-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 (62) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-DXJ2WY6W.js → chunk-53A2PEHT.js} +241 -31
  3. package/dist/chunk-53A2PEHT.js.map +1 -0
  4. package/dist/{chunk-JS3AWDKZ.js → chunk-5S4BQAHX.js} +3 -3
  5. package/dist/{chunk-JP6IYGYI.js → chunk-CITYUGMW.js} +3 -3
  6. package/dist/{chunk-6FIVOJ3V.js → chunk-F5YOZEYQ.js} +45 -1
  7. package/dist/chunk-F5YOZEYQ.js.map +1 -0
  8. package/dist/{chunk-ZTSJPFDH.js → chunk-HZV7O6JR.js} +2 -2
  9. package/dist/{chunk-VDXHLVDE.js → chunk-RLQVPOVR.js} +2 -2
  10. package/dist/{chunk-LHKB62GC.js → chunk-UKOGHUGC.js} +2 -2
  11. package/dist/{chunk-QL4AHBYD.js → chunk-X6TO5HII.js} +2 -2
  12. package/dist/{chunk-3ZMN4EXC.js → chunk-Y53WEFYB.js} +2 -2
  13. package/dist/connection-token-resolver.d.ts +4 -0
  14. package/dist/editable-artifact-durable-export.js +2 -2
  15. package/dist/editable-artifacts.js +3 -3
  16. package/dist/index.d.ts +59 -12
  17. package/dist/index.js +5259 -4374
  18. package/dist/index.js.map +1 -1
  19. package/dist/insights.d.ts +9 -0
  20. package/dist/managed-auth-session-sets.js +3 -3
  21. package/dist/new-session-drafts.d.ts +12 -0
  22. package/dist/organization-membership-lifecycle.d.ts +8 -1
  23. package/dist/organization-model-providers.d.ts +101 -0
  24. package/dist/pack-components.d.ts +1 -0
  25. package/dist/provision-roles.js +1 -1
  26. package/dist/runtime-posture.d.ts +5 -5
  27. package/dist/schema.d.ts +645 -0
  28. package/dist/schema.js +7 -1
  29. package/dist/session-background-commands.js +3 -3
  30. package/dist/session-tenancy.js +3 -3
  31. package/dist/video-generation.js +3 -3
  32. package/drizzle/0390_organization_model_provider_connections.sql +197 -0
  33. package/drizzle/0391_sandbox_provider_deadline_interaction_followup.sql +295 -0
  34. package/drizzle/0392_context_compaction_pending_observability.sql +198 -0
  35. package/drizzle/0393_workspace_memory_and_learning_defaults.sql +102 -0
  36. package/drizzle/0394_session_selected_skill_activation.sql +20 -0
  37. package/drizzle/0395_scheduled_task_unclaimed_occurrence_invalidation.sql +154 -0
  38. package/drizzle/0396_model_call_equivalent_credit_cost.sql +28 -0
  39. package/drizzle/0397_sandbox_deadline_rotation_preemption.sql +113 -0
  40. package/drizzle/0398_organization_workspace_management_entry.sql +206 -0
  41. package/drizzle/0399_additional_managed_organization_creation.sql +691 -0
  42. package/package.json +6 -6
  43. package/src/connection-token-resolver.ts +12 -0
  44. package/src/index.ts +701 -155
  45. package/src/insights-model-bundle.ts +54 -5
  46. package/src/insights.ts +32 -0
  47. package/src/new-session-drafts.ts +66 -21
  48. package/src/organization-membership-lifecycle.ts +47 -5
  49. package/src/organization-model-providers.ts +614 -0
  50. package/src/pack-components.ts +15 -2
  51. package/src/provision-roles.ts +2 -0
  52. package/src/runtime-posture.ts +44 -0
  53. package/src/schema.ts +250 -30
  54. package/dist/chunk-6FIVOJ3V.js.map +0 -1
  55. package/dist/chunk-DXJ2WY6W.js.map +0 -1
  56. /package/dist/{chunk-JS3AWDKZ.js.map → chunk-5S4BQAHX.js.map} +0 -0
  57. /package/dist/{chunk-JP6IYGYI.js.map → chunk-CITYUGMW.js.map} +0 -0
  58. /package/dist/{chunk-ZTSJPFDH.js.map → chunk-HZV7O6JR.js.map} +0 -0
  59. /package/dist/{chunk-VDXHLVDE.js.map → chunk-RLQVPOVR.js.map} +0 -0
  60. /package/dist/{chunk-LHKB62GC.js.map → chunk-UKOGHUGC.js.map} +0 -0
  61. /package/dist/{chunk-QL4AHBYD.js.map → chunk-X6TO5HII.js.map} +0 -0
  62. /package/dist/{chunk-3ZMN4EXC.js.map → chunk-Y53WEFYB.js.map} +0 -0
@@ -12,9 +12,9 @@ import {
12
12
  settleClaimedConnectedMachineBackgroundCommand,
13
13
  settleConnectedMachineSessionBackgroundCommand,
14
14
  settleSessionBackgroundCommandForRetainedProcess
15
- } from "./chunk-JP6IYGYI.js";
16
- import "./chunk-ZTSJPFDH.js";
17
- import "./chunk-DXJ2WY6W.js";
15
+ } from "./chunk-CITYUGMW.js";
16
+ import "./chunk-HZV7O6JR.js";
17
+ import "./chunk-53A2PEHT.js";
18
18
  import "./chunk-PZ5AY32C.js";
19
19
  export {
20
20
  backgroundCommandActivityForSessions,
@@ -14,9 +14,9 @@ import {
14
14
  replayAppliedSessionFork,
15
15
  sessionTenancyProductActivated,
16
16
  transitionSessionVisibility
17
- } from "./chunk-VDXHLVDE.js";
18
- import "./chunk-ZTSJPFDH.js";
19
- import "./chunk-DXJ2WY6W.js";
17
+ } from "./chunk-RLQVPOVR.js";
18
+ import "./chunk-HZV7O6JR.js";
19
+ import "./chunk-53A2PEHT.js";
20
20
  import "./chunk-PZ5AY32C.js";
21
21
  export {
22
22
  SessionTenancyAccessError,
@@ -29,9 +29,9 @@ import {
29
29
  settleVideoGenerationFailure,
30
30
  settleVideoGenerationReady,
31
31
  updateWorkspaceVideoGenerationPolicy
32
- } from "./chunk-JS3AWDKZ.js";
33
- import "./chunk-ZTSJPFDH.js";
34
- import "./chunk-DXJ2WY6W.js";
32
+ } from "./chunk-5S4BQAHX.js";
33
+ import "./chunk-HZV7O6JR.js";
34
+ import "./chunk-53A2PEHT.js";
35
35
  import "./chunk-PZ5AY32C.js";
36
36
  export {
37
37
  ACTIVE_VIDEO_GENERATION_STATUSES,
@@ -0,0 +1,197 @@
1
+ -- deployment-mode: maintenance
2
+ -- Organization-owned Vercel AI Gateway/OpenRouter credentials and model catalog.
3
+ -- Personal workspaces are deliberately excluded from runtime inheritance.
4
+
5
+ SET LOCAL lock_timeout = '5s';
6
+ SET LOCAL statement_timeout = '5min';
7
+
8
+ DO $scope_visibility$
9
+ DECLARE data_schema text := current_schema();
10
+ BEGIN
11
+ EXECUTE format($ddl$
12
+ CREATE OR REPLACE FUNCTION opengeni_private.organization_model_provider_scope_visible(
13
+ p_account_id uuid
14
+ ) RETURNS boolean
15
+ LANGUAGE plpgsql SECURITY DEFINER
16
+ SET search_path = pg_catalog
17
+ AS $function$
18
+ DECLARE
19
+ workspace_value uuid := opengeni_private.current_workspace_id();
20
+ subject_value text := opengeni_private.current_subject_id();
21
+ visible boolean := false;
22
+ previous_lifecycle text := pg_catalog.current_setting(
23
+ 'opengeni.organization_tenancy_lifecycle', true
24
+ );
25
+ BEGIN
26
+ IF p_account_id IS NULL
27
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
28
+ THEN RETURN false; END IF;
29
+
30
+ PERFORM pg_catalog.set_config(
31
+ 'opengeni.organization_tenancy_lifecycle',
32
+ 'organization_membership_lifecycle', true
33
+ );
34
+ IF workspace_value IS NOT NULL THEN
35
+ SELECT EXISTS (
36
+ SELECT 1 FROM %1$I.workspaces workspace
37
+ WHERE workspace.account_id = p_account_id AND workspace.id = workspace_value
38
+ ) AND NOT EXISTS (
39
+ SELECT 1 FROM %1$I.organization_memberships membership
40
+ WHERE membership.account_id = p_account_id
41
+ AND membership.personal_workspace_id = workspace_value
42
+ ) INTO visible;
43
+ ELSIF subject_value IS NOT NULL AND (
44
+ subject_value LIKE 'user:%%'
45
+ OR (
46
+ subject_value = 'dev'
47
+ AND EXISTS (
48
+ SELECT 1 FROM %1$I.managed_accounts account
49
+ WHERE account.id = p_account_id
50
+ AND account.external_source = 'opengeni:local'
51
+ AND account.external_id = 'default'
52
+ )
53
+ )
54
+ ) THEN
55
+ SELECT EXISTS (
56
+ SELECT 1 FROM %1$I.organization_memberships membership
57
+ WHERE membership.account_id = p_account_id
58
+ AND membership.subject_id = subject_value
59
+ AND membership.status = 'active'
60
+ AND membership.role IN ('owner', 'admin')
61
+ ) INTO visible;
62
+ END IF;
63
+ PERFORM pg_catalog.set_config(
64
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
65
+ );
66
+ RETURN coalesce(visible, false);
67
+ EXCEPTION WHEN OTHERS THEN
68
+ PERFORM pg_catalog.set_config(
69
+ 'opengeni.organization_tenancy_lifecycle', coalesce(previous_lifecycle, ''), true
70
+ );
71
+ RAISE;
72
+ END
73
+ $function$;
74
+ $ddl$, data_schema);
75
+ END
76
+ $scope_visibility$;
77
+
78
+ CREATE TABLE organization_model_provider_connections (
79
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
80
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
81
+ provider_kind text NOT NULL CHECK (provider_kind IN ('vercel_gateway', 'openrouter')),
82
+ status text NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'revoked')),
83
+ credential_encrypted text NOT NULL,
84
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
85
+ operation_id uuid NOT NULL,
86
+ request_hash text NOT NULL CHECK (request_hash ~ '^[a-f0-9]{64}$'),
87
+ updated_by_subject_id text NOT NULL CHECK (octet_length(updated_by_subject_id) BETWEEN 1 AND 1024),
88
+ created_at timestamptz NOT NULL DEFAULT now(),
89
+ updated_at timestamptz NOT NULL DEFAULT now(),
90
+ UNIQUE (account_id, provider_kind),
91
+ UNIQUE (account_id, provider_kind, operation_id)
92
+ );
93
+
94
+ CREATE TABLE organization_model_provider_connection_operations (
95
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
96
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
97
+ provider_kind text NOT NULL CHECK (provider_kind IN ('vercel_gateway', 'openrouter')),
98
+ operation_id uuid NOT NULL,
99
+ request_hash text NOT NULL CHECK (request_hash ~ '^[a-f0-9]{64}$'),
100
+ result_status text NOT NULL CHECK (result_status IN ('active', 'revoked')),
101
+ result_version integer NOT NULL CHECK (result_version > 0),
102
+ result_created_at timestamptz NOT NULL,
103
+ result_updated_at timestamptz NOT NULL,
104
+ created_at timestamptz NOT NULL DEFAULT now(),
105
+ UNIQUE (account_id, provider_kind, operation_id)
106
+ );
107
+
108
+ CREATE TABLE organization_model_provider_custom_models (
109
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
110
+ account_id uuid NOT NULL REFERENCES managed_accounts(id) ON DELETE CASCADE,
111
+ provider_kind text NOT NULL CHECK (provider_kind IN ('vercel_gateway', 'openrouter')),
112
+ upstream_model_id text NOT NULL CHECK (
113
+ octet_length(upstream_model_id) BETWEEN 1 AND 238
114
+ AND upstream_model_id ~ '^[!-~]+$' AND upstream_model_id !~ '[|]'
115
+ ),
116
+ label text CHECK (
117
+ label IS NULL OR (octet_length(label) BETWEEN 1 AND 128 AND label !~ '[\r\n|]')
118
+ ),
119
+ version integer NOT NULL DEFAULT 1 CHECK (version > 0),
120
+ create_operation_id uuid NOT NULL,
121
+ create_request_hash text NOT NULL CHECK (create_request_hash ~ '^[a-f0-9]{64}$'),
122
+ delete_operation_id uuid,
123
+ delete_request_hash text,
124
+ created_by_subject_id text NOT NULL CHECK (octet_length(created_by_subject_id) BETWEEN 1 AND 1024),
125
+ retired_at timestamptz,
126
+ created_at timestamptz NOT NULL DEFAULT now(),
127
+ updated_at timestamptz NOT NULL DEFAULT now(),
128
+ CHECK (
129
+ (delete_operation_id IS NULL AND delete_request_hash IS NULL)
130
+ OR (delete_operation_id IS NOT NULL AND delete_request_hash ~ '^[a-f0-9]{64}$' AND retired_at IS NOT NULL)
131
+ ),
132
+ UNIQUE (account_id, provider_kind, create_operation_id)
133
+ );
134
+ CREATE UNIQUE INDEX organization_model_provider_custom_models_active_uq
135
+ ON organization_model_provider_custom_models(account_id, provider_kind, upstream_model_id)
136
+ WHERE retired_at IS NULL;
137
+ CREATE UNIQUE INDEX organization_model_provider_custom_models_delete_operation_uq
138
+ ON organization_model_provider_custom_models(account_id, provider_kind, delete_operation_id)
139
+ WHERE delete_operation_id IS NOT NULL;
140
+
141
+ ALTER TABLE organization_model_provider_connections ENABLE ROW LEVEL SECURITY;
142
+ ALTER TABLE organization_model_provider_connections FORCE ROW LEVEL SECURITY;
143
+ ALTER TABLE organization_model_provider_connection_operations ENABLE ROW LEVEL SECURITY;
144
+ ALTER TABLE organization_model_provider_connection_operations FORCE ROW LEVEL SECURITY;
145
+ ALTER TABLE organization_model_provider_custom_models ENABLE ROW LEVEL SECURITY;
146
+ ALTER TABLE organization_model_provider_custom_models FORCE ROW LEVEL SECURITY;
147
+
148
+ CREATE POLICY organization_model_provider_scope
149
+ ON organization_model_provider_connections
150
+ USING (opengeni_private.organization_model_provider_scope_visible(account_id))
151
+ WITH CHECK (opengeni_private.organization_model_provider_scope_visible(account_id));
152
+ CREATE POLICY organization_model_provider_scope
153
+ ON organization_model_provider_connection_operations
154
+ USING (opengeni_private.organization_model_provider_scope_visible(account_id))
155
+ WITH CHECK (opengeni_private.organization_model_provider_scope_visible(account_id));
156
+ CREATE POLICY organization_model_provider_scope
157
+ ON organization_model_provider_custom_models
158
+ USING (opengeni_private.organization_model_provider_scope_visible(account_id))
159
+ WITH CHECK (opengeni_private.organization_model_provider_scope_visible(account_id));
160
+
161
+ REVOKE ALL ON organization_model_provider_connections FROM PUBLIC;
162
+ REVOKE ALL ON organization_model_provider_connection_operations FROM PUBLIC;
163
+ REVOKE ALL ON organization_model_provider_custom_models FROM PUBLIC;
164
+ REVOKE ALL ON FUNCTION opengeni_private.organization_model_provider_scope_visible(uuid) FROM PUBLIC;
165
+
166
+ DO $application_grants$
167
+ DECLARE
168
+ data_schema text := current_schema();
169
+ application_role text;
170
+ BEGIN
171
+ FOR application_role IN
172
+ SELECT role_value.rolname
173
+ FROM pg_catalog.jsonb_array_elements_text(
174
+ coalesce(nullif(current_setting('opengeni.migration_application_roles', true), ''), '[]')::jsonb
175
+ ) configured(value)
176
+ JOIN pg_catalog.pg_roles role_value ON role_value.rolname = configured.value
177
+ UNION SELECT 'opengeni_app'
178
+ WHERE EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = 'opengeni_app')
179
+ LOOP
180
+ EXECUTE format(
181
+ 'GRANT SELECT, INSERT, UPDATE ON TABLE %1$I.organization_model_provider_connections, %1$I.organization_model_provider_connection_operations, %1$I.organization_model_provider_custom_models TO %2$I',
182
+ data_schema, application_role
183
+ );
184
+ EXECUTE format(
185
+ 'GRANT EXECUTE ON FUNCTION opengeni_private.organization_model_provider_scope_visible(uuid) TO %I',
186
+ application_role
187
+ );
188
+ END LOOP;
189
+ END
190
+ $application_grants$;
191
+
192
+ COMMENT ON TABLE organization_model_provider_connections IS
193
+ 'Encrypted organization-owned Vercel AI Gateway/OpenRouter credentials inherited by shared workspaces only.';
194
+ COMMENT ON TABLE organization_model_provider_connection_operations IS
195
+ 'Durable secret-free idempotency receipts for organization provider connection mutations.';
196
+ COMMENT ON TABLE organization_model_provider_custom_models IS
197
+ 'Retained organization provider model generations; active rows are fresh-selection authority.';
@@ -0,0 +1,295 @@
1
+ -- deployment-mode: rolling
2
+ -- Repair the post-0388 provider-deadline interaction lifecycle without
3
+ -- widening ordinary interaction expiry or weakening the session-tenancy fence.
4
+ SET LOCAL lock_timeout = '5s';
5
+ SET LOCAL statement_timeout = '10min';
6
+
7
+ -- The TTL-aware fence inventory below must discover due lease-free controller
8
+ -- transitions before the SECURITY DEFINER reaper opens its fenced owner scope.
9
+ -- Keep this as inventory-only SELECT authority; the 0388 fenced owner policies
10
+ -- remain the sole mutation path after every workspace advisory lock is held.
11
+ DO $interaction_reaper_inventory_policies$
12
+ DECLARE
13
+ target_schema text := pg_catalog.current_schema();
14
+ target_schema_oid oid := pg_catalog.current_schema()::pg_catalog.regnamespace;
15
+ migration_owner text := current_user;
16
+ table_name text;
17
+ BEGIN
18
+ FOREACH table_name IN ARRAY ARRAY[
19
+ 'browser_sessions',
20
+ 'computer_sessions',
21
+ 'interaction_operations'
22
+ ] LOOP
23
+ EXECUTE pg_catalog.format(
24
+ 'DROP POLICY IF EXISTS session_tenancy_fence_inventory_read ON %I.%I',
25
+ target_schema,
26
+ table_name
27
+ );
28
+ EXECUTE pg_catalog.format(
29
+ 'CREATE POLICY session_tenancy_fence_inventory_read ON %I.%I '
30
+ || 'FOR SELECT USING ('
31
+ || '%I.session_tenancy_fence_owner_policy_active('
32
+ || 'current_user::text, %L::text, %s::oid, workspace_id, true))',
33
+ target_schema,
34
+ table_name,
35
+ target_schema,
36
+ migration_owner,
37
+ target_schema_oid
38
+ );
39
+ END LOOP;
40
+ END
41
+ $interaction_reaper_inventory_policies$;
42
+
43
+ -- The no-argument helper remains installed for reap_sandbox_leases() and old
44
+ -- in-flight callers. The interaction reaper uses this overload so its bounded
45
+ -- stale-operation batch can also see lease-free Connected Machine and attached-
46
+ -- device workspaces. Because SKIP LOCKED can advance beyond any preselected
47
+ -- operation row, fence every currently due workspace, but no healthy lease-free
48
+ -- interaction workspace, in canonical UUID order before taking row locks.
49
+ CREATE OR REPLACE FUNCTION acquire_sandbox_reaper_session_tenancy_fences(
50
+ p_interaction_holder_ttl_ms bigint
51
+ )
52
+ RETURNS integer
53
+ LANGUAGE plpgsql
54
+ SECURITY DEFINER
55
+ SET search_path FROM CURRENT
56
+ AS $interaction_reaper_fences$
57
+ DECLARE
58
+ inventory_capability_id uuid;
59
+ workspace_id_value uuid;
60
+ locked_count integer := 0;
61
+ BEGIN
62
+ IF p_interaction_holder_ttl_ms <= 0 THEN
63
+ RETURN 0;
64
+ END IF;
65
+ PERFORM pg_catalog.set_config('opengeni.sandbox_recovery_protocol_v2', '1', true);
66
+ inventory_capability_id :=
67
+ opengeni_private.open_session_tenancy_fence_inventory(
68
+ session_tenancy_fence_target_schema()
69
+ );
70
+ FOR workspace_id_value IN
71
+ SELECT candidate.workspace_id
72
+ FROM (
73
+ SELECT lease.workspace_id
74
+ FROM sandbox_leases lease
75
+ WHERE lease.workspace_id IS NOT NULL
76
+ AND (
77
+ lease.liveness <> 'cold'
78
+ OR EXISTS (
79
+ SELECT 1
80
+ FROM sandbox_lease_holders holder
81
+ WHERE holder.lease_id = lease.id
82
+ )
83
+ )
84
+ UNION
85
+ SELECT operation.workspace_id
86
+ FROM interaction_operations operation
87
+ WHERE operation.workspace_id IS NOT NULL
88
+ -- Lease-free prepared operations remain replayable. A dispatched
89
+ -- operation is the first controller-side effect and is the only
90
+ -- lease-free transition the ordinary stale-transition reaper settles.
91
+ AND operation.state = 'dispatched'
92
+ AND operation.updated_at
93
+ < pg_catalog.now()
94
+ - pg_catalog.make_interval(
95
+ secs => p_interaction_holder_ttl_ms / 1000.0
96
+ )
97
+ AND (
98
+ EXISTS (
99
+ SELECT 1
100
+ FROM browser_sessions browser
101
+ WHERE operation.resource_kind = 'browser_session'
102
+ AND operation.account_id = browser.account_id
103
+ AND operation.workspace_id = browser.workspace_id
104
+ AND operation.resource_id = browser.id
105
+ AND browser.lifecycle IN (
106
+ 'starting', 'suspending', 'restoring', 'ending'
107
+ )
108
+ )
109
+ OR EXISTS (
110
+ SELECT 1
111
+ FROM computer_sessions computer
112
+ WHERE operation.resource_kind = 'computer_session'
113
+ AND operation.account_id = computer.account_id
114
+ AND operation.workspace_id = computer.workspace_id
115
+ AND operation.resource_id = computer.id
116
+ AND computer.lifecycle IN ('starting', 'ending')
117
+ )
118
+ )
119
+ ) candidate
120
+ ORDER BY candidate.workspace_id
121
+ LOOP
122
+ PERFORM acquire_session_tenancy_fence(workspace_id_value);
123
+ locked_count := locked_count + 1;
124
+ END LOOP;
125
+ PERFORM opengeni_private.close_session_tenancy_fence_inventory(
126
+ inventory_capability_id
127
+ );
128
+ RETURN locked_count;
129
+ EXCEPTION WHEN OTHERS THEN
130
+ PERFORM opengeni_private.close_session_tenancy_fence_inventory(
131
+ inventory_capability_id
132
+ );
133
+ RAISE;
134
+ END
135
+ $interaction_reaper_fences$;
136
+
137
+ REVOKE ALL ON FUNCTION
138
+ acquire_sandbox_reaper_session_tenancy_fences(bigint)
139
+ FROM PUBLIC;
140
+
141
+ -- CREATE OR REPLACE records `SET search_path FROM CURRENT` from the migrator
142
+ -- connection. Re-pin this overload to the exact target schema before callers
143
+ -- can invoke the enclosing reaper, so a caller-created temp relation cannot
144
+ -- shadow its lease-free inventory before workspace fences are acquired.
145
+ DO $pin_interaction_reaper_fence_helper$
146
+ DECLARE
147
+ target_schema text := pg_catalog.current_schema();
148
+ BEGIN
149
+ EXECUTE pg_catalog.format(
150
+ 'ALTER FUNCTION %I.acquire_sandbox_reaper_session_tenancy_fences(bigint) '
151
+ || 'SET search_path = pg_catalog, %I, pg_temp',
152
+ target_schema,
153
+ target_schema
154
+ );
155
+ END
156
+ $pin_interaction_reaper_fence_helper$;
157
+
158
+ -- Patch only the exact 0388 definition. The interaction reaper keeps the
159
+ -- canonical advisory-fence -> owner capability -> operation -> lease -> holder
160
+ -- ordering. The holder EXISTS is an unlocked eligibility read before LIMIT;
161
+ -- lease rows are still the first ownership rows locked by the deadline phase.
162
+ DO $provider_deadline_interaction_followup$
163
+ DECLARE
164
+ data_schema text := pg_catalog.current_schema();
165
+ definition text;
166
+ patched text;
167
+ fence_anchor constant text :=
168
+ E' PERFORM acquire_sandbox_reaper_session_tenancy_fences();';
169
+ fence_replacement constant text :=
170
+ E' -- 0391 provider-deadline interaction follow-up: inventory due\n'
171
+ || E' -- lease-free transitions before any interaction row lock.\n'
172
+ || E' PERFORM acquire_sandbox_reaper_session_tenancy_fences(\n'
173
+ || E' p_interaction_holder_ttl_ms\n'
174
+ || E' );';
175
+ liveness_anchor constant text :=
176
+ E' AND lease.liveness IN (''warming'', ''warm'')';
177
+ liveness_replacement constant text :=
178
+ E' AND lease.liveness IN (''warming'', ''warm'', ''draining'')';
179
+ deadline_anchor constant text :=
180
+ E' AND lease.provider_deadline_at <= pg_catalog.now()';
181
+ deadline_replacement text;
182
+ occurrences integer;
183
+ BEGIN
184
+ SELECT pg_catalog.pg_get_functiondef(
185
+ 'opengeni_private.reap_stale_interaction_transitions(bigint)'::regprocedure
186
+ ) INTO definition;
187
+
188
+ IF definition IS NULL
189
+ OR pg_catalog.strpos(definition, 'deadline_lease_ids uuid[]') = 0
190
+ OR pg_catalog.strpos(definition, 'provider_deadline_rotation') = 0
191
+ OR pg_catalog.strpos(definition, 'open_session_tenancy_fenced_access') = 0
192
+ OR pg_catalog.strpos(definition, 'LIMIT 500') = 0
193
+ THEN
194
+ RAISE EXCEPTION '0391 interaction reaper prerequisite definition drift'
195
+ USING ERRCODE = '55000';
196
+ END IF;
197
+
198
+ IF pg_catalog.strpos(
199
+ definition,
200
+ '0391 provider-deadline interaction follow-up'
201
+ ) > 0 THEN
202
+ IF pg_catalog.strpos(
203
+ definition,
204
+ 'acquire_sandbox_reaper_session_tenancy_fences('
205
+ ) = 0
206
+ OR pg_catalog.strpos(
207
+ definition,
208
+ 'p_interaction_holder_ttl_ms'
209
+ ) = 0
210
+ OR pg_catalog.strpos(
211
+ definition,
212
+ 'lease.liveness IN (''warming'', ''warm'', ''draining'')'
213
+ ) = 0
214
+ OR pg_catalog.strpos(
215
+ definition,
216
+ 'interaction_holder.kind = ''interaction'''
217
+ ) = 0
218
+ THEN
219
+ RAISE EXCEPTION '0391 interaction reaper replay definition drift'
220
+ USING ERRCODE = '55000';
221
+ END IF;
222
+ ELSE
223
+ patched := definition;
224
+
225
+ occurrences := (
226
+ pg_catalog.length(patched)
227
+ - pg_catalog.length(pg_catalog.replace(patched, fence_anchor, ''))
228
+ ) / pg_catalog.length(fence_anchor);
229
+ IF occurrences <> 1 THEN
230
+ RAISE EXCEPTION '0391 interaction reaper fence anchor drift'
231
+ USING ERRCODE = '55000';
232
+ END IF;
233
+ patched := pg_catalog.replace(patched, fence_anchor, fence_replacement);
234
+
235
+ occurrences := (
236
+ pg_catalog.length(patched)
237
+ - pg_catalog.length(pg_catalog.replace(patched, liveness_anchor, ''))
238
+ ) / pg_catalog.length(liveness_anchor);
239
+ IF occurrences <> 1 THEN
240
+ RAISE EXCEPTION '0391 interaction reaper liveness anchor drift'
241
+ USING ERRCODE = '55000';
242
+ END IF;
243
+ patched := pg_catalog.replace(
244
+ patched,
245
+ liveness_anchor,
246
+ liveness_replacement
247
+ );
248
+
249
+ occurrences := (
250
+ pg_catalog.length(patched)
251
+ - pg_catalog.length(pg_catalog.replace(patched, deadline_anchor, ''))
252
+ ) / pg_catalog.length(deadline_anchor);
253
+ IF occurrences <> 1 THEN
254
+ RAISE EXCEPTION '0391 interaction reaper deadline anchor drift'
255
+ USING ERRCODE = '55000';
256
+ END IF;
257
+ deadline_replacement := deadline_anchor || pg_catalog.format(
258
+ E'\n AND EXISTS (\n'
259
+ || E' SELECT 1\n'
260
+ || E' FROM %I.sandbox_lease_holders interaction_holder\n'
261
+ || E' WHERE interaction_holder.lease_id = lease.id\n'
262
+ || E' AND interaction_holder.kind = ''interaction''\n'
263
+ || E' )',
264
+ data_schema
265
+ );
266
+ patched := pg_catalog.replace(
267
+ patched,
268
+ deadline_anchor,
269
+ deadline_replacement
270
+ );
271
+
272
+ IF pg_catalog.strpos(
273
+ patched,
274
+ '0391 provider-deadline interaction follow-up'
275
+ ) = 0
276
+ OR pg_catalog.strpos(
277
+ patched,
278
+ 'lease.liveness IN (''warming'', ''warm'', ''draining'')'
279
+ ) = 0
280
+ OR pg_catalog.strpos(
281
+ patched,
282
+ 'interaction_holder.kind = ''interaction'''
283
+ ) = 0
284
+ THEN
285
+ RAISE EXCEPTION '0391 interaction reaper patch failed'
286
+ USING ERRCODE = '55000';
287
+ END IF;
288
+
289
+ EXECUTE patched;
290
+ END IF;
291
+ END
292
+ $provider_deadline_interaction_followup$;
293
+
294
+ RESET statement_timeout;
295
+ RESET lock_timeout;