@opengeni/db 0.31.1 → 0.36.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/browser-auth.d.ts +209 -1
- package/dist/browser-downloads.d.ts +51 -0
- package/dist/browser-identities.d.ts +11 -2
- package/dist/browser-sessions.d.ts +83 -1
- package/dist/browser-state-artifacts.d.ts +75 -1
- package/dist/canonical-human-identities.d.ts +48 -0
- package/dist/canonical-human-identities.js +28 -0
- package/dist/canonical-human-identities.js.map +1 -0
- package/dist/capability-integrations.d.ts +8 -6
- package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
- package/dist/chunk-5KTIEDX7.js.map +1 -0
- package/dist/chunk-FPVAD4CO.js +117 -0
- package/dist/chunk-FPVAD4CO.js.map +1 -0
- package/dist/chunk-GYO7ED2Q.js +259 -0
- package/dist/chunk-GYO7ED2Q.js.map +1 -0
- package/dist/{chunk-F5FT7BEZ.js → chunk-HSOWSYOX.js} +2 -2
- package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
- package/dist/chunk-VKLB2ZGI.js.map +1 -0
- package/dist/{chunk-NBE6CHRI.js → chunk-VTPRIXKG.js} +49 -9
- package/dist/chunk-VTPRIXKG.js.map +1 -0
- package/dist/{chunk-ISI2TIUG.js → chunk-Z7VYMNPZ.js} +76 -4
- package/dist/chunk-Z7VYMNPZ.js.map +1 -0
- package/dist/database.d.ts +5 -0
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +467 -29
- package/dist/index.js +23247 -16232
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +23 -7
- package/dist/integration-bindings.d.ts +24 -24
- package/dist/integration-facets.d.ts +99 -0
- package/dist/interaction-revisions.d.ts +11 -0
- package/dist/interaction-schema.d.ts +698 -34
- package/dist/persistence-errors.d.ts +8 -0
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +8 -7
- package/dist/schema.d.ts +9464 -5412
- package/dist/schema.js +51 -9
- package/dist/session-control.d.ts +29 -3
- package/dist/session-tenancy.d.ts +44 -0
- package/dist/session-tenancy.js +16 -0
- package/dist/session-tenancy.js.map +1 -0
- package/dist/slack-task-policy-schema.d.ts +847 -0
- package/dist/slack-task-policy.d.ts +65 -0
- package/dist/task-notes-schema.d.ts +924 -0
- package/dist/task-notes.d.ts +85 -0
- package/dist/video-generation.d.ts +9 -0
- package/dist/video-generation.js +5 -3
- package/dist/xai-subscription.d.ts +326 -0
- package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
- package/drizzle/0213_browser_interaction_authority.sql +67 -0
- package/drizzle/0214_browser_download_saves.sql +34 -0
- package/drizzle/0215_browser_controller_host.sql +385 -0
- package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
- package/drizzle/0217_external_browser_auth_operations.sql +22 -0
- package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
- package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
- package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
- package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
- package/drizzle/0220_session_channels.sql +54 -0
- package/drizzle/0221_sessions_channel_index.sql +5 -0
- package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
- package/drizzle/0222_sessions_channel_fk.sql +89 -0
- package/drizzle/0223_pending_tool_event_output.sql +55 -0
- package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
- package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
- package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
- package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
- package/drizzle/0227_slack_native_actions.sql +145 -0
- package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
- package/drizzle/0228_slack_task_policy.sql +400 -0
- package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
- package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
- package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
- package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
- package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
- package/drizzle/0234_xai_subscription_authority.sql +1139 -0
- package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
- package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
- package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
- package/drizzle/0237_interaction_transition_reaper.sql +263 -0
- package/drizzle/0238_goal_persistence_policy.sql +471 -0
- package/drizzle/0238_recover_unclaimed_session_turns.sql +307 -0
- package/drizzle/0238_supergrok_realtime_model.sql +29 -0
- package/drizzle/0239_supergrok_video_funding.sql +50 -0
- package/drizzle/0239_task_tree_notes.sql +990 -0
- package/package.json +13 -5
- package/src/browser-auth.ts +2600 -123
- package/src/browser-downloads.ts +517 -0
- package/src/browser-identities.ts +220 -3
- package/src/browser-sessions.ts +334 -45
- package/src/browser-state-artifacts.ts +402 -12
- package/src/canonical-human-identities.ts +317 -0
- package/src/capability-integrations.ts +121 -213
- package/src/database.ts +41 -1
- package/src/index.ts +4625 -411
- package/src/insights.ts +109 -28
- package/src/integration-bindings.ts +101 -101
- package/src/{integration-features.ts → integration-facets.ts} +187 -184
- package/src/interaction-revisions.ts +33 -3
- package/src/interaction-schema.ts +311 -8
- package/src/pack-components.ts +43 -43
- package/src/pack-installations.ts +0 -43
- package/src/persistence-errors.ts +60 -1
- package/src/plugin-packages.ts +9 -9
- package/src/provision-roles.ts +232 -0
- package/src/runtime-posture.ts +227 -14
- package/src/schema.ts +1188 -75
- package/src/session-control.ts +122 -37
- package/src/session-queue-commands.ts +78 -1
- package/src/session-tenancy.ts +188 -0
- package/src/session-tool-call-settlement.ts +9 -3
- package/src/slack-task-policy-schema.ts +130 -0
- package/src/slack-task-policy.ts +341 -0
- package/src/task-notes-schema.ts +135 -0
- package/src/task-notes.ts +178 -0
- package/src/video-generation.ts +90 -12
- package/src/workspace-artifacts.ts +1 -1
- package/src/xai-subscription.ts +1887 -0
- package/dist/chunk-ISI2TIUG.js.map +0 -1
- package/dist/chunk-MWDVXQOL.js.map +0 -1
- package/dist/chunk-NBE6CHRI.js.map +0 -1
- package/dist/chunk-YHZXQ7HP.js.map +0 -1
- package/dist/integration-features.d.ts +0 -99
- /package/dist/{chunk-F5FT7BEZ.js.map → chunk-HSOWSYOX.js.map} +0 -0
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
-- deployment-mode: maintenance
|
|
2
|
+
-- Crash-safe SiteAuth maintenance claims. A claim owns one hidden session id
|
|
3
|
+
-- and one operation id; retries repair the same session rather than dispatching
|
|
4
|
+
-- duplicate health or repair agents.
|
|
5
|
+
SET LOCAL lock_timeout = '5s';
|
|
6
|
+
SET LOCAL statement_timeout = '30s';
|
|
7
|
+
|
|
8
|
+
ALTER TABLE "site_auth_connections"
|
|
9
|
+
ADD COLUMN "maintenance_operation_id" uuid,
|
|
10
|
+
ADD COLUMN "maintenance_action" text,
|
|
11
|
+
ADD COLUMN "maintenance_due_at" timestamptz,
|
|
12
|
+
ADD COLUMN "maintenance_claimed_at" timestamptz,
|
|
13
|
+
ADD COLUMN "maintenance_session_id" uuid,
|
|
14
|
+
ADD COLUMN "maintenance_started_at" timestamptz;
|
|
15
|
+
|
|
16
|
+
ALTER TABLE "site_auth_connections"
|
|
17
|
+
ADD CONSTRAINT "site_auth_connections_maintenance_check" CHECK (
|
|
18
|
+
(
|
|
19
|
+
"maintenance_operation_id" IS NULL
|
|
20
|
+
AND "maintenance_action" IS NULL
|
|
21
|
+
AND "maintenance_due_at" IS NULL
|
|
22
|
+
AND "maintenance_claimed_at" IS NULL
|
|
23
|
+
AND "maintenance_session_id" IS NULL
|
|
24
|
+
AND "maintenance_started_at" IS NULL
|
|
25
|
+
) OR (
|
|
26
|
+
"status" = 'active'
|
|
27
|
+
AND "health_policy"->>'mode' = 'maintained'
|
|
28
|
+
AND "maintenance_operation_id" IS NOT NULL
|
|
29
|
+
AND "maintenance_action" IN ('health_check', 'repair')
|
|
30
|
+
AND "maintenance_due_at" IS NOT NULL
|
|
31
|
+
AND "maintenance_claimed_at" IS NOT NULL
|
|
32
|
+
AND "maintenance_session_id" IS NOT NULL
|
|
33
|
+
AND (
|
|
34
|
+
"maintenance_started_at" IS NULL
|
|
35
|
+
OR "maintenance_started_at" >= "maintenance_due_at"
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
ALTER TABLE "auth_runs"
|
|
41
|
+
ADD COLUMN "maintenance_operation_id" uuid,
|
|
42
|
+
ADD CONSTRAINT "auth_runs_maintenance_check" CHECK (
|
|
43
|
+
"maintenance_operation_id" IS NULL
|
|
44
|
+
OR "purpose" IN ('health_check', 'repair')
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
DROP INDEX "site_auth_connections_health_maintenance_idx";
|
|
48
|
+
CREATE INDEX "site_auth_connections_health_maintenance_idx"
|
|
49
|
+
ON "site_auth_connections" (
|
|
50
|
+
"next_check_at", "maintenance_claimed_at", "id"
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE UNIQUE INDEX "auth_runs_workspace_maintenance_operation_uq"
|
|
54
|
+
ON "auth_runs" ("workspace_id", "maintenance_operation_id")
|
|
55
|
+
WHERE "maintenance_operation_id" IS NOT NULL;
|
|
56
|
+
|
|
57
|
+
-- Sole cross-workspace claim boundary. An undispatched stale claim reuses its
|
|
58
|
+
-- exact ids. A completed/failed maintenance session without a live AuthRun is
|
|
59
|
+
-- replaced only after the claim timeout. Active sessions and interventions are
|
|
60
|
+
-- never stolen.
|
|
61
|
+
DO $migration$
|
|
62
|
+
DECLARE target_schema text := current_schema();
|
|
63
|
+
BEGIN
|
|
64
|
+
EXECUTE format($create$
|
|
65
|
+
CREATE OR REPLACE FUNCTION opengeni_private.claim_site_auth_maintenance(
|
|
66
|
+
p_claim_timeout_ms bigint,
|
|
67
|
+
p_limit integer
|
|
68
|
+
)
|
|
69
|
+
RETURNS TABLE (
|
|
70
|
+
operation_id uuid,
|
|
71
|
+
session_id uuid,
|
|
72
|
+
account_id uuid,
|
|
73
|
+
workspace_id uuid,
|
|
74
|
+
site_auth_connection_id uuid,
|
|
75
|
+
connection_version bigint,
|
|
76
|
+
action text,
|
|
77
|
+
due_at timestamptz,
|
|
78
|
+
claimed_at timestamptz,
|
|
79
|
+
name text,
|
|
80
|
+
account_label text,
|
|
81
|
+
login_url text,
|
|
82
|
+
verification_url_prefixes jsonb,
|
|
83
|
+
preferred_identity_id uuid,
|
|
84
|
+
preferred_placement jsonb,
|
|
85
|
+
preferred_network_route_id uuid,
|
|
86
|
+
health_policy jsonb,
|
|
87
|
+
verification_state text
|
|
88
|
+
)
|
|
89
|
+
LANGUAGE sql
|
|
90
|
+
SECURITY DEFINER
|
|
91
|
+
SET search_path = pg_catalog
|
|
92
|
+
AS $function$
|
|
93
|
+
WITH candidates AS MATERIALIZED (
|
|
94
|
+
SELECT
|
|
95
|
+
C.id,
|
|
96
|
+
C.maintenance_operation_id,
|
|
97
|
+
C.maintenance_session_id,
|
|
98
|
+
C.maintenance_started_at,
|
|
99
|
+
S.status AS session_status
|
|
100
|
+
FROM %1$I.site_auth_connections C
|
|
101
|
+
LEFT JOIN %1$I.sessions S
|
|
102
|
+
ON S.workspace_id = C.workspace_id
|
|
103
|
+
AND S.id = C.maintenance_session_id
|
|
104
|
+
WHERE C.status = 'active'
|
|
105
|
+
AND C.health_policy->>'mode' = 'maintained'
|
|
106
|
+
AND C.next_check_at <= clock_timestamp()
|
|
107
|
+
AND (
|
|
108
|
+
C.maintenance_operation_id IS NULL
|
|
109
|
+
OR (
|
|
110
|
+
C.maintenance_started_at IS NULL
|
|
111
|
+
AND C.maintenance_claimed_at <= clock_timestamp() - (
|
|
112
|
+
greatest(coalesce(p_claim_timeout_ms, 0), 0)::double precision * interval '1 millisecond'
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
OR (
|
|
116
|
+
C.maintenance_started_at IS NOT NULL
|
|
117
|
+
AND S.status = 'queued'
|
|
118
|
+
AND NOT EXISTS (
|
|
119
|
+
SELECT 1
|
|
120
|
+
FROM %1$I.auth_runs R
|
|
121
|
+
WHERE R.workspace_id = C.workspace_id
|
|
122
|
+
AND R.maintenance_operation_id = C.maintenance_operation_id
|
|
123
|
+
AND R.settled_at IS NULL
|
|
124
|
+
)
|
|
125
|
+
AND C.maintenance_claimed_at <= clock_timestamp() - (
|
|
126
|
+
greatest(coalesce(p_claim_timeout_ms, 0), 0)::double precision * interval '1 millisecond'
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
OR (
|
|
130
|
+
C.maintenance_started_at IS NOT NULL
|
|
131
|
+
AND (S.id IS NULL OR S.status IN ('idle', 'failed', 'cancelled'))
|
|
132
|
+
AND NOT EXISTS (
|
|
133
|
+
SELECT 1
|
|
134
|
+
FROM %1$I.auth_runs R
|
|
135
|
+
WHERE R.workspace_id = C.workspace_id
|
|
136
|
+
AND R.maintenance_operation_id = C.maintenance_operation_id
|
|
137
|
+
AND R.settled_at IS NULL
|
|
138
|
+
)
|
|
139
|
+
AND C.maintenance_claimed_at <= clock_timestamp() - (
|
|
140
|
+
greatest(coalesce(p_claim_timeout_ms, 0), 0)::double precision * interval '1 millisecond'
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
ORDER BY C.next_check_at, C.id
|
|
145
|
+
LIMIT least(greatest(coalesce(p_limit, 0), 0), 1000)
|
|
146
|
+
FOR UPDATE OF C SKIP LOCKED
|
|
147
|
+
), prepared AS (
|
|
148
|
+
SELECT
|
|
149
|
+
X.id,
|
|
150
|
+
CASE
|
|
151
|
+
WHEN X.maintenance_operation_id IS NOT NULL
|
|
152
|
+
AND (
|
|
153
|
+
X.maintenance_started_at IS NULL
|
|
154
|
+
OR X.session_status = 'queued'
|
|
155
|
+
)
|
|
156
|
+
THEN X.maintenance_operation_id
|
|
157
|
+
ELSE gen_random_uuid()
|
|
158
|
+
END AS operation_id,
|
|
159
|
+
CASE
|
|
160
|
+
WHEN X.maintenance_session_id IS NOT NULL
|
|
161
|
+
AND (
|
|
162
|
+
X.maintenance_started_at IS NULL
|
|
163
|
+
OR X.session_status = 'queued'
|
|
164
|
+
)
|
|
165
|
+
THEN X.maintenance_session_id
|
|
166
|
+
ELSE gen_random_uuid()
|
|
167
|
+
END AS session_id
|
|
168
|
+
FROM candidates X
|
|
169
|
+
), claimed AS (
|
|
170
|
+
UPDATE %1$I.site_auth_connections C
|
|
171
|
+
SET
|
|
172
|
+
maintenance_operation_id = P.operation_id,
|
|
173
|
+
maintenance_action = CASE
|
|
174
|
+
WHEN C.verification_state IN ('needs_repair', 'failed')
|
|
175
|
+
AND (C.health_policy->>'automaticRepair')::boolean
|
|
176
|
+
THEN 'repair'
|
|
177
|
+
ELSE 'health_check'
|
|
178
|
+
END,
|
|
179
|
+
maintenance_due_at = C.next_check_at,
|
|
180
|
+
maintenance_claimed_at = clock_timestamp(),
|
|
181
|
+
maintenance_session_id = P.session_id,
|
|
182
|
+
maintenance_started_at = NULL,
|
|
183
|
+
version = C.version + 1,
|
|
184
|
+
updated_by_subject_id = 'site-auth-maintenance',
|
|
185
|
+
updated_at = clock_timestamp()
|
|
186
|
+
FROM prepared P
|
|
187
|
+
WHERE C.id = P.id
|
|
188
|
+
RETURNING C.*
|
|
189
|
+
), revisions AS (
|
|
190
|
+
INSERT INTO %1$I.workspace_interaction_revisions (
|
|
191
|
+
workspace_id, account_id, revision, updated_at
|
|
192
|
+
)
|
|
193
|
+
SELECT DISTINCT C.workspace_id, C.account_id, 1, clock_timestamp()
|
|
194
|
+
FROM claimed C
|
|
195
|
+
ON CONFLICT (workspace_id) DO UPDATE
|
|
196
|
+
SET revision = %1$I.workspace_interaction_revisions.revision + 1,
|
|
197
|
+
updated_at = excluded.updated_at
|
|
198
|
+
RETURNING workspace_id
|
|
199
|
+
)
|
|
200
|
+
SELECT
|
|
201
|
+
C.maintenance_operation_id,
|
|
202
|
+
C.maintenance_session_id,
|
|
203
|
+
C.account_id,
|
|
204
|
+
C.workspace_id,
|
|
205
|
+
C.id,
|
|
206
|
+
C.version,
|
|
207
|
+
C.maintenance_action,
|
|
208
|
+
C.maintenance_due_at,
|
|
209
|
+
C.maintenance_claimed_at,
|
|
210
|
+
C.name,
|
|
211
|
+
C.account_label,
|
|
212
|
+
C.login_url,
|
|
213
|
+
C.verification_url_prefixes,
|
|
214
|
+
C.preferred_identity_id,
|
|
215
|
+
C.preferred_placement,
|
|
216
|
+
C.preferred_network_route_id,
|
|
217
|
+
C.health_policy,
|
|
218
|
+
C.verification_state
|
|
219
|
+
FROM claimed C;
|
|
220
|
+
$function$;
|
|
221
|
+
$create$, target_schema);
|
|
222
|
+
END $migration$;
|
|
223
|
+
|
|
224
|
+
REVOKE ALL ON FUNCTION opengeni_private.claim_site_auth_maintenance(bigint, integer)
|
|
225
|
+
FROM PUBLIC;
|
|
226
|
+
|
|
227
|
+
DO $site_auth_maintenance_role_grants$
|
|
228
|
+
BEGIN
|
|
229
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
230
|
+
GRANT EXECUTE ON FUNCTION opengeni_private.claim_site_auth_maintenance(bigint, integer)
|
|
231
|
+
TO opengeni_app;
|
|
232
|
+
END IF;
|
|
233
|
+
END
|
|
234
|
+
$site_auth_maintenance_role_grants$;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
SET lock_timeout = '5s';
|
|
3
|
+
SET statement_timeout = '30s';
|
|
4
|
+
|
|
5
|
+
-- Configuration revisions and delivery receipts remain immutable while their
|
|
6
|
+
-- workspace exists. Parent account/workspace deletion must still be able to
|
|
7
|
+
-- cascade through that history, including on embedded non-public schemas.
|
|
8
|
+
CREATE OR REPLACE FUNCTION opengeni_private.reject_memory_slack_immutable_mutation()
|
|
9
|
+
RETURNS trigger
|
|
10
|
+
LANGUAGE plpgsql
|
|
11
|
+
SECURITY DEFINER
|
|
12
|
+
SET search_path = pg_catalog
|
|
13
|
+
AS $function$
|
|
14
|
+
DECLARE
|
|
15
|
+
workspace_exists boolean;
|
|
16
|
+
BEGIN
|
|
17
|
+
IF TG_OP = 'DELETE' THEN
|
|
18
|
+
IF pg_trigger_depth() > 1 THEN
|
|
19
|
+
RETURN OLD;
|
|
20
|
+
END IF;
|
|
21
|
+
|
|
22
|
+
EXECUTE format(
|
|
23
|
+
'SELECT EXISTS (SELECT 1 FROM %I.workspaces WHERE id = $1)',
|
|
24
|
+
TG_TABLE_SCHEMA
|
|
25
|
+
)
|
|
26
|
+
INTO workspace_exists
|
|
27
|
+
USING OLD.workspace_id;
|
|
28
|
+
IF NOT workspace_exists THEN
|
|
29
|
+
RETURN OLD;
|
|
30
|
+
END IF;
|
|
31
|
+
END IF;
|
|
32
|
+
|
|
33
|
+
RAISE EXCEPTION '% is append-only', TG_TABLE_NAME USING ERRCODE = '55000';
|
|
34
|
+
END;
|
|
35
|
+
$function$;
|
|
36
|
+
|
|
37
|
+
REVOKE ALL ON FUNCTION opengeni_private.reject_memory_slack_immutable_mutation()
|
|
38
|
+
FROM PUBLIC;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
|
|
3
|
+
-- Workspace-shared channels organize root sessions ("workstreams") by work
|
|
4
|
+
-- type in the rail. A channel is pure organizational metadata: filing a
|
|
5
|
+
-- session into one never affects execution, authority, memory, or history,
|
|
6
|
+
-- and deleting a channel only detaches its sessions (channel_id -> NULL).
|
|
7
|
+
CREATE TABLE "channels" (
|
|
8
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
9
|
+
"account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
|
|
10
|
+
"workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
|
|
11
|
+
"name" text NOT NULL,
|
|
12
|
+
"description" text,
|
|
13
|
+
-- Attribution string: 'user:<subject>' | 'session:<id>' | 'system'.
|
|
14
|
+
"created_by" text,
|
|
15
|
+
"created_at" timestamptz NOT NULL DEFAULT now(),
|
|
16
|
+
"updated_at" timestamptz NOT NULL DEFAULT now(),
|
|
17
|
+
CONSTRAINT "channels_name_chk"
|
|
18
|
+
CHECK (length(btrim("name")) BETWEEN 1 AND 80 AND "name" = btrim("name")),
|
|
19
|
+
CONSTRAINT "channels_workspace_account_fk"
|
|
20
|
+
FOREIGN KEY ("workspace_id", "account_id")
|
|
21
|
+
REFERENCES "workspaces"("id", "account_id") ON DELETE CASCADE
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE UNIQUE INDEX "channels_workspace_name_idx"
|
|
25
|
+
ON "channels" ("workspace_id", lower("name"));
|
|
26
|
+
CREATE UNIQUE INDEX "channels_workspace_id_uq"
|
|
27
|
+
ON "channels" ("workspace_id", "id");
|
|
28
|
+
|
|
29
|
+
ALTER TABLE "channels" ENABLE ROW LEVEL SECURITY;
|
|
30
|
+
ALTER TABLE "channels" FORCE ROW LEVEL SECURITY;
|
|
31
|
+
CREATE POLICY workspace_isolation ON "channels"
|
|
32
|
+
USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
|
|
33
|
+
WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
|
|
34
|
+
|
|
35
|
+
DO $grants$
|
|
36
|
+
DECLARE target_schema text := current_schema();
|
|
37
|
+
BEGIN
|
|
38
|
+
IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
|
|
39
|
+
EXECUTE format(
|
|
40
|
+
'GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE %I.channels TO opengeni_app',
|
|
41
|
+
target_schema
|
|
42
|
+
);
|
|
43
|
+
END IF;
|
|
44
|
+
END
|
|
45
|
+
$grants$;
|
|
46
|
+
|
|
47
|
+
-- A root session may be filed into one channel; NULL means unfiled (the
|
|
48
|
+
-- inbox). Keep this transaction to the metadata-only nullable column addition:
|
|
49
|
+
-- both the supporting index and the foreign-key validation need table scans,
|
|
50
|
+
-- so migrations 0221-0223 perform those steps without retaining this ADD
|
|
51
|
+
-- COLUMN transaction's ACCESS EXCLUSIVE lock for the duration of a scan.
|
|
52
|
+
SET LOCAL lock_timeout = '5s';
|
|
53
|
+
ALTER TABLE "sessions"
|
|
54
|
+
ADD COLUMN "channel_id" uuid;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Activate the existing Slice-A session tenancy facts at the accepted-attempt
|
|
3
|
+
-- boundary. This migration is additive: legacy rows remain workspace-shared at
|
|
4
|
+
-- epoch 1, while new claim writers persist the exact session authority they
|
|
5
|
+
-- admitted.
|
|
6
|
+
|
|
7
|
+
SET LOCAL lock_timeout = '5s';
|
|
8
|
+
SET LOCAL statement_timeout = '10min';
|
|
9
|
+
|
|
10
|
+
ALTER TABLE "session_turn_attempts"
|
|
11
|
+
ADD COLUMN IF NOT EXISTS "authority_epoch" integer,
|
|
12
|
+
ADD COLUMN IF NOT EXISTS "authority_visibility" text,
|
|
13
|
+
ADD COLUMN IF NOT EXISTS "authority_owner_organization_membership_id" uuid;
|
|
14
|
+
|
|
15
|
+
UPDATE "session_turn_attempts" attempts
|
|
16
|
+
SET
|
|
17
|
+
authority_epoch = sessions.authority_epoch,
|
|
18
|
+
authority_visibility = sessions.visibility,
|
|
19
|
+
authority_owner_organization_membership_id = sessions.owner_organization_membership_id
|
|
20
|
+
FROM "sessions" sessions
|
|
21
|
+
WHERE sessions.workspace_id = attempts.workspace_id
|
|
22
|
+
AND sessions.id = attempts.session_id;
|
|
23
|
+
|
|
24
|
+
DO $session_visibility_attempt_compatibility$
|
|
25
|
+
DECLARE
|
|
26
|
+
data_schema text := current_schema();
|
|
27
|
+
BEGIN
|
|
28
|
+
EXECUTE format($ddl$
|
|
29
|
+
CREATE OR REPLACE FUNCTION %1$I.session_turn_attempts_fill_authority_snapshot()
|
|
30
|
+
RETURNS trigger
|
|
31
|
+
LANGUAGE plpgsql
|
|
32
|
+
AS $body$
|
|
33
|
+
DECLARE
|
|
34
|
+
workspace_marker boolean;
|
|
35
|
+
source_epoch integer;
|
|
36
|
+
source_visibility text;
|
|
37
|
+
source_owner uuid;
|
|
38
|
+
BEGIN
|
|
39
|
+
SELECT true
|
|
40
|
+
INTO STRICT workspace_marker
|
|
41
|
+
FROM %1$I.workspaces workspace_row
|
|
42
|
+
WHERE workspace_row.id = NEW.workspace_id
|
|
43
|
+
AND workspace_row.account_id = NEW.account_id
|
|
44
|
+
FOR KEY SHARE;
|
|
45
|
+
|
|
46
|
+
SELECT
|
|
47
|
+
source.authority_epoch,
|
|
48
|
+
source.visibility,
|
|
49
|
+
source.owner_organization_membership_id
|
|
50
|
+
INTO STRICT source_epoch, source_visibility, source_owner
|
|
51
|
+
FROM %1$I.sessions source
|
|
52
|
+
WHERE source.workspace_id = NEW.workspace_id
|
|
53
|
+
AND source.account_id = NEW.account_id
|
|
54
|
+
AND source.id = NEW.session_id
|
|
55
|
+
FOR UPDATE;
|
|
56
|
+
|
|
57
|
+
NEW.authority_epoch := source_epoch;
|
|
58
|
+
NEW.authority_visibility := source_visibility;
|
|
59
|
+
NEW.authority_owner_organization_membership_id := source_owner;
|
|
60
|
+
RETURN NEW;
|
|
61
|
+
EXCEPTION
|
|
62
|
+
WHEN NO_DATA_FOUND THEN
|
|
63
|
+
RAISE EXCEPTION 'session authority snapshot source workspace/session not found: %%/%%',
|
|
64
|
+
NEW.workspace_id, NEW.session_id
|
|
65
|
+
USING ERRCODE = '23503';
|
|
66
|
+
END;
|
|
67
|
+
$body$;
|
|
68
|
+
$ddl$, data_schema);
|
|
69
|
+
|
|
70
|
+
EXECUTE format(
|
|
71
|
+
'DROP TRIGGER IF EXISTS session_turn_attempts_fill_authority_snapshot_trg ON %I.session_turn_attempts',
|
|
72
|
+
data_schema
|
|
73
|
+
);
|
|
74
|
+
EXECUTE format($ddl$
|
|
75
|
+
CREATE TRIGGER session_turn_attempts_fill_authority_snapshot_trg
|
|
76
|
+
BEFORE INSERT ON %1$I.session_turn_attempts
|
|
77
|
+
FOR EACH ROW
|
|
78
|
+
WHEN (
|
|
79
|
+
NEW.authority_epoch IS NULL
|
|
80
|
+
AND NEW.authority_visibility IS NULL
|
|
81
|
+
AND NEW.authority_owner_organization_membership_id IS NULL
|
|
82
|
+
)
|
|
83
|
+
EXECUTE FUNCTION %1$I.session_turn_attempts_fill_authority_snapshot()
|
|
84
|
+
$ddl$, data_schema);
|
|
85
|
+
END;
|
|
86
|
+
$session_visibility_attempt_compatibility$;
|
|
87
|
+
|
|
88
|
+
ALTER TABLE "session_turn_attempts"
|
|
89
|
+
ADD CONSTRAINT "session_turn_attempts_authority_epoch_check"
|
|
90
|
+
CHECK (
|
|
91
|
+
"authority_epoch" IS NOT NULL
|
|
92
|
+
AND "authority_visibility" IS NOT NULL
|
|
93
|
+
AND "authority_epoch" > 0
|
|
94
|
+
) NOT VALID,
|
|
95
|
+
ADD CONSTRAINT "session_turn_attempts_authority_visibility_check"
|
|
96
|
+
CHECK (
|
|
97
|
+
"authority_epoch" IS NOT NULL
|
|
98
|
+
AND "authority_visibility" IS NOT NULL
|
|
99
|
+
AND "authority_visibility" IN ('user_private', 'workspace_shared')
|
|
100
|
+
) NOT VALID,
|
|
101
|
+
ADD CONSTRAINT "session_turn_attempts_authority_owner_shape_check"
|
|
102
|
+
CHECK (
|
|
103
|
+
"authority_visibility" <> 'user_private'
|
|
104
|
+
OR "authority_owner_organization_membership_id" IS NOT NULL
|
|
105
|
+
) NOT VALID,
|
|
106
|
+
ADD CONSTRAINT "session_turn_attempts_authority_owner_fk"
|
|
107
|
+
FOREIGN KEY ("authority_owner_organization_membership_id", "account_id")
|
|
108
|
+
REFERENCES "organization_memberships"("id", "account_id")
|
|
109
|
+
ON DELETE RESTRICT NOT VALID;
|
|
110
|
+
|
|
111
|
+
ALTER TABLE "session_turn_attempts"
|
|
112
|
+
VALIDATE CONSTRAINT "session_turn_attempts_authority_epoch_check",
|
|
113
|
+
VALIDATE CONSTRAINT "session_turn_attempts_authority_visibility_check",
|
|
114
|
+
VALIDATE CONSTRAINT "session_turn_attempts_authority_owner_shape_check",
|
|
115
|
+
VALIDATE CONSTRAINT "session_turn_attempts_authority_owner_fk";
|
|
116
|
+
|
|
117
|
+
ALTER TABLE "session_turn_attempts"
|
|
118
|
+
ALTER COLUMN "authority_epoch" SET NOT NULL,
|
|
119
|
+
ALTER COLUMN "authority_visibility" SET NOT NULL;
|
|
120
|
+
|
|
121
|
+
CREATE INDEX IF NOT EXISTS "session_turn_attempts_authority_epoch_idx"
|
|
122
|
+
ON "session_turn_attempts" ("workspace_id", "session_id", "authority_epoch");
|
|
123
|
+
|
|
124
|
+
DO $session_visibility_attempt_immutability$
|
|
125
|
+
DECLARE
|
|
126
|
+
data_schema text := current_schema();
|
|
127
|
+
BEGIN
|
|
128
|
+
EXECUTE format($ddl$
|
|
129
|
+
CREATE OR REPLACE FUNCTION %1$I.session_turn_attempts_guard_authority_snapshot_immutable()
|
|
130
|
+
RETURNS trigger
|
|
131
|
+
LANGUAGE plpgsql
|
|
132
|
+
AS $body$
|
|
133
|
+
BEGIN
|
|
134
|
+
IF NEW.authority_epoch IS DISTINCT FROM OLD.authority_epoch
|
|
135
|
+
OR NEW.authority_visibility IS DISTINCT FROM OLD.authority_visibility
|
|
136
|
+
OR NEW.authority_owner_organization_membership_id IS DISTINCT FROM OLD.authority_owner_organization_membership_id
|
|
137
|
+
THEN
|
|
138
|
+
RAISE EXCEPTION 'session turn attempt authority snapshot is immutable'
|
|
139
|
+
USING ERRCODE = '23514';
|
|
140
|
+
END IF;
|
|
141
|
+
RETURN NEW;
|
|
142
|
+
END;
|
|
143
|
+
$body$;
|
|
144
|
+
$ddl$, data_schema);
|
|
145
|
+
|
|
146
|
+
EXECUTE format(
|
|
147
|
+
'DROP TRIGGER IF EXISTS session_turn_attempts_guard_authority_snapshot_immutable_trg ON %I.session_turn_attempts',
|
|
148
|
+
data_schema
|
|
149
|
+
);
|
|
150
|
+
EXECUTE format($ddl$
|
|
151
|
+
CREATE TRIGGER session_turn_attempts_guard_authority_snapshot_immutable_trg
|
|
152
|
+
BEFORE UPDATE OF authority_epoch, authority_visibility, authority_owner_organization_membership_id
|
|
153
|
+
ON %1$I.session_turn_attempts
|
|
154
|
+
FOR EACH ROW
|
|
155
|
+
EXECUTE FUNCTION %1$I.session_turn_attempts_guard_authority_snapshot_immutable()
|
|
156
|
+
$ddl$, data_schema);
|
|
157
|
+
END;
|
|
158
|
+
$session_visibility_attempt_immutability$;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Install the channel foreign key without scanning the populated sessions
|
|
3
|
+
-- table while holding ACCESS EXCLUSIVE. New writes are checked immediately;
|
|
4
|
+
-- migration 0223 performs the bounded online validation scan separately.
|
|
5
|
+
--
|
|
6
|
+
-- The equivalent auto-named constraint may already exist on a database that
|
|
7
|
+
-- applied the original unreleased 0220 migration. Preserve that validated
|
|
8
|
+
-- constraint only when its complete catalog semantics match this migration.
|
|
9
|
+
SET LOCAL lock_timeout = '5s';
|
|
10
|
+
|
|
11
|
+
DO $migration$
|
|
12
|
+
DECLARE
|
|
13
|
+
same_name_count integer;
|
|
14
|
+
exact_count integer;
|
|
15
|
+
BEGIN
|
|
16
|
+
SELECT count(*)::integer
|
|
17
|
+
INTO same_name_count
|
|
18
|
+
FROM pg_catalog.pg_constraint AS constraint_row
|
|
19
|
+
WHERE constraint_row.conrelid = 'sessions'::regclass
|
|
20
|
+
AND constraint_row.conname IN ('sessions_channel_id_fk', 'sessions_channel_id_fkey');
|
|
21
|
+
|
|
22
|
+
SELECT count(*)::integer
|
|
23
|
+
INTO exact_count
|
|
24
|
+
FROM pg_catalog.pg_constraint AS constraint_row
|
|
25
|
+
JOIN pg_catalog.pg_attribute AS local_column
|
|
26
|
+
ON local_column.attrelid = constraint_row.conrelid
|
|
27
|
+
AND local_column.attnum = constraint_row.conkey[1]
|
|
28
|
+
JOIN pg_catalog.pg_attribute AS referenced_column
|
|
29
|
+
ON referenced_column.attrelid = constraint_row.confrelid
|
|
30
|
+
AND referenced_column.attnum = constraint_row.confkey[1]
|
|
31
|
+
WHERE constraint_row.conrelid = 'sessions'::regclass
|
|
32
|
+
AND constraint_row.confrelid = 'channels'::regclass
|
|
33
|
+
AND constraint_row.contype = 'f'
|
|
34
|
+
AND constraint_row.conname IN ('sessions_channel_id_fk', 'sessions_channel_id_fkey')
|
|
35
|
+
AND cardinality(constraint_row.conkey) = 1
|
|
36
|
+
AND cardinality(constraint_row.confkey) = 1
|
|
37
|
+
AND local_column.attname = 'channel_id'
|
|
38
|
+
AND referenced_column.attname = 'id'
|
|
39
|
+
AND constraint_row.confdeltype = 'n'
|
|
40
|
+
AND constraint_row.confupdtype = 'a'
|
|
41
|
+
AND constraint_row.confmatchtype = 's'
|
|
42
|
+
AND NOT constraint_row.condeferrable
|
|
43
|
+
AND NOT constraint_row.condeferred;
|
|
44
|
+
|
|
45
|
+
IF same_name_count <> exact_count OR exact_count > 1 THEN
|
|
46
|
+
RAISE EXCEPTION
|
|
47
|
+
'sessions channel foreign key name is occupied by an incompatible constraint';
|
|
48
|
+
END IF;
|
|
49
|
+
|
|
50
|
+
IF exact_count = 0 THEN
|
|
51
|
+
ALTER TABLE "sessions"
|
|
52
|
+
ADD CONSTRAINT "sessions_channel_id_fk"
|
|
53
|
+
FOREIGN KEY ("channel_id")
|
|
54
|
+
REFERENCES "channels"("id")
|
|
55
|
+
MATCH SIMPLE
|
|
56
|
+
ON UPDATE NO ACTION
|
|
57
|
+
ON DELETE SET NULL
|
|
58
|
+
NOT DEFERRABLE
|
|
59
|
+
NOT VALID;
|
|
60
|
+
|
|
61
|
+
SELECT count(*)::integer
|
|
62
|
+
INTO exact_count
|
|
63
|
+
FROM pg_catalog.pg_constraint AS constraint_row
|
|
64
|
+
JOIN pg_catalog.pg_attribute AS local_column
|
|
65
|
+
ON local_column.attrelid = constraint_row.conrelid
|
|
66
|
+
AND local_column.attnum = constraint_row.conkey[1]
|
|
67
|
+
JOIN pg_catalog.pg_attribute AS referenced_column
|
|
68
|
+
ON referenced_column.attrelid = constraint_row.confrelid
|
|
69
|
+
AND referenced_column.attnum = constraint_row.confkey[1]
|
|
70
|
+
WHERE constraint_row.conrelid = 'sessions'::regclass
|
|
71
|
+
AND constraint_row.confrelid = 'channels'::regclass
|
|
72
|
+
AND constraint_row.contype = 'f'
|
|
73
|
+
AND constraint_row.conname = 'sessions_channel_id_fk'
|
|
74
|
+
AND cardinality(constraint_row.conkey) = 1
|
|
75
|
+
AND cardinality(constraint_row.confkey) = 1
|
|
76
|
+
AND local_column.attname = 'channel_id'
|
|
77
|
+
AND referenced_column.attname = 'id'
|
|
78
|
+
AND constraint_row.confdeltype = 'n'
|
|
79
|
+
AND constraint_row.confupdtype = 'a'
|
|
80
|
+
AND constraint_row.confmatchtype = 's'
|
|
81
|
+
AND NOT constraint_row.condeferrable
|
|
82
|
+
AND NOT constraint_row.condeferred;
|
|
83
|
+
|
|
84
|
+
IF exact_count <> 1 THEN
|
|
85
|
+
RAISE EXCEPTION 'failed to install the exact sessions channel foreign key';
|
|
86
|
+
END IF;
|
|
87
|
+
END IF;
|
|
88
|
+
END
|
|
89
|
+
$migration$;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
-- deployment-mode: rolling
|
|
2
|
+
-- Preserve the exact audit-event output separately from the bounded
|
|
3
|
+
-- model-facing result item so crash recovery can reproduce the live event.
|
|
4
|
+
|
|
5
|
+
SET lock_timeout = '5s';
|
|
6
|
+
SET statement_timeout = '10min';
|
|
7
|
+
|
|
8
|
+
ALTER TABLE session_pending_tool_calls
|
|
9
|
+
ADD COLUMN IF NOT EXISTS event_output jsonb,
|
|
10
|
+
ADD COLUMN IF NOT EXISTS event_output_codec_version integer;
|
|
11
|
+
|
|
12
|
+
ALTER TABLE session_pending_tool_calls
|
|
13
|
+
DROP CONSTRAINT IF EXISTS session_pending_tool_calls_event_output_codec_version_chk;
|
|
14
|
+
|
|
15
|
+
ALTER TABLE session_pending_tool_calls
|
|
16
|
+
ADD CONSTRAINT session_pending_tool_calls_event_output_codec_version_chk
|
|
17
|
+
CHECK (
|
|
18
|
+
(event_output IS NULL AND event_output_codec_version IS NULL)
|
|
19
|
+
OR (event_output IS NOT NULL AND event_output_codec_version = 1)
|
|
20
|
+
) NOT VALID;
|
|
21
|
+
|
|
22
|
+
ALTER TABLE session_pending_tool_calls
|
|
23
|
+
VALIDATE CONSTRAINT session_pending_tool_calls_event_output_codec_version_chk;
|
|
24
|
+
|
|
25
|
+
COMMENT ON COLUMN session_pending_tool_calls.event_output IS
|
|
26
|
+
'Lossless {value} envelope for the exact agent.toolCall.output projection retained until live publish or recovery.';
|
|
27
|
+
COMMENT ON COLUMN session_pending_tool_calls.event_output_codec_version IS
|
|
28
|
+
'Out-of-band lossless JSON codec version for event_output; NULL denotes an older writer or no retained event output.';
|
|
29
|
+
|
|
30
|
+
CREATE OR REPLACE FUNCTION opengeni_private.guard_pending_tool_event_output_delete()
|
|
31
|
+
RETURNS trigger
|
|
32
|
+
LANGUAGE plpgsql
|
|
33
|
+
SET search_path = pg_catalog
|
|
34
|
+
AS $$
|
|
35
|
+
BEGIN
|
|
36
|
+
IF OLD.event_output IS NOT NULL
|
|
37
|
+
AND nullif(current_setting('opengeni.account_id', true), '') IS NOT NULL
|
|
38
|
+
AND current_setting('opengeni.pending_tool_event_output_v1', true) IS DISTINCT FROM '1' THEN
|
|
39
|
+
RAISE EXCEPTION USING
|
|
40
|
+
ERRCODE = '55000',
|
|
41
|
+
MESSAGE = 'rich pending tool output requires a v1-aware settlement worker';
|
|
42
|
+
END IF;
|
|
43
|
+
RETURN OLD;
|
|
44
|
+
END;
|
|
45
|
+
$$;
|
|
46
|
+
|
|
47
|
+
DROP TRIGGER IF EXISTS session_pending_tool_calls_event_output_delete_guard
|
|
48
|
+
ON session_pending_tool_calls;
|
|
49
|
+
|
|
50
|
+
CREATE TRIGGER session_pending_tool_calls_event_output_delete_guard
|
|
51
|
+
BEFORE DELETE ON session_pending_tool_calls
|
|
52
|
+
FOR EACH ROW
|
|
53
|
+
EXECUTE FUNCTION opengeni_private.guard_pending_tool_event_output_delete();
|
|
54
|
+
|
|
55
|
+
REVOKE ALL ON FUNCTION opengeni_private.guard_pending_tool_event_output_delete() FROM PUBLIC;
|