@opengeni/db 0.12.6 → 0.13.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/schema.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import 'drizzle-orm';
2
2
  import 'drizzle-orm/pg-core';
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-CeXWlYcG.js';
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 slackBotPostOperations, at as socialConnections, au as socialPosts, av as stripeWebhookEvents, aw as usageEvents, ax as workspaceCaptures, ay as workspaceControlEvents, az as workspaceInferenceControls, aA as workspaceInstructionPolicyActivationEvents, aB as workspaceInstructionPolicyHeads, aC as workspaceInstructionPolicyRevisions, aD as workspaceMemberships, aE as workspaceModelPolicies, aF as workspacePacks, aG as workspaceSessionActivityRevisions, aH as workspaceVariableSetVariables, aI as workspaceVariableSets, w as workspaces } from './schema-D-hyrsFt.js';
package/dist/schema.js CHANGED
@@ -79,6 +79,7 @@ import {
79
79
  sessionTurns,
80
80
  sessionWorkflowWakeOutbox,
81
81
  sessions,
82
+ slackBotPostOperations,
82
83
  socialConnections,
83
84
  socialPosts,
84
85
  stripeWebhookEvents,
@@ -86,6 +87,9 @@ import {
86
87
  workspaceCaptures,
87
88
  workspaceControlEvents,
88
89
  workspaceInferenceControls,
90
+ workspaceInstructionPolicyActivationEvents,
91
+ workspaceInstructionPolicyHeads,
92
+ workspaceInstructionPolicyRevisions,
89
93
  workspaceMemberships,
90
94
  workspaceModelPolicies,
91
95
  workspacePacks,
@@ -93,7 +97,7 @@ import {
93
97
  workspaceVariableSetVariables,
94
98
  workspaceVariableSets,
95
99
  workspaces
96
- } from "./chunk-CYA6BHV6.js";
100
+ } from "./chunk-N4WTE6SB.js";
97
101
  import "./chunk-PZ5AY32C.js";
98
102
  export {
99
103
  agentRunStates,
@@ -176,6 +180,7 @@ export {
176
180
  sessionTurns,
177
181
  sessionWorkflowWakeOutbox,
178
182
  sessions,
183
+ slackBotPostOperations,
179
184
  socialConnections,
180
185
  socialPosts,
181
186
  stripeWebhookEvents,
@@ -183,6 +188,9 @@ export {
183
188
  workspaceCaptures,
184
189
  workspaceControlEvents,
185
190
  workspaceInferenceControls,
191
+ workspaceInstructionPolicyActivationEvents,
192
+ workspaceInstructionPolicyHeads,
193
+ workspaceInstructionPolicyRevisions,
186
194
  workspaceMemberships,
187
195
  workspaceModelPolicies,
188
196
  workspacePacks,
@@ -0,0 +1,330 @@
1
+ -- deployment-mode: rolling
2
+ -- Backend-only workspace instruction-policy foundation. This migration performs no
3
+ -- backfill: workspaces without an activation head retain legacy behavior.
4
+
5
+ CREATE SEQUENCE IF NOT EXISTS "workspace_instruction_policy_revision_seq" AS bigint;
6
+
7
+ CREATE TABLE IF NOT EXISTS "workspace_instruction_policy_revisions" (
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
+ "revision" bigint NOT NULL DEFAULT nextval('workspace_instruction_policy_revision_seq'),
12
+ "kind" text NOT NULL,
13
+ "scope" text NOT NULL,
14
+ "role_key" text,
15
+ "content" text NOT NULL,
16
+ "content_hash" text NOT NULL,
17
+ "provenance_source" text NOT NULL,
18
+ "provenance_source_id" text,
19
+ "supersedes_revision_id" uuid REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
20
+ "created_by_subject_id" text NOT NULL,
21
+ "created_at" timestamptz NOT NULL DEFAULT now(),
22
+ CONSTRAINT "workspace_instruction_policy_revisions_revision_chk" CHECK ("revision" > 0),
23
+ CONSTRAINT "workspace_instruction_policy_revisions_target_chk" CHECK (
24
+ ("kind" = 'charter' AND "scope" = 'global' AND "role_key" IS NULL)
25
+ OR ("kind" = 'policy' AND "scope" = 'global' AND "role_key" IS NULL)
26
+ OR ("kind" = 'policy' AND "scope" = 'role' AND "role_key" IS NOT NULL)
27
+ ),
28
+ CONSTRAINT "workspace_instruction_policy_revisions_role_key_chk" CHECK (
29
+ "role_key" IS NULL
30
+ OR (
31
+ "role_key" = lower(btrim("role_key"))
32
+ AND length("role_key") BETWEEN 1 AND 64
33
+ AND "role_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
34
+ AND "role_key" !~ '--'
35
+ )
36
+ ),
37
+ CONSTRAINT "workspace_instruction_policy_revisions_content_chk" CHECK (
38
+ length(btrim("content")) > 0 AND octet_length("content") <= 1048576
39
+ ),
40
+ CONSTRAINT "workspace_instruction_policy_revisions_hash_chk" CHECK (
41
+ "content_hash" ~ '^[0-9a-f]{64}$'
42
+ AND "content_hash" = encode(sha256(convert_to("content", 'UTF8')), 'hex')
43
+ ),
44
+ CONSTRAINT "workspace_instruction_policy_revisions_provenance_chk" CHECK (
45
+ "provenance_source" IN ('human', 'onboarding', 'knowledge_proposal', 'legacy_import')
46
+ AND ("provenance_source_id" IS NULL OR length("provenance_source_id") BETWEEN 1 AND 512)
47
+ ),
48
+ CONSTRAINT "workspace_instruction_policy_revisions_actor_chk" CHECK (
49
+ length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
50
+ ),
51
+ CONSTRAINT "workspace_instruction_policy_revisions_workspace_revision_uq"
52
+ UNIQUE ("workspace_id", "revision")
53
+ );
54
+
55
+ CREATE INDEX IF NOT EXISTS "workspace_instruction_policy_revisions_workspace_history_idx"
56
+ ON "workspace_instruction_policy_revisions"
57
+ ("workspace_id", "kind", "scope", "role_key", "revision" DESC);
58
+
59
+ CREATE TABLE IF NOT EXISTS "workspace_instruction_policy_heads" (
60
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
61
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
62
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
63
+ "kind" text NOT NULL,
64
+ "scope" text NOT NULL,
65
+ "role_key" text,
66
+ "revision_id" uuid NOT NULL REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
67
+ "revision" bigint NOT NULL,
68
+ "content_hash" text NOT NULL,
69
+ "activation_version" bigint NOT NULL,
70
+ "activated_at" timestamptz NOT NULL DEFAULT now(),
71
+ CONSTRAINT "workspace_instruction_policy_heads_target_chk" CHECK (
72
+ ("kind" = 'charter' AND "scope" = 'global' AND "role_key" IS NULL)
73
+ OR ("kind" = 'policy' AND "scope" = 'global' AND "role_key" IS NULL)
74
+ OR ("kind" = 'policy' AND "scope" = 'role' AND "role_key" IS NOT NULL)
75
+ ),
76
+ CONSTRAINT "workspace_instruction_policy_heads_role_key_chk" CHECK (
77
+ "role_key" IS NULL
78
+ OR (
79
+ "role_key" = lower(btrim("role_key"))
80
+ AND length("role_key") BETWEEN 1 AND 64
81
+ AND "role_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
82
+ AND "role_key" !~ '--'
83
+ )
84
+ ),
85
+ CONSTRAINT "workspace_instruction_policy_heads_revision_chk" CHECK (
86
+ "revision" > 0 AND "activation_version" > 0 AND "content_hash" ~ '^[0-9a-f]{64}$'
87
+ )
88
+ );
89
+
90
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_heads_charter_uq"
91
+ ON "workspace_instruction_policy_heads" ("workspace_id")
92
+ WHERE "kind" = 'charter';
93
+
94
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_heads_global_policy_uq"
95
+ ON "workspace_instruction_policy_heads" ("workspace_id")
96
+ WHERE "kind" = 'policy' AND "scope" = 'global';
97
+
98
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_heads_role_policy_uq"
99
+ ON "workspace_instruction_policy_heads" ("workspace_id", "role_key")
100
+ WHERE "kind" = 'policy' AND "scope" = 'role';
101
+
102
+ CREATE TABLE IF NOT EXISTS "workspace_instruction_policy_activation_events" (
103
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
104
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
105
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
106
+ "kind" text NOT NULL,
107
+ "scope" text NOT NULL,
108
+ "role_key" text,
109
+ "type" text NOT NULL,
110
+ "activation_version" bigint NOT NULL,
111
+ "old_revision_id" uuid REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
112
+ "old_revision" bigint,
113
+ "old_content_hash" text,
114
+ "new_revision_id" uuid NOT NULL REFERENCES "workspace_instruction_policy_revisions"("id") ON DELETE RESTRICT,
115
+ "new_revision" bigint NOT NULL,
116
+ "new_content_hash" text NOT NULL,
117
+ "actor_subject_id" text NOT NULL,
118
+ "reason" text NOT NULL,
119
+ "created_at" timestamptz NOT NULL DEFAULT now(),
120
+ CONSTRAINT "workspace_instruction_policy_activation_events_type_chk" CHECK (
121
+ "type" IN ('activate', 'rollback')
122
+ ),
123
+ CONSTRAINT "workspace_instruction_policy_activation_events_target_chk" CHECK (
124
+ ("kind" = 'charter' AND "scope" = 'global' AND "role_key" IS NULL)
125
+ OR ("kind" = 'policy' AND "scope" = 'global' AND "role_key" IS NULL)
126
+ OR ("kind" = 'policy' AND "scope" = 'role' AND "role_key" IS NOT NULL)
127
+ ),
128
+ CONSTRAINT "workspace_instruction_policy_activation_events_role_key_chk" CHECK (
129
+ "role_key" IS NULL
130
+ OR (
131
+ "role_key" = lower(btrim("role_key"))
132
+ AND length("role_key") BETWEEN 1 AND 64
133
+ AND "role_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
134
+ AND "role_key" !~ '--'
135
+ )
136
+ ),
137
+ CONSTRAINT "workspace_instruction_policy_activation_events_old_revision_chk" CHECK (
138
+ ("old_revision_id" IS NULL AND "old_revision" IS NULL AND "old_content_hash" IS NULL)
139
+ OR (
140
+ "old_revision_id" IS NOT NULL
141
+ AND "old_revision" > 0
142
+ AND "old_content_hash" ~ '^[0-9a-f]{64}$'
143
+ )
144
+ ),
145
+ CONSTRAINT "workspace_instruction_policy_activation_events_new_revision_chk" CHECK (
146
+ "new_revision" > 0
147
+ AND "new_content_hash" ~ '^[0-9a-f]{64}$'
148
+ AND "activation_version" > 0
149
+ ),
150
+ CONSTRAINT "workspace_instruction_policy_activation_events_audit_chk" CHECK (
151
+ length(btrim("actor_subject_id")) BETWEEN 1 AND 1024
152
+ AND length(btrim("reason")) BETWEEN 1 AND 4096
153
+ )
154
+ );
155
+
156
+ CREATE UNIQUE INDEX IF NOT EXISTS "workspace_instruction_policy_events_target_version_uq"
157
+ ON "workspace_instruction_policy_activation_events"
158
+ ("workspace_id", "kind", "scope", coalesce("role_key", ''), "activation_version");
159
+
160
+ CREATE INDEX IF NOT EXISTS "workspace_instruction_policy_events_workspace_time_idx"
161
+ ON "workspace_instruction_policy_activation_events" ("workspace_id", "created_at" DESC, "id");
162
+
163
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_revision_link()
164
+ RETURNS trigger
165
+ LANGUAGE plpgsql
166
+ AS $$
167
+ DECLARE
168
+ target_workspace_id uuid;
169
+ target_kind text;
170
+ target_scope text;
171
+ target_role_key text;
172
+ BEGIN
173
+ IF NEW."supersedes_revision_id" IS NULL THEN
174
+ RETURN NEW;
175
+ END IF;
176
+ SELECT "workspace_id", "kind", "scope", "role_key"
177
+ INTO target_workspace_id, target_kind, target_scope, target_role_key
178
+ FROM "workspace_instruction_policy_revisions"
179
+ WHERE "id" = NEW."supersedes_revision_id";
180
+ IF NOT FOUND
181
+ OR target_workspace_id IS DISTINCT FROM NEW."workspace_id"
182
+ OR target_kind IS DISTINCT FROM NEW."kind"
183
+ OR target_scope IS DISTINCT FROM NEW."scope"
184
+ OR target_role_key IS DISTINCT FROM NEW."role_key"
185
+ THEN
186
+ RAISE EXCEPTION 'superseded instruction-policy revision must use the same workspace and target'
187
+ USING ERRCODE = '23514';
188
+ END IF;
189
+ RETURN NEW;
190
+ END;
191
+ $$;
192
+
193
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_revisions_validate_link
194
+ ON "workspace_instruction_policy_revisions";
195
+ CREATE TRIGGER workspace_instruction_policy_revisions_validate_link
196
+ BEFORE INSERT OR UPDATE ON "workspace_instruction_policy_revisions"
197
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_revision_link();
198
+
199
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_head()
200
+ RETURNS trigger
201
+ LANGUAGE plpgsql
202
+ AS $$
203
+ BEGIN
204
+ IF NOT EXISTS (
205
+ SELECT 1
206
+ FROM "workspace_instruction_policy_revisions" revision
207
+ WHERE revision."id" = NEW."revision_id"
208
+ AND revision."account_id" = NEW."account_id"
209
+ AND revision."workspace_id" = NEW."workspace_id"
210
+ AND revision."kind" = NEW."kind"
211
+ AND revision."scope" = NEW."scope"
212
+ AND revision."role_key" IS NOT DISTINCT FROM NEW."role_key"
213
+ AND revision."revision" = NEW."revision"
214
+ AND revision."content_hash" = NEW."content_hash"
215
+ ) THEN
216
+ RAISE EXCEPTION 'instruction-policy head must identify an exact target revision'
217
+ USING ERRCODE = '23514';
218
+ END IF;
219
+ RETURN NEW;
220
+ END;
221
+ $$;
222
+
223
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_heads_validate_revision
224
+ ON "workspace_instruction_policy_heads";
225
+ CREATE TRIGGER workspace_instruction_policy_heads_validate_revision
226
+ BEFORE INSERT OR UPDATE ON "workspace_instruction_policy_heads"
227
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_head();
228
+
229
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_event()
230
+ RETURNS trigger
231
+ LANGUAGE plpgsql
232
+ AS $$
233
+ BEGIN
234
+ IF NOT EXISTS (
235
+ SELECT 1
236
+ FROM "workspace_instruction_policy_revisions" revision
237
+ WHERE revision."id" = NEW."new_revision_id"
238
+ AND revision."account_id" = NEW."account_id"
239
+ AND revision."workspace_id" = NEW."workspace_id"
240
+ AND revision."kind" = NEW."kind"
241
+ AND revision."scope" = NEW."scope"
242
+ AND revision."role_key" IS NOT DISTINCT FROM NEW."role_key"
243
+ AND revision."revision" = NEW."new_revision"
244
+ AND revision."content_hash" = NEW."new_content_hash"
245
+ ) THEN
246
+ RAISE EXCEPTION 'instruction-policy activation event has an invalid new revision'
247
+ USING ERRCODE = '23514';
248
+ END IF;
249
+ IF NEW."old_revision_id" IS NOT NULL AND NOT EXISTS (
250
+ SELECT 1
251
+ FROM "workspace_instruction_policy_revisions" revision
252
+ WHERE revision."id" = NEW."old_revision_id"
253
+ AND revision."account_id" = NEW."account_id"
254
+ AND revision."workspace_id" = NEW."workspace_id"
255
+ AND revision."kind" = NEW."kind"
256
+ AND revision."scope" = NEW."scope"
257
+ AND revision."role_key" IS NOT DISTINCT FROM NEW."role_key"
258
+ AND revision."revision" = NEW."old_revision"
259
+ AND revision."content_hash" = NEW."old_content_hash"
260
+ ) THEN
261
+ RAISE EXCEPTION 'instruction-policy activation event has an invalid old revision'
262
+ USING ERRCODE = '23514';
263
+ END IF;
264
+ RETURN NEW;
265
+ END;
266
+ $$;
267
+
268
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_events_validate_revisions
269
+ ON "workspace_instruction_policy_activation_events";
270
+ CREATE TRIGGER workspace_instruction_policy_events_validate_revisions
271
+ BEFORE INSERT OR UPDATE ON "workspace_instruction_policy_activation_events"
272
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_event();
273
+
274
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_reject_mutation()
275
+ RETURNS trigger
276
+ LANGUAGE plpgsql
277
+ AS $$
278
+ BEGIN
279
+ -- PostgreSQL implements ON DELETE CASCADE with a parent constraint trigger.
280
+ -- By the time that trigger deletes child rows, the workspace is absent and
281
+ -- this child trigger is nested. Require both facts so direct history deletes
282
+ -- and deletes issued by unrelated triggers remain fail-closed.
283
+ IF TG_OP = 'DELETE'
284
+ AND pg_trigger_depth() > 1
285
+ AND NOT EXISTS (
286
+ SELECT 1
287
+ FROM "workspaces" workspace
288
+ WHERE workspace."id" = OLD."workspace_id"
289
+ )
290
+ THEN
291
+ RETURN OLD;
292
+ END IF;
293
+ RAISE EXCEPTION 'workspace instruction-policy history is immutable'
294
+ USING ERRCODE = '55000';
295
+ END;
296
+ $$;
297
+
298
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_revisions_immutable
299
+ ON "workspace_instruction_policy_revisions";
300
+ CREATE TRIGGER workspace_instruction_policy_revisions_immutable
301
+ BEFORE UPDATE OR DELETE ON "workspace_instruction_policy_revisions"
302
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_reject_mutation();
303
+
304
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_events_immutable
305
+ ON "workspace_instruction_policy_activation_events";
306
+ CREATE TRIGGER workspace_instruction_policy_events_immutable
307
+ BEFORE UPDATE OR DELETE ON "workspace_instruction_policy_activation_events"
308
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_reject_mutation();
309
+
310
+ ALTER TABLE "workspace_instruction_policy_revisions" ENABLE ROW LEVEL SECURITY;
311
+ ALTER TABLE "workspace_instruction_policy_revisions" FORCE ROW LEVEL SECURITY;
312
+ ALTER TABLE "workspace_instruction_policy_heads" ENABLE ROW LEVEL SECURITY;
313
+ ALTER TABLE "workspace_instruction_policy_heads" FORCE ROW LEVEL SECURITY;
314
+ ALTER TABLE "workspace_instruction_policy_activation_events" ENABLE ROW LEVEL SECURITY;
315
+ ALTER TABLE "workspace_instruction_policy_activation_events" FORCE ROW LEVEL SECURITY;
316
+
317
+ DROP POLICY IF EXISTS workspace_isolation ON "workspace_instruction_policy_revisions";
318
+ CREATE POLICY workspace_isolation ON "workspace_instruction_policy_revisions"
319
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
320
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
321
+
322
+ DROP POLICY IF EXISTS workspace_isolation ON "workspace_instruction_policy_heads";
323
+ CREATE POLICY workspace_isolation ON "workspace_instruction_policy_heads"
324
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
325
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
326
+
327
+ DROP POLICY IF EXISTS workspace_isolation ON "workspace_instruction_policy_activation_events";
328
+ CREATE POLICY workspace_isolation ON "workspace_instruction_policy_activation_events"
329
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
330
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
@@ -0,0 +1,133 @@
1
+ -- deployment-mode: rolling
2
+ -- New readers trust only dedicated-route Slack installs whose server marker
3
+ -- matches the exact credential version. Existing rows and rows created by old
4
+ -- generic API pods remain markerless and fail closed. The trigger also clears a
5
+ -- marker if a rolling old writer mutates credential/identity fields without a
6
+ -- fresh verification in that same statement.
7
+
8
+ ALTER TABLE "connections"
9
+ ADD COLUMN IF NOT EXISTS "verified_install_at" timestamptz,
10
+ ADD COLUMN IF NOT EXISTS "verified_install_version" integer;
11
+
12
+ DO $constraint$
13
+ BEGIN
14
+ IF NOT EXISTS (
15
+ SELECT 1
16
+ FROM pg_constraint
17
+ WHERE conname = 'connections_verified_install_check'
18
+ AND conrelid = 'connections'::regclass
19
+ ) THEN
20
+ ALTER TABLE "connections"
21
+ ADD CONSTRAINT "connections_verified_install_check"
22
+ CHECK (
23
+ (
24
+ "verified_install_at" IS NULL
25
+ AND "verified_install_version" IS NULL
26
+ )
27
+ OR (
28
+ "verified_install_at" IS NOT NULL
29
+ AND "verified_install_version" = "version"
30
+ AND "verified_install_version" > 0
31
+ AND "subject_id" IS NULL
32
+ AND "provider_domain" = 'slack.com'
33
+ AND "kind" = 'app_install'
34
+ )
35
+ );
36
+ END IF;
37
+ END $constraint$;
38
+
39
+ CREATE OR REPLACE FUNCTION opengeni_private.invalidate_stale_verified_install()
40
+ RETURNS trigger
41
+ LANGUAGE plpgsql
42
+ AS $function$
43
+ BEGIN
44
+ IF (
45
+ NEW."subject_id" IS DISTINCT FROM OLD."subject_id"
46
+ OR NEW."provider_domain" IS DISTINCT FROM OLD."provider_domain"
47
+ OR NEW."kind" IS DISTINCT FROM OLD."kind"
48
+ OR NEW."credential_encrypted" IS DISTINCT FROM OLD."credential_encrypted"
49
+ OR NEW."granted_scopes" IS DISTINCT FROM OLD."granted_scopes"
50
+ OR NEW."metadata" IS DISTINCT FROM OLD."metadata"
51
+ OR NEW."version" IS DISTINCT FROM OLD."version"
52
+ )
53
+ AND NEW."verified_install_at" IS NOT DISTINCT FROM OLD."verified_install_at"
54
+ AND NEW."verified_install_version" IS NOT DISTINCT FROM OLD."verified_install_version"
55
+ THEN
56
+ NEW."verified_install_at" := NULL;
57
+ NEW."verified_install_version" := NULL;
58
+ END IF;
59
+ RETURN NEW;
60
+ END;
61
+ $function$;
62
+
63
+ DROP TRIGGER IF EXISTS connections_invalidate_stale_verified_install ON "connections";
64
+ CREATE TRIGGER connections_invalidate_stale_verified_install
65
+ BEFORE UPDATE OF
66
+ "subject_id", "provider_domain", "kind", "credential_encrypted",
67
+ "granted_scopes", "metadata", "version",
68
+ "verified_install_at", "verified_install_version"
69
+ ON "connections"
70
+ FOR EACH ROW
71
+ EXECUTE FUNCTION opengeni_private.invalidate_stale_verified_install();
72
+
73
+ CREATE TABLE "slack_bot_post_operations" (
74
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
75
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
76
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
77
+ "connection_id" uuid NOT NULL REFERENCES "connections"("id") ON DELETE CASCADE,
78
+ "operation_id" uuid NOT NULL,
79
+ "client_message_id" uuid NOT NULL,
80
+ "target_kind" text NOT NULL,
81
+ "target_id" text NOT NULL,
82
+ "request_digest" text NOT NULL,
83
+ "status" text NOT NULL,
84
+ "claim_holder_id" uuid,
85
+ "claim_expires_at" timestamptz,
86
+ "attempt_count" integer NOT NULL DEFAULT 0,
87
+ "last_failure_code" text,
88
+ "slack_channel_id" text,
89
+ "slack_message_timestamp" text,
90
+ "completed_at" timestamptz,
91
+ "created_at" timestamptz NOT NULL DEFAULT now(),
92
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
93
+ CONSTRAINT "slack_bot_post_operations_workspace_operation_uq"
94
+ UNIQUE ("workspace_id", "connection_id", "operation_id"),
95
+ CONSTRAINT "slack_bot_post_operations_target_kind_check"
96
+ CHECK ("target_kind" IN ('channel', 'user')),
97
+ CONSTRAINT "slack_bot_post_operations_status_check"
98
+ CHECK ("status" IN ('provider_started', 'completed')),
99
+ CONSTRAINT "slack_bot_post_operations_identity_check"
100
+ CHECK (
101
+ "client_message_id" = "operation_id"
102
+ AND length("target_id") BETWEEN 1 AND 64
103
+ AND "request_digest" ~ '^[0-9a-f]{64}$'
104
+ AND "attempt_count" > 0
105
+ AND (("claim_holder_id" IS NULL) = ("claim_expires_at" IS NULL))
106
+ ),
107
+ CONSTRAINT "slack_bot_post_operations_completion_check"
108
+ CHECK (
109
+ (
110
+ "status" = 'provider_started'
111
+ AND "slack_channel_id" IS NULL
112
+ AND "slack_message_timestamp" IS NULL
113
+ AND "completed_at" IS NULL
114
+ )
115
+ OR (
116
+ "status" = 'completed'
117
+ AND "claim_holder_id" IS NULL
118
+ AND "claim_expires_at" IS NULL
119
+ AND "slack_channel_id" IS NOT NULL
120
+ AND "slack_message_timestamp" IS NOT NULL
121
+ AND "completed_at" IS NOT NULL
122
+ )
123
+ )
124
+ );
125
+
126
+ CREATE INDEX "slack_bot_post_operations_workspace_status_idx"
127
+ ON "slack_bot_post_operations" ("workspace_id", "status", "updated_at");
128
+
129
+ ALTER TABLE "slack_bot_post_operations" ENABLE ROW LEVEL SECURITY;
130
+ ALTER TABLE "slack_bot_post_operations" FORCE ROW LEVEL SECURITY;
131
+ CREATE POLICY workspace_isolation ON "slack_bot_post_operations"
132
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
133
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/db",
3
- "version": "0.12.6",
3
+ "version": "0.13.1",
4
4
  "description": "OpenGeni persistence: Drizzle schema, RLS-scoped query layer, the SQL migration runner, and role provisioning.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,8 +51,8 @@
51
51
  },
52
52
  "dependencies": {
53
53
  "@opengeni/codex": "^0.2.7",
54
- "@opengeni/config": "^0.7.6",
55
- "@opengeni/contracts": "^0.19.4",
54
+ "@opengeni/config": "^0.7.8",
55
+ "@opengeni/contracts": "^0.20.1",
56
56
  "@opengeni/network": "^0.1.1",
57
57
  "drizzle-orm": "^0.45.2",
58
58
  "postgres": "^3.4.7"