@opengeni/db 0.10.7 → 0.12.1
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/{chunk-P6PKXY5W.js → chunk-VUKRIBO5.js} +485 -12
- package/dist/chunk-VUKRIBO5.js.map +1 -0
- package/dist/{chunk-KW526IJA.js → chunk-Y5WZZVQK.js} +80 -4
- package/dist/chunk-Y5WZZVQK.js.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +5114 -2085
- package/dist/index.js.map +1 -1
- package/dist/migrate.d.ts +6 -3
- package/dist/migrate.js +1 -1
- package/dist/provision-roles.d.ts +720 -63
- package/dist/{schema-CqkzrBRS.d.ts → schema-BejThLcd.d.ts} +2965 -1194
- package/dist/schema.d.ts +1 -1
- package/dist/schema.js +17 -1
- package/drizzle/0109_nested_agent_depth_expand.sql +42 -0
- package/drizzle/0110_nested_agent_depth_boundary.sql +480 -0
- package/drizzle/0111_nested_agent_depth_backfill.sql +49 -0
- package/drizzle/0112_nested_agent_depth_contract.sql +38 -0
- package/drizzle/0113_nested_agent_depth_validate.sql +13 -0
- package/drizzle/0114_nested_agent_depth_contract.sql +49 -0
- package/drizzle/0115_nested_agent_depth_validate.sql +11 -0
- package/drizzle/0116_nested_agent_depth_index.sql +4 -0
- package/drizzle/0117_sandbox_recovery_generations.sql +699 -0
- package/drizzle/0118_new_session_drafts.sql +59 -0
- package/drizzle/0119_pending_tool_output_policy.sql +5 -0
- package/drizzle/0120_durable_goal_wake.sql +360 -0
- package/drizzle/0121_goal_update_idempotency.sql +11 -0
- package/package.json +3 -3
- package/src/index.ts +5961 -1240
- package/src/migrate.ts +131 -2
- package/src/new-session-drafts.ts +144 -0
- package/src/schema.ts +519 -15
- package/src/session-control.ts +42 -18
- package/src/session-tool-call-settlement.ts +6 -1
- package/dist/chunk-KW526IJA.js.map +0 -1
- package/dist/chunk-P6PKXY5W.js.map +0 -1
package/dist/schema.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import 'drizzle-orm';
|
|
2
2
|
import 'drizzle-orm/pg-core';
|
|
3
|
-
export {
|
|
3
|
+
export { o as agentRunStates, p as apiKeys, q as auditEvents, r as billingCustomers, t as capabilityCatalogItems, u as capabilityInstallations, v as codexCapacityWaiters, x as codexCredentialLeases, y as codexResetRedemptionAttempts, z as codexRotationSettings, A as codexSubscriptionCredentials, d as composerDrafts, B as connections, C as creditLedgerEntries, D as deviceEnrollmentRequests, h as deviceEnrollmentStatusValues, E as documentBases, F as documentChunks, G as documents, g as enrollmentExposureValues, f as enrollmentOsValues, i as enrollmentStatusValues, H as enrollments, I as fileUploads, J as files, K as githubInstallationRepositories, L as githubInstallations, M as hostExportConfig, N as hostExportConsumers, O as hostExportCursorState, P as hostExportDeadLetters, Q as hostExportOutbox, R as importBatches, S as integrationOauthClients, T as integrationOauthStateNonces, U as knowledgeMemories, V as machineMetricsLatest, W as machineMetricsSeries, X as managedAccounts, Y as nestedAgentDepthConfiguration, n as newSessionDrafts, Z as packInstallations, _ as rigChanges, $ as rigVersions, a0 as rigs, j as sandboxKindValues, a1 as sandboxLeaseHolders, a2 as sandboxLeaseLivenessValues, a3 as sandboxLeases, a4 as sandboxPtySessions, a5 as sandboxRetainedProcessStateValues, a6 as sandboxRetainedProcesses, a7 as sandboxSessionEnvelopes, a8 as sandboxWorkspaceMutationActorKindValues, a9 as sandboxWorkspaceMutationAdmissions, aa as sandboxWorkspaceMutationHolderKindValues, ab as sandboxes, ac as scheduledTaskRuns, ad as scheduledTasks, ae as sessionAttemptInterruptions, s as sessionCommandReceipts, af as sessionEvents, ag as sessionGoals, ah as sessionHistoryItems, ai as sessionHumanInputRequests, aj as sessionListSnapshots, ak as sessionMcpServers, al as sessionPendingToolCalls, am as sessionPins, k as sessionRecordingCodecValues, l as sessionRecordingModeValues, m as sessionRecordingStateValues, an as sessionRecordings, ao as sessionSpawnDenials, ap as sessionSystemUpdateOutbox, aq as sessionSystemUpdates, c as sessionTurnAttempts, b as sessionTurns, ar as sessionWorkflowWakeOutbox, a as sessions, as as socialConnections, at as socialPosts, au as stripeWebhookEvents, av as usageEvents, aw as workspaceCaptures, ax as workspaceControlEvents, ay as workspaceInferenceControls, az as workspaceMemberships, aA as workspaceModelPolicies, aB as workspacePacks, aC as workspaceSessionActivityRevisions, aD as workspaceVariableSetVariables, aE as workspaceVariableSets, w as workspaces } from './schema-BejThLcd.js';
|
package/dist/schema.js
CHANGED
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
machineMetricsLatest,
|
|
39
39
|
machineMetricsSeries,
|
|
40
40
|
managedAccounts,
|
|
41
|
+
nestedAgentDepthConfiguration,
|
|
42
|
+
newSessionDrafts,
|
|
41
43
|
packInstallations,
|
|
42
44
|
rigChanges,
|
|
43
45
|
rigVersions,
|
|
@@ -47,7 +49,12 @@ import {
|
|
|
47
49
|
sandboxLeaseLivenessValues,
|
|
48
50
|
sandboxLeases,
|
|
49
51
|
sandboxPtySessions,
|
|
52
|
+
sandboxRetainedProcessStateValues,
|
|
53
|
+
sandboxRetainedProcesses,
|
|
50
54
|
sandboxSessionEnvelopes,
|
|
55
|
+
sandboxWorkspaceMutationActorKindValues,
|
|
56
|
+
sandboxWorkspaceMutationAdmissions,
|
|
57
|
+
sandboxWorkspaceMutationHolderKindValues,
|
|
51
58
|
sandboxes,
|
|
52
59
|
scheduledTaskRuns,
|
|
53
60
|
scheduledTasks,
|
|
@@ -65,6 +72,7 @@ import {
|
|
|
65
72
|
sessionRecordingModeValues,
|
|
66
73
|
sessionRecordingStateValues,
|
|
67
74
|
sessionRecordings,
|
|
75
|
+
sessionSpawnDenials,
|
|
68
76
|
sessionSystemUpdateOutbox,
|
|
69
77
|
sessionSystemUpdates,
|
|
70
78
|
sessionTurnAttempts,
|
|
@@ -85,7 +93,7 @@ import {
|
|
|
85
93
|
workspaceVariableSetVariables,
|
|
86
94
|
workspaceVariableSets,
|
|
87
95
|
workspaces
|
|
88
|
-
} from "./chunk-
|
|
96
|
+
} from "./chunk-VUKRIBO5.js";
|
|
89
97
|
import "./chunk-PZ5AY32C.js";
|
|
90
98
|
export {
|
|
91
99
|
agentRunStates,
|
|
@@ -127,6 +135,8 @@ export {
|
|
|
127
135
|
machineMetricsLatest,
|
|
128
136
|
machineMetricsSeries,
|
|
129
137
|
managedAccounts,
|
|
138
|
+
nestedAgentDepthConfiguration,
|
|
139
|
+
newSessionDrafts,
|
|
130
140
|
packInstallations,
|
|
131
141
|
rigChanges,
|
|
132
142
|
rigVersions,
|
|
@@ -136,7 +146,12 @@ export {
|
|
|
136
146
|
sandboxLeaseLivenessValues,
|
|
137
147
|
sandboxLeases,
|
|
138
148
|
sandboxPtySessions,
|
|
149
|
+
sandboxRetainedProcessStateValues,
|
|
150
|
+
sandboxRetainedProcesses,
|
|
139
151
|
sandboxSessionEnvelopes,
|
|
152
|
+
sandboxWorkspaceMutationActorKindValues,
|
|
153
|
+
sandboxWorkspaceMutationAdmissions,
|
|
154
|
+
sandboxWorkspaceMutationHolderKindValues,
|
|
140
155
|
sandboxes,
|
|
141
156
|
scheduledTaskRuns,
|
|
142
157
|
scheduledTasks,
|
|
@@ -154,6 +169,7 @@ export {
|
|
|
154
169
|
sessionRecordingModeValues,
|
|
155
170
|
sessionRecordingStateValues,
|
|
156
171
|
sessionRecordings,
|
|
172
|
+
sessionSpawnDenials,
|
|
157
173
|
sessionSystemUpdateOutbox,
|
|
158
174
|
sessionSystemUpdates,
|
|
159
175
|
sessionTurnAttempts,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Expand phase: persist deployment policy and add nullable lineage columns.
|
|
3
|
+
SET lock_timeout = '5s';
|
|
4
|
+
SET statement_timeout = '30s';
|
|
5
|
+
|
|
6
|
+
CREATE TABLE "nested_agent_depth_configuration" (
|
|
7
|
+
"singleton" boolean PRIMARY KEY DEFAULT true NOT NULL,
|
|
8
|
+
"max_nested_agent_depth" integer NOT NULL,
|
|
9
|
+
"policy_source" text NOT NULL,
|
|
10
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
11
|
+
CONSTRAINT "nested_agent_depth_configuration_singleton_check" CHECK ("singleton"),
|
|
12
|
+
CONSTRAINT "nested_agent_depth_configuration_max_check"
|
|
13
|
+
CHECK ("max_nested_agent_depth" >= 0 AND "max_nested_agent_depth" <= 2147483647),
|
|
14
|
+
CONSTRAINT "nested_agent_depth_configuration_source_check"
|
|
15
|
+
CHECK ("policy_source" IN ('deployment', 'default'))
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
DO $grants$
|
|
19
|
+
BEGIN
|
|
20
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
21
|
+
EXECUTE format('GRANT SELECT ON %I."nested_agent_depth_configuration" TO opengeni_app', current_schema());
|
|
22
|
+
END IF;
|
|
23
|
+
END $grants$;
|
|
24
|
+
|
|
25
|
+
INSERT INTO "nested_agent_depth_configuration" (
|
|
26
|
+
"singleton", "max_nested_agent_depth", "policy_source"
|
|
27
|
+
) VALUES (
|
|
28
|
+
true,
|
|
29
|
+
coalesce(nullif(current_setting('opengeni.max_nested_agent_depth', true), ''), '3')::integer,
|
|
30
|
+
coalesce(nullif(current_setting('opengeni.nested_agent_depth_policy_source', true), ''), 'default')
|
|
31
|
+
) ON CONFLICT ("singleton") DO NOTHING;
|
|
32
|
+
|
|
33
|
+
ALTER TABLE "sessions"
|
|
34
|
+
ADD COLUMN IF NOT EXISTS "root_session_id" uuid,
|
|
35
|
+
ADD COLUMN IF NOT EXISTS "nested_agent_depth" integer,
|
|
36
|
+
ADD COLUMN IF NOT EXISTS "max_nested_agent_depth_override" integer,
|
|
37
|
+
ADD COLUMN IF NOT EXISTS "effective_max_nested_agent_depth" integer,
|
|
38
|
+
ADD COLUMN IF NOT EXISTS "nested_agent_depth_policy_source" text,
|
|
39
|
+
ADD COLUMN IF NOT EXISTS "nested_agent_depth_policy_session_id" uuid;
|
|
40
|
+
|
|
41
|
+
RESET statement_timeout;
|
|
42
|
+
RESET lock_timeout;
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Mixed-version admission boundary, denial evidence, idempotency fencing,
|
|
3
|
+
-- policy lock/read, and RLS. This boundary must be installed before the
|
|
4
|
+
-- batched lineage backfill: old writers remain allowed to insert sessions and
|
|
5
|
+
-- this trigger supplies the new snapshot while the backfill is in flight.
|
|
6
|
+
SET lock_timeout = '5s';
|
|
7
|
+
SET statement_timeout = '30s';
|
|
8
|
+
|
|
9
|
+
-- A partial unique index on sessions and a separate partial unique index on
|
|
10
|
+
-- denials cannot prevent one old writer from committing a success while a
|
|
11
|
+
-- newer writer commits a denial for the same workspace/key. This small
|
|
12
|
+
-- cross-outcome ledger is the database authority for that decision. It is
|
|
13
|
+
-- intentionally private to the trigger functions; callers replay the outcome
|
|
14
|
+
-- from the durable source row.
|
|
15
|
+
CREATE TABLE "session_create_idempotency_guard" (
|
|
16
|
+
"workspace_id" uuid NOT NULL,
|
|
17
|
+
"idempotency_key" text NOT NULL,
|
|
18
|
+
"outcome" text NOT NULL,
|
|
19
|
+
"session_id" uuid,
|
|
20
|
+
"denial_id" uuid,
|
|
21
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
22
|
+
CONSTRAINT "session_create_idempotency_guard_pk"
|
|
23
|
+
PRIMARY KEY ("workspace_id", "idempotency_key"),
|
|
24
|
+
CONSTRAINT "session_create_idempotency_guard_workspace_fk"
|
|
25
|
+
FOREIGN KEY ("workspace_id")
|
|
26
|
+
REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
27
|
+
CONSTRAINT "session_create_idempotency_guard_outcome_check"
|
|
28
|
+
CHECK ("outcome" IN ('session', 'denial')),
|
|
29
|
+
CONSTRAINT "session_create_idempotency_guard_target_check"
|
|
30
|
+
CHECK (("outcome" = 'session' AND "session_id" IS NOT NULL AND "denial_id" IS NULL)
|
|
31
|
+
OR ("outcome" = 'denial' AND "session_id" IS NULL AND "denial_id" IS NOT NULL))
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
CREATE TABLE "session_spawn_denials" (
|
|
35
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
36
|
+
"account_id" uuid NOT NULL,
|
|
37
|
+
"workspace_id" uuid NOT NULL,
|
|
38
|
+
"parent_session_id" uuid,
|
|
39
|
+
"root_session_id" uuid,
|
|
40
|
+
"current_depth" integer NOT NULL,
|
|
41
|
+
"attempted_depth" bigint NOT NULL,
|
|
42
|
+
"effective_max_nested_agent_depth" integer NOT NULL,
|
|
43
|
+
"requested_max_nested_agent_depth_override" integer,
|
|
44
|
+
"policy_source" text NOT NULL,
|
|
45
|
+
"policy_session_id" uuid,
|
|
46
|
+
"subject_id" text,
|
|
47
|
+
"code" text NOT NULL,
|
|
48
|
+
"idempotency_key" text,
|
|
49
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
50
|
+
CONSTRAINT "session_spawn_denials_workspace_account_fk"
|
|
51
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
52
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE,
|
|
53
|
+
CONSTRAINT "session_spawn_denials_depth_check"
|
|
54
|
+
CHECK ("current_depth" >= 0 AND "attempted_depth" >= 0
|
|
55
|
+
AND "effective_max_nested_agent_depth" >= 0
|
|
56
|
+
AND ("requested_max_nested_agent_depth_override" IS NULL
|
|
57
|
+
OR "requested_max_nested_agent_depth_override" >= 0)),
|
|
58
|
+
CONSTRAINT "session_spawn_denials_policy_source_check"
|
|
59
|
+
CHECK ("policy_source" IN ('session', 'workspace', 'deployment', 'default')),
|
|
60
|
+
CONSTRAINT "session_spawn_denials_code_check"
|
|
61
|
+
CHECK ("code" IN ('nested_agent_depth_exceeded', 'nested_agent_depth_override_forbidden'))
|
|
62
|
+
);
|
|
63
|
+
CREATE UNIQUE INDEX "session_spawn_denials_workspace_id_uq"
|
|
64
|
+
ON "session_spawn_denials" ("workspace_id", "id");
|
|
65
|
+
CREATE INDEX "session_spawn_denials_workspace_created_idx"
|
|
66
|
+
ON "session_spawn_denials" ("workspace_id", "created_at");
|
|
67
|
+
CREATE INDEX "session_spawn_denials_parent_idx"
|
|
68
|
+
ON "session_spawn_denials" ("workspace_id", "parent_session_id", "created_at");
|
|
69
|
+
CREATE UNIQUE INDEX "session_spawn_denials_workspace_idempotency_idx"
|
|
70
|
+
ON "session_spawn_denials" ("workspace_id", "idempotency_key")
|
|
71
|
+
WHERE "idempotency_key" IS NOT NULL;
|
|
72
|
+
|
|
73
|
+
CREATE OR REPLACE FUNCTION opengeni_private.session_create_idempotency_guard_insert()
|
|
74
|
+
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
75
|
+
AS $function$
|
|
76
|
+
DECLARE
|
|
77
|
+
inserted_rows integer;
|
|
78
|
+
new_outcome text;
|
|
79
|
+
new_session_id uuid;
|
|
80
|
+
new_denial_id uuid;
|
|
81
|
+
new_idempotency_key text;
|
|
82
|
+
BEGIN
|
|
83
|
+
IF TG_TABLE_NAME = 'sessions' THEN
|
|
84
|
+
new_idempotency_key := NEW.create_idempotency_key;
|
|
85
|
+
new_outcome := 'session';
|
|
86
|
+
new_session_id := NEW.id;
|
|
87
|
+
new_denial_id := NULL;
|
|
88
|
+
ELSE
|
|
89
|
+
new_idempotency_key := NEW.idempotency_key;
|
|
90
|
+
new_outcome := 'denial';
|
|
91
|
+
new_session_id := NULL;
|
|
92
|
+
new_denial_id := NEW.id;
|
|
93
|
+
END IF;
|
|
94
|
+
IF new_idempotency_key IS NULL THEN
|
|
95
|
+
RETURN NEW;
|
|
96
|
+
END IF;
|
|
97
|
+
|
|
98
|
+
-- This is the exact lock used by the application admission path. The
|
|
99
|
+
-- trigger must take it before reserving the cross-outcome ledger row so an
|
|
100
|
+
-- old writer and a current writer cannot choose different winners for the
|
|
101
|
+
-- same workspace/key. The depth trigger already took this same advisory
|
|
102
|
+
-- lock and checked for an existing winner; taking it again is
|
|
103
|
+
-- transaction-reentrant and keeps this guard safe for direct old writers as
|
|
104
|
+
-- well as the current application.
|
|
105
|
+
PERFORM pg_advisory_xact_lock(
|
|
106
|
+
hashtext('session-create:' || NEW.workspace_id::text || ':' || new_idempotency_key)
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
EXECUTE format($sql$
|
|
110
|
+
INSERT INTO %I."session_create_idempotency_guard"
|
|
111
|
+
("workspace_id", "idempotency_key", "outcome", "session_id", "denial_id")
|
|
112
|
+
VALUES ($1, $2, $3, $4, $5)
|
|
113
|
+
ON CONFLICT ("workspace_id", "idempotency_key") DO NOTHING
|
|
114
|
+
$sql$, TG_TABLE_SCHEMA)
|
|
115
|
+
USING NEW.workspace_id, new_idempotency_key, new_outcome, new_session_id, new_denial_id;
|
|
116
|
+
GET DIAGNOSTICS inserted_rows = ROW_COUNT;
|
|
117
|
+
IF inserted_rows = 1 THEN
|
|
118
|
+
RETURN NEW;
|
|
119
|
+
END IF;
|
|
120
|
+
|
|
121
|
+
-- The durable ledger row is the winner. Returning NULL from a BEFORE INSERT
|
|
122
|
+
-- trigger suppresses this source row without aborting the transaction, so
|
|
123
|
+
-- old writers and current writers both receive an empty RETURNING result and
|
|
124
|
+
-- can replay the winner. Raising 23505 here would abort the transaction
|
|
125
|
+
-- before the application could inspect the durable winner.
|
|
126
|
+
RETURN NULL;
|
|
127
|
+
END
|
|
128
|
+
$function$;
|
|
129
|
+
|
|
130
|
+
DO $lock_function$
|
|
131
|
+
DECLARE target_schema text := current_schema();
|
|
132
|
+
BEGIN
|
|
133
|
+
EXECUTE format($ddl$
|
|
134
|
+
CREATE OR REPLACE FUNCTION %I."lock_nested_agent_depth_configuration"()
|
|
135
|
+
RETURNS TABLE ("max_nested_agent_depth" integer, "policy_source" text)
|
|
136
|
+
LANGUAGE sql SECURITY DEFINER SET search_path = %I, pg_catalog
|
|
137
|
+
AS $body$
|
|
138
|
+
SELECT "max_nested_agent_depth", "policy_source"
|
|
139
|
+
FROM %I."nested_agent_depth_configuration"
|
|
140
|
+
WHERE "singleton" FOR SHARE
|
|
141
|
+
$body$
|
|
142
|
+
$ddl$, target_schema, target_schema, target_schema);
|
|
143
|
+
EXECUTE format('REVOKE ALL ON FUNCTION %I."lock_nested_agent_depth_configuration"() FROM PUBLIC', target_schema);
|
|
144
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
145
|
+
EXECUTE format('GRANT EXECUTE ON FUNCTION %I."lock_nested_agent_depth_configuration"() TO opengeni_app', target_schema);
|
|
146
|
+
EXECUTE format('GRANT SELECT, INSERT ON %I."session_spawn_denials" TO opengeni_app', target_schema);
|
|
147
|
+
END IF;
|
|
148
|
+
END $lock_function$;
|
|
149
|
+
|
|
150
|
+
CREATE OR REPLACE FUNCTION opengeni_private.session_depth_policy_defaults()
|
|
151
|
+
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
152
|
+
AS $function$
|
|
153
|
+
DECLARE
|
|
154
|
+
workspace_policy integer;
|
|
155
|
+
workspace_has_policy boolean;
|
|
156
|
+
workspace_account_id uuid;
|
|
157
|
+
control_account_id uuid;
|
|
158
|
+
deployment_policy integer;
|
|
159
|
+
deployment_source text;
|
|
160
|
+
parent_root uuid;
|
|
161
|
+
parent_depth integer;
|
|
162
|
+
parent_effective integer;
|
|
163
|
+
parent_source text;
|
|
164
|
+
parent_policy_session uuid;
|
|
165
|
+
expected_root uuid;
|
|
166
|
+
expected_depth bigint;
|
|
167
|
+
expected_effective integer;
|
|
168
|
+
expected_source text;
|
|
169
|
+
expected_policy_session uuid;
|
|
170
|
+
new_idempotency_key text;
|
|
171
|
+
ledger_winner_exists boolean;
|
|
172
|
+
BEGIN
|
|
173
|
+
-- Keep direct/older writers on the same control -> advisory -> workspace
|
|
174
|
+
-- ordering as pre-boundary application admission. The control prefix is
|
|
175
|
+
-- required for rolling compatibility: an old binary can already hold this
|
|
176
|
+
-- row before its insert reaches this trigger. The keyed advisory lock below
|
|
177
|
+
-- still fences the cross-outcome ledger before policy evaluation.
|
|
178
|
+
EXECUTE format('SELECT account_id FROM %I.workspace_inference_controls '
|
|
179
|
+
'WHERE workspace_id = $1 FOR SHARE', TG_TABLE_SCHEMA)
|
|
180
|
+
INTO control_account_id USING NEW.workspace_id;
|
|
181
|
+
IF control_account_id IS NULL THEN
|
|
182
|
+
RAISE EXCEPTION 'workspace has no mandatory inference-control row' USING ERRCODE = '23503';
|
|
183
|
+
END IF;
|
|
184
|
+
|
|
185
|
+
-- Take the same transaction advisory lock as current and old application
|
|
186
|
+
-- admission, then check the durable outcome before policy/control suffix
|
|
187
|
+
-- reads. A retry must replay its winner even if the retry's parent would now
|
|
188
|
+
-- exceed the effective depth policy. Waiting on the advisory lock also makes
|
|
189
|
+
-- an uncommitted winner visible before this check, instead of allowing a
|
|
190
|
+
-- mixed-version loser to evaluate policy first.
|
|
191
|
+
IF TG_TABLE_NAME = 'sessions' THEN
|
|
192
|
+
new_idempotency_key := NEW.create_idempotency_key;
|
|
193
|
+
IF new_idempotency_key IS NOT NULL THEN
|
|
194
|
+
PERFORM pg_advisory_xact_lock(
|
|
195
|
+
hashtext('session-create:' || NEW.workspace_id::text || ':' || new_idempotency_key)
|
|
196
|
+
);
|
|
197
|
+
EXECUTE format('SELECT EXISTS (
|
|
198
|
+
SELECT 1
|
|
199
|
+
FROM %I.session_create_idempotency_guard
|
|
200
|
+
WHERE workspace_id = $1 AND idempotency_key = $2
|
|
201
|
+
)', TG_TABLE_SCHEMA)
|
|
202
|
+
INTO ledger_winner_exists
|
|
203
|
+
USING NEW.workspace_id, new_idempotency_key;
|
|
204
|
+
IF ledger_winner_exists THEN
|
|
205
|
+
RETURN NULL;
|
|
206
|
+
END IF;
|
|
207
|
+
END IF;
|
|
208
|
+
END IF;
|
|
209
|
+
|
|
210
|
+
EXECUTE format('SELECT CASE WHEN jsonb_typeof(settings -> ''maxNestedAgentDepth'') = ''number''
|
|
211
|
+
AND settings ->> ''maxNestedAgentDepth'' ~ ''^(0|[1-9][0-9]{0,9})$''
|
|
212
|
+
AND (settings ->> ''maxNestedAgentDepth'')::numeric BETWEEN 0 AND 2147483647
|
|
213
|
+
THEN (settings ->> ''maxNestedAgentDepth'')::integer ELSE NULL END,
|
|
214
|
+
coalesce((jsonb_typeof(settings -> ''maxNestedAgentDepth'') = ''number''
|
|
215
|
+
AND settings ->> ''maxNestedAgentDepth'' ~ ''^(0|[1-9][0-9]{0,9})$''
|
|
216
|
+
AND (settings ->> ''maxNestedAgentDepth'')::numeric BETWEEN 0 AND 2147483647), false),
|
|
217
|
+
account_id
|
|
218
|
+
FROM %I.workspaces WHERE id = $1', TG_TABLE_SCHEMA)
|
|
219
|
+
INTO workspace_policy, workspace_has_policy, workspace_account_id USING NEW.workspace_id;
|
|
220
|
+
IF workspace_account_id IS NULL THEN
|
|
221
|
+
RAISE EXCEPTION 'workspace not found for session insert' USING ERRCODE = '23503';
|
|
222
|
+
END IF;
|
|
223
|
+
EXECUTE format('SELECT max_nested_agent_depth, policy_source FROM %I.lock_nested_agent_depth_configuration()', TG_TABLE_SCHEMA)
|
|
224
|
+
INTO deployment_policy, deployment_source;
|
|
225
|
+
IF deployment_policy IS NULL THEN
|
|
226
|
+
RAISE EXCEPTION 'nested-agent deployment policy is not configured' USING ERRCODE = '23514';
|
|
227
|
+
END IF;
|
|
228
|
+
|
|
229
|
+
IF NEW.parent_session_id IS NULL THEN
|
|
230
|
+
expected_root := NEW.id;
|
|
231
|
+
expected_depth := 0;
|
|
232
|
+
ELSE
|
|
233
|
+
EXECUTE format('SELECT root_session_id, nested_agent_depth, effective_max_nested_agent_depth,
|
|
234
|
+
nested_agent_depth_policy_source, nested_agent_depth_policy_session_id
|
|
235
|
+
FROM %I.sessions WHERE workspace_id = $1 AND id = $2 FOR SHARE', TG_TABLE_SCHEMA)
|
|
236
|
+
INTO parent_root, parent_depth, parent_effective, parent_source, parent_policy_session
|
|
237
|
+
USING NEW.workspace_id, NEW.parent_session_id;
|
|
238
|
+
IF parent_root IS NULL OR parent_depth IS NULL THEN
|
|
239
|
+
-- During 0111, a legacy parent can still have nullable lineage columns.
|
|
240
|
+
-- Walk that existing tree and derive the missing snapshot from the
|
|
241
|
+
-- nearest materialized ancestor, or from the legacy root and workspace
|
|
242
|
+
-- policy when the whole chain predates this boundary. This keeps old
|
|
243
|
+
-- trees writable while the bounded backfill catches up; it does not
|
|
244
|
+
-- bypass the effective-depth check below.
|
|
245
|
+
EXECUTE format($sql$
|
|
246
|
+
WITH RECURSIVE lineage AS (
|
|
247
|
+
SELECT
|
|
248
|
+
node.id,
|
|
249
|
+
node.parent_session_id,
|
|
250
|
+
node.root_session_id,
|
|
251
|
+
node.nested_agent_depth,
|
|
252
|
+
node.effective_max_nested_agent_depth,
|
|
253
|
+
node.nested_agent_depth_policy_source,
|
|
254
|
+
node.nested_agent_depth_policy_session_id,
|
|
255
|
+
0::bigint AS distance,
|
|
256
|
+
ARRAY[node.id]::uuid[] AS path
|
|
257
|
+
FROM %I.sessions AS node
|
|
258
|
+
WHERE node.workspace_id = $1 AND node.id = $2
|
|
259
|
+
UNION ALL
|
|
260
|
+
SELECT
|
|
261
|
+
ancestor.id,
|
|
262
|
+
ancestor.parent_session_id,
|
|
263
|
+
ancestor.root_session_id,
|
|
264
|
+
ancestor.nested_agent_depth,
|
|
265
|
+
ancestor.effective_max_nested_agent_depth,
|
|
266
|
+
ancestor.nested_agent_depth_policy_source,
|
|
267
|
+
ancestor.nested_agent_depth_policy_session_id,
|
|
268
|
+
lineage.distance + 1,
|
|
269
|
+
lineage.path || ancestor.id
|
|
270
|
+
FROM %I.sessions AS ancestor
|
|
271
|
+
JOIN lineage
|
|
272
|
+
ON ancestor.workspace_id = $1
|
|
273
|
+
AND ancestor.id = lineage.parent_session_id
|
|
274
|
+
WHERE NOT ancestor.id = ANY(lineage.path)
|
|
275
|
+
),
|
|
276
|
+
complete AS (
|
|
277
|
+
SELECT *
|
|
278
|
+
FROM lineage
|
|
279
|
+
WHERE root_session_id IS NOT NULL
|
|
280
|
+
AND nested_agent_depth IS NOT NULL
|
|
281
|
+
AND effective_max_nested_agent_depth IS NOT NULL
|
|
282
|
+
AND nested_agent_depth_policy_source IS NOT NULL
|
|
283
|
+
)
|
|
284
|
+
SELECT
|
|
285
|
+
COALESCE(
|
|
286
|
+
(SELECT root_session_id FROM lineage
|
|
287
|
+
WHERE root_session_id IS NOT NULL
|
|
288
|
+
ORDER BY distance LIMIT 1),
|
|
289
|
+
(SELECT id FROM lineage
|
|
290
|
+
WHERE parent_session_id IS NULL
|
|
291
|
+
ORDER BY distance DESC LIMIT 1)
|
|
292
|
+
),
|
|
293
|
+
COALESCE(
|
|
294
|
+
(SELECT nested_agent_depth::bigint + distance FROM lineage
|
|
295
|
+
WHERE nested_agent_depth IS NOT NULL
|
|
296
|
+
ORDER BY distance LIMIT 1),
|
|
297
|
+
(SELECT max(distance) FROM lineage)
|
|
298
|
+
),
|
|
299
|
+
(SELECT effective_max_nested_agent_depth FROM complete
|
|
300
|
+
ORDER BY distance LIMIT 1),
|
|
301
|
+
(SELECT nested_agent_depth_policy_source FROM complete
|
|
302
|
+
ORDER BY distance LIMIT 1),
|
|
303
|
+
(SELECT nested_agent_depth_policy_session_id FROM complete
|
|
304
|
+
ORDER BY distance LIMIT 1)
|
|
305
|
+
$sql$, TG_TABLE_SCHEMA, TG_TABLE_SCHEMA)
|
|
306
|
+
INTO parent_root, parent_depth, parent_effective, parent_source, parent_policy_session
|
|
307
|
+
USING NEW.workspace_id, NEW.parent_session_id;
|
|
308
|
+
END IF;
|
|
309
|
+
IF parent_root IS NULL OR parent_depth IS NULL THEN
|
|
310
|
+
RAISE EXCEPTION 'parent session not found for nested insert' USING ERRCODE = '23503';
|
|
311
|
+
END IF;
|
|
312
|
+
expected_root := parent_root;
|
|
313
|
+
expected_depth := parent_depth + 1;
|
|
314
|
+
END IF;
|
|
315
|
+
|
|
316
|
+
IF NEW.max_nested_agent_depth_override IS NOT NULL THEN
|
|
317
|
+
expected_effective := NEW.max_nested_agent_depth_override;
|
|
318
|
+
expected_source := 'session';
|
|
319
|
+
expected_policy_session := NEW.id;
|
|
320
|
+
ELSIF NEW.parent_session_id IS NOT NULL AND parent_source = 'session' THEN
|
|
321
|
+
expected_effective := parent_effective;
|
|
322
|
+
expected_source := 'session';
|
|
323
|
+
expected_policy_session := parent_policy_session;
|
|
324
|
+
ELSIF workspace_has_policy THEN
|
|
325
|
+
expected_effective := workspace_policy;
|
|
326
|
+
expected_source := 'workspace';
|
|
327
|
+
expected_policy_session := NULL;
|
|
328
|
+
ELSE
|
|
329
|
+
expected_effective := deployment_policy;
|
|
330
|
+
expected_source := deployment_source;
|
|
331
|
+
expected_policy_session := NULL;
|
|
332
|
+
END IF;
|
|
333
|
+
|
|
334
|
+
IF expected_depth > expected_effective THEN
|
|
335
|
+
RAISE EXCEPTION 'nested agent depth exceeds effective maximum' USING ERRCODE = '23514';
|
|
336
|
+
END IF;
|
|
337
|
+
NEW.root_session_id := expected_root;
|
|
338
|
+
NEW.nested_agent_depth := expected_depth;
|
|
339
|
+
NEW.effective_max_nested_agent_depth := expected_effective;
|
|
340
|
+
NEW.nested_agent_depth_policy_source := expected_source;
|
|
341
|
+
NEW.nested_agent_depth_policy_session_id := expected_policy_session;
|
|
342
|
+
RETURN NEW;
|
|
343
|
+
END
|
|
344
|
+
$function$;
|
|
345
|
+
|
|
346
|
+
CREATE OR REPLACE FUNCTION opengeni_private.session_spawn_denials_append_only()
|
|
347
|
+
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
348
|
+
AS $function$
|
|
349
|
+
DECLARE
|
|
350
|
+
workspace_exists boolean;
|
|
351
|
+
BEGIN
|
|
352
|
+
IF TG_OP = 'DELETE' THEN
|
|
353
|
+
-- Workspace/account deletion is intentionally allowed to cascade through
|
|
354
|
+
-- evidence rows. A direct row delete while its workspace still exists is
|
|
355
|
+
-- never allowed, preserving append-only audit evidence for live tenants.
|
|
356
|
+
IF pg_trigger_depth() > 1 THEN
|
|
357
|
+
RETURN OLD;
|
|
358
|
+
END IF;
|
|
359
|
+
-- The FK cascade removes the parent workspace before its child rows are
|
|
360
|
+
-- deleted. Resolve the target schema dynamically because embedded hosts
|
|
361
|
+
-- run the same migration chain outside public.
|
|
362
|
+
EXECUTE format('SELECT EXISTS (SELECT 1 FROM %I.workspaces WHERE id = $1)', TG_TABLE_SCHEMA)
|
|
363
|
+
INTO workspace_exists
|
|
364
|
+
USING OLD.workspace_id;
|
|
365
|
+
IF NOT workspace_exists THEN
|
|
366
|
+
RETURN OLD;
|
|
367
|
+
END IF;
|
|
368
|
+
END IF;
|
|
369
|
+
RAISE EXCEPTION 'session spawn denial evidence is append-only' USING ERRCODE = '55000';
|
|
370
|
+
END
|
|
371
|
+
$function$;
|
|
372
|
+
|
|
373
|
+
CREATE OR REPLACE FUNCTION opengeni_private.lock_nested_agent_workspace_policy_update()
|
|
374
|
+
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path = pg_catalog
|
|
375
|
+
AS $function$
|
|
376
|
+
DECLARE
|
|
377
|
+
control_account_id uuid;
|
|
378
|
+
BEGIN
|
|
379
|
+
IF OLD.settings -> 'maxNestedAgentDepth'
|
|
380
|
+
IS NOT DISTINCT FROM NEW.settings -> 'maxNestedAgentDepth' THEN
|
|
381
|
+
RETURN NEW;
|
|
382
|
+
END IF;
|
|
383
|
+
EXECUTE format('SELECT account_id FROM %I.workspace_inference_controls '
|
|
384
|
+
'WHERE workspace_id = $1 FOR UPDATE', TG_TABLE_SCHEMA)
|
|
385
|
+
INTO control_account_id USING NEW.id;
|
|
386
|
+
IF control_account_id IS NULL THEN
|
|
387
|
+
RAISE EXCEPTION 'workspace has no mandatory inference-control row' USING ERRCODE = '23503';
|
|
388
|
+
END IF;
|
|
389
|
+
RETURN NEW;
|
|
390
|
+
END
|
|
391
|
+
$function$;
|
|
392
|
+
|
|
393
|
+
-- Fence the source tables before installing any source-row guard. A SHARE
|
|
394
|
+
-- lock waits for every pre-boundary writer already in flight, and prevents
|
|
395
|
+
-- new INSERT/UPDATE/DELETE writers from committing while the trigger boundary
|
|
396
|
+
-- and initial ledger reconciliation are installed. Without this fence an old
|
|
397
|
+
-- writer can commit after the trigger install decision but before its source
|
|
398
|
+
-- row is backfilled into the ledger.
|
|
399
|
+
LOCK TABLE "workspaces", "sessions", "session_spawn_denials" IN SHARE MODE;
|
|
400
|
+
|
|
401
|
+
DO $triggers$
|
|
402
|
+
DECLARE target_schema text := current_schema();
|
|
403
|
+
BEGIN
|
|
404
|
+
EXECUTE format('DROP TRIGGER IF EXISTS session_depth_policy_defaults ON %I.sessions', target_schema);
|
|
405
|
+
EXECUTE format('CREATE TRIGGER session_depth_policy_defaults BEFORE INSERT ON %I.sessions FOR EACH ROW EXECUTE FUNCTION opengeni_private.session_depth_policy_defaults()', target_schema);
|
|
406
|
+
EXECUTE format('DROP TRIGGER IF EXISTS session_create_idempotency_guard ON %I.sessions', target_schema);
|
|
407
|
+
EXECUTE format('DROP TRIGGER IF EXISTS session_idempotency_guard ON %I.sessions', target_schema);
|
|
408
|
+
EXECUTE format('CREATE TRIGGER session_idempotency_guard BEFORE INSERT ON %I.sessions FOR EACH ROW WHEN (NEW.create_idempotency_key IS NOT NULL) EXECUTE FUNCTION opengeni_private.session_create_idempotency_guard_insert()', target_schema);
|
|
409
|
+
EXECUTE format('DROP TRIGGER IF EXISTS session_spawn_denials_append_only ON %I.session_spawn_denials', target_schema);
|
|
410
|
+
EXECUTE format('CREATE TRIGGER session_spawn_denials_append_only BEFORE UPDATE OR DELETE ON %I.session_spawn_denials FOR EACH ROW EXECUTE FUNCTION opengeni_private.session_spawn_denials_append_only()', target_schema);
|
|
411
|
+
EXECUTE format('DROP TRIGGER IF EXISTS session_spawn_denials_idempotency_guard ON %I.session_spawn_denials', target_schema);
|
|
412
|
+
EXECUTE format('CREATE TRIGGER session_spawn_denials_idempotency_guard BEFORE INSERT ON %I.session_spawn_denials FOR EACH ROW WHEN (NEW.idempotency_key IS NOT NULL) EXECUTE FUNCTION opengeni_private.session_create_idempotency_guard_insert()', target_schema);
|
|
413
|
+
EXECUTE format('DROP TRIGGER IF EXISTS lock_nested_agent_workspace_policy_update ON %I.workspaces', target_schema);
|
|
414
|
+
EXECUTE format('CREATE TRIGGER lock_nested_agent_workspace_policy_update BEFORE UPDATE OF settings ON %I.workspaces FOR EACH ROW EXECUTE FUNCTION opengeni_private.lock_nested_agent_workspace_policy_update()', target_schema);
|
|
415
|
+
END $triggers$;
|
|
416
|
+
|
|
417
|
+
-- Preserve source rows created before this boundary. The shared source-table
|
|
418
|
+
-- lock remains held, so this reconciliation is fenced against both old and
|
|
419
|
+
-- new writers and cannot miss a source row between the scan and ledger insert.
|
|
420
|
+
INSERT INTO "session_create_idempotency_guard" (
|
|
421
|
+
"workspace_id", "idempotency_key", "outcome", "session_id"
|
|
422
|
+
)
|
|
423
|
+
SELECT "workspace_id", "create_idempotency_key", 'session', "id"
|
|
424
|
+
FROM "sessions"
|
|
425
|
+
WHERE "create_idempotency_key" IS NOT NULL
|
|
426
|
+
ON CONFLICT ("workspace_id", "idempotency_key") DO NOTHING;
|
|
427
|
+
|
|
428
|
+
-- Keep a final reconciliation for both outcomes while the source-table locks
|
|
429
|
+
-- are still held. The checks make a migration failure explicit rather than
|
|
430
|
+
-- publishing a source row with no ledger entry.
|
|
431
|
+
INSERT INTO "session_create_idempotency_guard" (
|
|
432
|
+
"workspace_id", "idempotency_key", "outcome", "session_id"
|
|
433
|
+
)
|
|
434
|
+
SELECT "workspace_id", "create_idempotency_key", 'session', "id"
|
|
435
|
+
FROM "sessions"
|
|
436
|
+
WHERE "create_idempotency_key" IS NOT NULL
|
|
437
|
+
ON CONFLICT ("workspace_id", "idempotency_key") DO NOTHING;
|
|
438
|
+
INSERT INTO "session_create_idempotency_guard" (
|
|
439
|
+
"workspace_id", "idempotency_key", "outcome", "denial_id"
|
|
440
|
+
)
|
|
441
|
+
SELECT "workspace_id", "idempotency_key", 'denial', "id"
|
|
442
|
+
FROM "session_spawn_denials"
|
|
443
|
+
WHERE "idempotency_key" IS NOT NULL
|
|
444
|
+
ON CONFLICT ("workspace_id", "idempotency_key") DO NOTHING;
|
|
445
|
+
|
|
446
|
+
DO $reconcile$
|
|
447
|
+
DECLARE
|
|
448
|
+
missing_source_rows integer;
|
|
449
|
+
BEGIN
|
|
450
|
+
SELECT count(*)::integer
|
|
451
|
+
INTO missing_source_rows
|
|
452
|
+
FROM (
|
|
453
|
+
SELECT "workspace_id", "create_idempotency_key" AS "idempotency_key"
|
|
454
|
+
FROM "sessions"
|
|
455
|
+
WHERE "create_idempotency_key" IS NOT NULL
|
|
456
|
+
UNION ALL
|
|
457
|
+
SELECT "workspace_id", "idempotency_key"
|
|
458
|
+
FROM "session_spawn_denials"
|
|
459
|
+
WHERE "idempotency_key" IS NOT NULL
|
|
460
|
+
) source_rows
|
|
461
|
+
WHERE NOT EXISTS (
|
|
462
|
+
SELECT 1
|
|
463
|
+
FROM "session_create_idempotency_guard" guard
|
|
464
|
+
WHERE guard."workspace_id" = source_rows."workspace_id"
|
|
465
|
+
AND guard."idempotency_key" = source_rows."idempotency_key"
|
|
466
|
+
);
|
|
467
|
+
IF missing_source_rows <> 0 THEN
|
|
468
|
+
RAISE EXCEPTION 'session create idempotency ledger reconciliation missed % source rows', missing_source_rows
|
|
469
|
+
USING ERRCODE = '23514';
|
|
470
|
+
END IF;
|
|
471
|
+
END $reconcile$;
|
|
472
|
+
|
|
473
|
+
ALTER TABLE "session_spawn_denials" ENABLE ROW LEVEL SECURITY;
|
|
474
|
+
ALTER TABLE "session_spawn_denials" FORCE ROW LEVEL SECURITY;
|
|
475
|
+
CREATE POLICY workspace_isolation ON "session_spawn_denials"
|
|
476
|
+
USING (opengeni_private.workspace_rls_visible(account_id, workspace_id))
|
|
477
|
+
WITH CHECK (opengeni_private.workspace_rls_visible(account_id, workspace_id));
|
|
478
|
+
|
|
479
|
+
RESET statement_timeout;
|
|
480
|
+
RESET lock_timeout;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- opengeni:batched-backfill batch-size=1000 lock-timeout=5s statement-timeout=30s
|
|
3
|
+
WITH candidates AS (
|
|
4
|
+
SELECT
|
|
5
|
+
session.ctid AS target_ctid,
|
|
6
|
+
CASE WHEN session."parent_session_id" IS NULL THEN session."id"
|
|
7
|
+
ELSE parent."root_session_id" END AS root_session_id,
|
|
8
|
+
CASE WHEN session."parent_session_id" IS NULL THEN 0
|
|
9
|
+
ELSE parent."nested_agent_depth"::bigint + 1 END AS nested_agent_depth,
|
|
10
|
+
CASE
|
|
11
|
+
WHEN jsonb_typeof(workspace."settings" -> 'maxNestedAgentDepth') = 'number'
|
|
12
|
+
AND workspace."settings" ->> 'maxNestedAgentDepth' ~ '^(0|[1-9][0-9]{0,9})$'
|
|
13
|
+
AND (workspace."settings" ->> 'maxNestedAgentDepth')::numeric BETWEEN 0 AND 2147483647
|
|
14
|
+
THEN (workspace."settings" ->> 'maxNestedAgentDepth')::integer
|
|
15
|
+
ELSE configuration."max_nested_agent_depth"
|
|
16
|
+
END AS effective_max_nested_agent_depth,
|
|
17
|
+
CASE
|
|
18
|
+
WHEN jsonb_typeof(workspace."settings" -> 'maxNestedAgentDepth') = 'number'
|
|
19
|
+
AND workspace."settings" ->> 'maxNestedAgentDepth' ~ '^(0|[1-9][0-9]{0,9})$'
|
|
20
|
+
AND (workspace."settings" ->> 'maxNestedAgentDepth')::numeric BETWEEN 0 AND 2147483647
|
|
21
|
+
THEN 'workspace'
|
|
22
|
+
ELSE configuration."policy_source"
|
|
23
|
+
END AS nested_agent_depth_policy_source
|
|
24
|
+
FROM "sessions" session
|
|
25
|
+
LEFT JOIN "sessions" parent
|
|
26
|
+
ON parent."workspace_id" = session."workspace_id"
|
|
27
|
+
AND parent."id" = session."parent_session_id"
|
|
28
|
+
JOIN "workspaces" workspace ON workspace."id" = session."workspace_id"
|
|
29
|
+
JOIN "nested_agent_depth_configuration" configuration ON configuration."singleton"
|
|
30
|
+
WHERE (session."root_session_id" IS NULL
|
|
31
|
+
OR session."nested_agent_depth" IS NULL
|
|
32
|
+
OR session."effective_max_nested_agent_depth" IS NULL
|
|
33
|
+
OR session."nested_agent_depth_policy_source" IS NULL)
|
|
34
|
+
AND (session."parent_session_id" IS NULL
|
|
35
|
+
OR (parent."root_session_id" IS NOT NULL AND parent."nested_agent_depth" IS NOT NULL))
|
|
36
|
+
ORDER BY session."created_at", session."id"
|
|
37
|
+
LIMIT 1000
|
|
38
|
+
FOR UPDATE OF session
|
|
39
|
+
)
|
|
40
|
+
UPDATE "sessions" session
|
|
41
|
+
SET "root_session_id" = candidates.root_session_id,
|
|
42
|
+
"nested_agent_depth" = candidates.nested_agent_depth,
|
|
43
|
+
"max_nested_agent_depth_override" = NULL,
|
|
44
|
+
"effective_max_nested_agent_depth" = candidates.effective_max_nested_agent_depth,
|
|
45
|
+
"nested_agent_depth_policy_source" = candidates.nested_agent_depth_policy_source,
|
|
46
|
+
"nested_agent_depth_policy_session_id" = NULL
|
|
47
|
+
FROM candidates
|
|
48
|
+
WHERE session.ctid = candidates.target_ctid
|
|
49
|
+
RETURNING session."id";
|