@opengeni/db 0.14.3 → 0.17.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.
Files changed (67) hide show
  1. package/dist/{chunk-Y5WZZVQK.js → chunk-SK7YYHBI.js} +5 -2
  2. package/dist/chunk-SK7YYHBI.js.map +1 -0
  3. package/dist/{chunk-ELMUCYZF.js → chunk-T6RSJT6C.js} +202 -43
  4. package/dist/chunk-T6RSJT6C.js.map +1 -0
  5. package/dist/chunk-TLAC622R.js +4990 -0
  6. package/dist/chunk-TLAC622R.js.map +1 -0
  7. package/dist/codex-token-resolver.d.ts +64 -0
  8. package/dist/connection-token-resolver.d.ts +90 -0
  9. package/dist/environment-crypto.d.ts +11 -0
  10. package/dist/event-payload-sanitizer.d.ts +32 -0
  11. package/dist/index.d.ts +6374 -10
  12. package/dist/index.js +8075 -2776
  13. package/dist/index.js.map +1 -1
  14. package/dist/insights.d.ts +157 -0
  15. package/dist/memory-domain.d.ts +44 -0
  16. package/dist/migrate.d.ts +5 -7
  17. package/dist/migrate.js +1 -1
  18. package/dist/new-session-drafts.d.ts +49 -0
  19. package/dist/persistence-errors.d.ts +69 -0
  20. package/dist/preference-registry-schema.d.ts +1147 -0
  21. package/dist/preference-registry.d.ts +878 -0
  22. package/dist/provision-roles.d.ts +4 -6489
  23. package/dist/provision-roles.js +1 -1
  24. package/dist/role-relationships.d.ts +35 -0
  25. package/dist/runtime-posture-cli.d.ts +1 -0
  26. package/dist/runtime-posture.d.ts +103 -0
  27. package/dist/schema.d.ts +25330 -3
  28. package/dist/schema.js +31 -1
  29. package/dist/session-control.d.ts +331 -0
  30. package/dist/session-queue-commands.d.ts +186 -0
  31. package/dist/session-tool-call-settlement.d.ts +32 -0
  32. package/dist/turn-initiator.d.ts +28 -0
  33. package/dist/workspace-artifacts.d.ts +62 -0
  34. package/dist/workspace-instruction-policies-schema.d.ts +735 -0
  35. package/dist/workspace-instruction-policies.d.ts +64 -0
  36. package/drizzle/0136_unified_session_tool_policy.sql +1 -1
  37. package/drizzle/0137_preference_registry.sql +1347 -0
  38. package/drizzle/0138_sandbox_checkpoint_artifacts_and_deadlines.sql +1074 -0
  39. package/drizzle/0139_codex_provider_artifact_invalidations.sql +51 -0
  40. package/drizzle/0140_sandbox_restore_and_reaper_fences.sql +276 -0
  41. package/drizzle/0142_sandbox_archive_capture_gate.sql +80 -0
  42. package/drizzle/0143_session_codex_compaction_mode.sql +20 -0
  43. package/drizzle/0144_sandbox_viewer_force_drain_gate.sql +95 -0
  44. package/drizzle/0145_model_call_facts.sql +96 -0
  45. package/drizzle/0146_slack_bot_delete_idempotency.sql +105 -0
  46. package/drizzle/0147_draft_latency_mode.sql +12 -0
  47. package/drizzle/0148_session_turn_latency_mode.sql +12 -0
  48. package/drizzle/0149_workspace_artifacts.sql +348 -0
  49. package/drizzle/0150_slack_task_interactions.sql +344 -0
  50. package/package.json +6 -6
  51. package/src/index.ts +5766 -275
  52. package/src/insights.ts +837 -0
  53. package/src/migrate.ts +9 -1
  54. package/src/new-session-drafts.ts +4 -0
  55. package/src/preference-registry-schema.ts +170 -0
  56. package/src/preference-registry.ts +1220 -0
  57. package/src/provision-roles.ts +95 -22
  58. package/src/role-relationships.ts +132 -0
  59. package/src/runtime-posture.ts +47 -27
  60. package/src/schema.ts +875 -19
  61. package/src/session-queue-commands.ts +39 -6
  62. package/src/workspace-artifacts.ts +751 -0
  63. package/dist/chunk-DW24CKCT.js +0 -4046
  64. package/dist/chunk-DW24CKCT.js.map +0 -1
  65. package/dist/chunk-ELMUCYZF.js.map +0 -1
  66. package/dist/chunk-Y5WZZVQK.js.map +0 -1
  67. package/dist/schema-DhkRhcuQ.d.ts +0 -22666
@@ -0,0 +1,1347 @@
1
+ -- deployment-mode: rolling
2
+ -- Additive, backend-only structured preference registry. No prompt
3
+ -- composition, document ingestion, knowledge-memory mutation, or backfill.
4
+
5
+ SET lock_timeout = '5s';
6
+ SET statement_timeout = '10min';
7
+
8
+ CREATE SEQUENCE IF NOT EXISTS "preference_registry_revision_seq" AS bigint;
9
+
10
+ CREATE TABLE "preference_registry_preferences" (
11
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
12
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
13
+ "stable_key" text NOT NULL,
14
+ "scope" text NOT NULL,
15
+ "scope_workspace_id" uuid REFERENCES "workspaces"("id") ON DELETE RESTRICT,
16
+ "scope_subject_id" text,
17
+ "status" text NOT NULL DEFAULT 'proposed',
18
+ "scope_version" integer NOT NULL DEFAULT 1,
19
+ "activation_version" integer NOT NULL DEFAULT 0,
20
+ "active_revision_id" uuid,
21
+ "active_revision" bigint,
22
+ "active_content_hash" text,
23
+ "superseded_by_preference_id" uuid,
24
+ "created_by_subject_id" text NOT NULL,
25
+ "created_at" timestamptz NOT NULL DEFAULT now(),
26
+ "updated_at" timestamptz NOT NULL DEFAULT now(),
27
+ CONSTRAINT "preference_registry_preferences_key_chk" CHECK (
28
+ length("stable_key") BETWEEN 1 AND 96
29
+ AND "stable_key" = lower(btrim("stable_key"))
30
+ AND "stable_key" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
31
+ AND "stable_key" !~ '--'
32
+ ),
33
+ CONSTRAINT "preference_registry_preferences_scope_shape_chk" CHECK (
34
+ ("scope" = 'organization' AND "scope_workspace_id" IS NULL AND "scope_subject_id" IS NULL)
35
+ OR ("scope" = 'workspace' AND "scope_workspace_id" IS NOT NULL AND "scope_subject_id" IS NULL)
36
+ OR ("scope" = 'user' AND "scope_workspace_id" IS NULL AND "scope_subject_id" IS NOT NULL)
37
+ ),
38
+ CONSTRAINT "preference_registry_preferences_status_chk" CHECK (
39
+ "status" IN ('proposed', 'active', 'inactive', 'rejected', 'superseded')
40
+ ),
41
+ CONSTRAINT "preference_registry_preferences_versions_chk" CHECK (
42
+ "scope_version" > 0 AND "activation_version" >= 0
43
+ ),
44
+ CONSTRAINT "preference_registry_preferences_head_shape_chk" CHECK (
45
+ ("active_revision_id" IS NULL AND "active_revision" IS NULL AND "active_content_hash" IS NULL)
46
+ OR ("active_revision_id" IS NOT NULL AND "active_revision" > 0
47
+ AND "active_content_hash" ~ '^[0-9a-f]{64}$')
48
+ ),
49
+ CONSTRAINT "preference_registry_preferences_actor_chk" CHECK (
50
+ length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
51
+ ),
52
+ CONSTRAINT "preference_registry_preferences_account_id_uq" UNIQUE ("account_id", "id")
53
+ );
54
+
55
+ -- Stable keys identify one preference within a target, not across a tenant.
56
+ -- The same key may therefore layer at organization, workspace, and user tiers.
57
+ CREATE UNIQUE INDEX "preference_registry_preferences_organization_key_uq"
58
+ ON "preference_registry_preferences" ("account_id", "stable_key")
59
+ WHERE "scope" = 'organization';
60
+ CREATE UNIQUE INDEX "preference_registry_preferences_workspace_key_uq"
61
+ ON "preference_registry_preferences" ("account_id", "scope_workspace_id", "stable_key")
62
+ WHERE "scope" = 'workspace';
63
+ CREATE UNIQUE INDEX "preference_registry_preferences_user_key_uq"
64
+ ON "preference_registry_preferences" ("account_id", "scope_subject_id", "stable_key")
65
+ WHERE "scope" = 'user';
66
+
67
+ ALTER TABLE "preference_registry_preferences"
68
+ ADD CONSTRAINT "preference_registry_preferences_superseded_by_fk"
69
+ FOREIGN KEY ("account_id", "superseded_by_preference_id")
70
+ REFERENCES "preference_registry_preferences"("account_id", "id") ON DELETE RESTRICT;
71
+
72
+ CREATE INDEX "preference_registry_preferences_applicable_idx"
73
+ ON "preference_registry_preferences"
74
+ ("account_id", "scope", "scope_workspace_id", "scope_subject_id", "status", "stable_key");
75
+
76
+ CREATE TABLE "preference_registry_revisions" (
77
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
78
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
79
+ "preference_id" uuid NOT NULL,
80
+ "revision" bigint NOT NULL DEFAULT nextval('preference_registry_revision_seq'),
81
+ "title" text NOT NULL,
82
+ "description" text NOT NULL,
83
+ "content" text NOT NULL,
84
+ "content_hash" text NOT NULL,
85
+ "precedence_rank" integer NOT NULL DEFAULT 0,
86
+ "conflict_strategy" text NOT NULL,
87
+ "conflicts_with" jsonb NOT NULL DEFAULT '[]'::jsonb,
88
+ "provenance_source" text NOT NULL,
89
+ "provenance_source_id" text,
90
+ "trust" text NOT NULL,
91
+ "expires_at" timestamptz,
92
+ "corrects_revision_id" uuid REFERENCES "preference_registry_revisions"("id") ON DELETE RESTRICT,
93
+ "created_by_subject_id" text NOT NULL,
94
+ "created_at" timestamptz NOT NULL DEFAULT now(),
95
+ CONSTRAINT "preference_registry_revisions_preference_fk"
96
+ FOREIGN KEY ("account_id", "preference_id")
97
+ REFERENCES "preference_registry_preferences"("account_id", "id") ON DELETE RESTRICT,
98
+ CONSTRAINT "preference_registry_revisions_revision_chk" CHECK ("revision" > 0),
99
+ CONSTRAINT "preference_registry_revisions_text_chk" CHECK (
100
+ length(btrim("title")) BETWEEN 1 AND 120
101
+ AND length(btrim("description")) BETWEEN 1 AND 240
102
+ AND length(btrim("content")) > 0
103
+ AND length("content") <= 262144
104
+ ),
105
+ CONSTRAINT "preference_registry_revisions_hash_chk" CHECK (
106
+ "content_hash" ~ '^[0-9a-f]{64}$'
107
+ AND "content_hash" = encode(sha256(convert_to("content", 'UTF8')), 'hex')
108
+ ),
109
+ CONSTRAINT "preference_registry_revisions_precedence_chk" CHECK (
110
+ "precedence_rank" BETWEEN -1000 AND 1000
111
+ AND "conflict_strategy" IN ('override', 'merge', 'reject', 'inform')
112
+ AND jsonb_typeof("conflicts_with") = 'array'
113
+ AND jsonb_array_length("conflicts_with") <= 32
114
+ ),
115
+ CONSTRAINT "preference_registry_revisions_provenance_chk" CHECK (
116
+ "provenance_source" IN (
117
+ 'human', 'onboarding', 'knowledge_proposal', 'imported_document',
118
+ 'slack', 'meeting_transcript', 'call_transcript'
119
+ )
120
+ AND "trust" IN ('untrusted_proposal', 'personal', 'workspace_managed', 'organization_managed')
121
+ AND ("provenance_source_id" IS NULL OR length("provenance_source_id") BETWEEN 1 AND 512)
122
+ AND (
123
+ "provenance_source" IN ('human', 'onboarding')
124
+ OR "provenance_source_id" IS NOT NULL
125
+ )
126
+ ),
127
+ CONSTRAINT "preference_registry_revisions_actor_chk" CHECK (
128
+ length(btrim("created_by_subject_id")) BETWEEN 1 AND 1024
129
+ ),
130
+ CONSTRAINT "preference_registry_revisions_preference_revision_uq"
131
+ UNIQUE ("preference_id", "revision"),
132
+ CONSTRAINT "preference_registry_revisions_preference_id_uq"
133
+ UNIQUE ("preference_id", "id")
134
+ );
135
+
136
+ CREATE INDEX "preference_registry_revisions_history_idx"
137
+ ON "preference_registry_revisions" ("preference_id", "revision" DESC);
138
+
139
+ ALTER TABLE "preference_registry_preferences"
140
+ ADD CONSTRAINT "preference_registry_preferences_active_revision_fk"
141
+ FOREIGN KEY ("id", "active_revision_id")
142
+ REFERENCES "preference_registry_revisions"("preference_id", "id") ON DELETE RESTRICT
143
+ DEFERRABLE INITIALLY DEFERRED;
144
+
145
+ CREATE TABLE "preference_registry_events" (
146
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
147
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
148
+ "preference_id" uuid NOT NULL,
149
+ "type" text NOT NULL,
150
+ "version" integer NOT NULL,
151
+ "old_revision_id" uuid,
152
+ "new_revision_id" uuid,
153
+ "old_scope" text,
154
+ "old_workspace_id" uuid,
155
+ "old_subject_id" text,
156
+ "new_scope" text,
157
+ "new_workspace_id" uuid,
158
+ "new_subject_id" text,
159
+ "related_preference_id" uuid,
160
+ "actor_subject_id" text NOT NULL,
161
+ "reason" text NOT NULL,
162
+ "created_at" timestamptz NOT NULL DEFAULT now(),
163
+ CONSTRAINT "preference_registry_events_preference_fk"
164
+ FOREIGN KEY ("account_id", "preference_id")
165
+ REFERENCES "preference_registry_preferences"("account_id", "id") ON DELETE RESTRICT,
166
+ CONSTRAINT "preference_registry_events_old_revision_fk"
167
+ FOREIGN KEY ("preference_id", "old_revision_id")
168
+ REFERENCES "preference_registry_revisions"("preference_id", "id") ON DELETE RESTRICT,
169
+ CONSTRAINT "preference_registry_events_new_revision_fk"
170
+ FOREIGN KEY ("preference_id", "new_revision_id")
171
+ REFERENCES "preference_registry_revisions"("preference_id", "id") ON DELETE RESTRICT,
172
+ CONSTRAINT "preference_registry_events_related_fk"
173
+ FOREIGN KEY ("account_id", "related_preference_id")
174
+ REFERENCES "preference_registry_preferences"("account_id", "id") ON DELETE RESTRICT,
175
+ CONSTRAINT "preference_registry_events_type_chk" CHECK (
176
+ "type" IN (
177
+ 'proposal_created', 'activated', 'corrected', 'rejected',
178
+ 'deactivated', 'superseded', 'scope_changed'
179
+ )
180
+ ),
181
+ CONSTRAINT "preference_registry_events_version_chk" CHECK ("version" > 0),
182
+ CONSTRAINT "preference_registry_events_audit_chk" CHECK (
183
+ length(btrim("actor_subject_id")) BETWEEN 1 AND 1024
184
+ AND length(btrim("reason")) BETWEEN 1 AND 4096
185
+ ),
186
+ CONSTRAINT "preference_registry_events_shape_chk" CHECK (
187
+ (
188
+ "type" = 'proposal_created'
189
+ AND "old_revision_id" IS NULL AND "new_revision_id" IS NOT NULL
190
+ AND "old_scope" IS NULL AND "old_workspace_id" IS NULL AND "old_subject_id" IS NULL
191
+ AND "new_scope" IS NOT NULL AND "related_preference_id" IS NULL
192
+ AND (
193
+ ("new_scope" = 'organization' AND "new_workspace_id" IS NULL AND "new_subject_id" IS NULL)
194
+ OR ("new_scope" = 'workspace' AND "new_workspace_id" IS NOT NULL AND "new_subject_id" IS NULL)
195
+ OR ("new_scope" = 'user' AND "new_workspace_id" IS NULL AND "new_subject_id" IS NOT NULL)
196
+ )
197
+ ) OR (
198
+ "type" IN ('activated', 'corrected')
199
+ AND "new_revision_id" IS NOT NULL
200
+ AND "old_scope" IS NULL AND "old_workspace_id" IS NULL AND "old_subject_id" IS NULL
201
+ AND "new_scope" IS NULL AND "new_workspace_id" IS NULL AND "new_subject_id" IS NULL
202
+ AND "related_preference_id" IS NULL
203
+ AND ("type" <> 'corrected' OR "old_revision_id" IS NOT NULL)
204
+ ) OR (
205
+ "type" = 'rejected'
206
+ AND "old_revision_id" IS NULL AND "new_revision_id" IS NOT NULL
207
+ AND "old_scope" IS NULL AND "old_workspace_id" IS NULL AND "old_subject_id" IS NULL
208
+ AND "new_scope" IS NULL AND "new_workspace_id" IS NULL AND "new_subject_id" IS NULL
209
+ AND "related_preference_id" IS NULL
210
+ ) OR (
211
+ "type" = 'deactivated'
212
+ AND "old_revision_id" IS NOT NULL AND "new_revision_id" IS NULL
213
+ AND "old_scope" IS NULL AND "old_workspace_id" IS NULL AND "old_subject_id" IS NULL
214
+ AND "new_scope" IS NULL AND "new_workspace_id" IS NULL AND "new_subject_id" IS NULL
215
+ AND "related_preference_id" IS NULL
216
+ ) OR (
217
+ "type" = 'superseded'
218
+ AND "old_revision_id" IS NOT NULL AND "new_revision_id" IS NULL
219
+ AND "old_scope" IS NULL AND "old_workspace_id" IS NULL AND "old_subject_id" IS NULL
220
+ AND "new_scope" IS NULL AND "new_workspace_id" IS NULL AND "new_subject_id" IS NULL
221
+ AND "related_preference_id" IS NOT NULL
222
+ AND "related_preference_id" <> "preference_id"
223
+ ) OR (
224
+ "type" = 'scope_changed'
225
+ AND "old_revision_id" IS NULL AND "new_revision_id" IS NULL
226
+ AND "old_scope" IS NOT NULL AND "new_scope" IS NOT NULL AND "old_scope" <> "new_scope"
227
+ AND "related_preference_id" IS NULL
228
+ AND (
229
+ ("old_scope" = 'organization' AND "old_workspace_id" IS NULL AND "old_subject_id" IS NULL)
230
+ OR ("old_scope" = 'workspace' AND "old_workspace_id" IS NOT NULL AND "old_subject_id" IS NULL)
231
+ OR ("old_scope" = 'user' AND "old_workspace_id" IS NULL AND "old_subject_id" IS NOT NULL)
232
+ )
233
+ AND (
234
+ ("new_scope" = 'organization' AND "new_workspace_id" IS NULL AND "new_subject_id" IS NULL)
235
+ OR ("new_scope" = 'workspace' AND "new_workspace_id" IS NOT NULL AND "new_subject_id" IS NULL)
236
+ OR ("new_scope" = 'user' AND "new_workspace_id" IS NULL AND "new_subject_id" IS NOT NULL)
237
+ )
238
+ )
239
+ ),
240
+ CONSTRAINT "preference_registry_events_preference_version_uq" UNIQUE ("preference_id", "version")
241
+ );
242
+
243
+ CREATE INDEX "preference_registry_events_timeline_idx"
244
+ ON "preference_registry_events" ("preference_id", "created_at" DESC, "id");
245
+
246
+ CREATE TABLE "preference_registry_snapshots" (
247
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
248
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE RESTRICT,
249
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE RESTRICT,
250
+ "session_id" uuid NOT NULL REFERENCES "sessions"("id") ON DELETE RESTRICT,
251
+ "turn_id" uuid NOT NULL REFERENCES "session_turns"("id") ON DELETE RESTRICT,
252
+ "attempt_id" uuid NOT NULL REFERENCES "session_turn_attempts"("id") ON DELETE RESTRICT,
253
+ "execution_generation" integer NOT NULL,
254
+ "initiating_human_subject_id" text NOT NULL,
255
+ "descriptors" jsonb NOT NULL,
256
+ "descriptor_hash" text NOT NULL,
257
+ "truncated" boolean NOT NULL DEFAULT false,
258
+ "created_at" timestamptz NOT NULL DEFAULT now(),
259
+ CONSTRAINT "preference_registry_snapshots_human_chk" CHECK (
260
+ length(btrim("initiating_human_subject_id")) BETWEEN 1 AND 1024
261
+ AND "execution_generation" > 0
262
+ ),
263
+ CONSTRAINT "preference_registry_snapshots_descriptors_chk" CHECK (
264
+ jsonb_typeof("descriptors") = 'array' AND jsonb_array_length("descriptors") <= 64
265
+ ),
266
+ CONSTRAINT "preference_registry_snapshots_descriptor_bytes_chk" CHECK (
267
+ octet_length(convert_to("descriptors"::text, 'UTF8')) <= 16384
268
+ ),
269
+ CONSTRAINT "preference_registry_snapshots_hash_chk" CHECK (
270
+ "descriptor_hash" ~ '^[0-9a-f]{64}$'
271
+ AND "descriptor_hash" = encode(sha256(convert_to("descriptors"::text, 'UTF8')), 'hex')
272
+ ),
273
+ CONSTRAINT "preference_registry_snapshots_attempt_uq"
274
+ UNIQUE ("account_id", "workspace_id", "attempt_id")
275
+ );
276
+
277
+ CREATE INDEX "preference_registry_snapshots_human_timeline_idx"
278
+ ON "preference_registry_snapshots"
279
+ ("workspace_id", "initiating_human_subject_id", "created_at" DESC);
280
+
281
+ CREATE OR REPLACE FUNCTION preference_registry_validate_head()
282
+ RETURNS trigger LANGUAGE plpgsql AS $$
283
+ BEGIN
284
+ IF NEW.active_revision_id IS NOT NULL AND NOT EXISTS (
285
+ SELECT 1 FROM preference_registry_revisions revision
286
+ WHERE revision.id = NEW.active_revision_id
287
+ AND revision.preference_id = NEW.id
288
+ AND revision.account_id = NEW.account_id
289
+ AND revision.revision = NEW.active_revision
290
+ AND revision.content_hash = NEW.active_content_hash
291
+ ) THEN
292
+ RAISE EXCEPTION 'preference registry head must identify an exact immutable revision'
293
+ USING ERRCODE = '23514';
294
+ END IF;
295
+ RETURN NEW;
296
+ END;
297
+ $$;
298
+
299
+ CREATE TRIGGER preference_registry_preferences_validate_head
300
+ BEFORE INSERT OR UPDATE ON "preference_registry_preferences"
301
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_validate_head();
302
+
303
+ CREATE OR REPLACE FUNCTION preference_registry_guard_head_mutation()
304
+ RETURNS trigger LANGUAGE plpgsql AS $$
305
+ DECLARE lifecycle_operation text;
306
+ BEGIN
307
+ IF TG_OP = 'DELETE' THEN
308
+ RAISE EXCEPTION 'preference registry heads cannot be deleted'
309
+ USING ERRCODE = '55000';
310
+ END IF;
311
+ IF current_setting('opengeni.preference_lifecycle_head_id', true)
312
+ IS DISTINCT FROM NEW.id::text THEN
313
+ RAISE EXCEPTION 'preference registry heads change only through the lifecycle function'
314
+ USING ERRCODE = '55000';
315
+ END IF;
316
+ lifecycle_operation := current_setting('opengeni.preference_lifecycle_operation', true);
317
+ IF NEW.id IS DISTINCT FROM OLD.id
318
+ OR NEW.account_id IS DISTINCT FROM OLD.account_id
319
+ OR NEW.stable_key IS DISTINCT FROM OLD.stable_key
320
+ OR NEW.created_by_subject_id IS DISTINCT FROM OLD.created_by_subject_id
321
+ OR NEW.created_at IS DISTINCT FROM OLD.created_at
322
+ THEN
323
+ RAISE EXCEPTION 'preference registry stable identity is immutable'
324
+ USING ERRCODE = '55000';
325
+ END IF;
326
+ IF lifecycle_operation IN ('activate', 'correct') THEN
327
+ IF NEW.status <> 'active'
328
+ OR NEW.active_revision_id IS NULL
329
+ OR NEW.activation_version <> OLD.activation_version + 1
330
+ OR NEW.scope_version <> OLD.scope_version
331
+ OR ROW(NEW.scope, NEW.scope_workspace_id, NEW.scope_subject_id)
332
+ IS DISTINCT FROM ROW(OLD.scope, OLD.scope_workspace_id, OLD.scope_subject_id)
333
+ OR NEW.superseded_by_preference_id IS DISTINCT FROM OLD.superseded_by_preference_id
334
+ THEN
335
+ RAISE EXCEPTION 'invalid preference activation transition'
336
+ USING ERRCODE = '23514';
337
+ END IF;
338
+ ELSIF lifecycle_operation = 'reject' THEN
339
+ IF OLD.status <> 'proposed' OR NEW.status <> 'rejected'
340
+ OR NEW.scope_version <> OLD.scope_version
341
+ OR NEW.activation_version <> OLD.activation_version
342
+ OR ROW(NEW.scope, NEW.scope_workspace_id, NEW.scope_subject_id)
343
+ IS DISTINCT FROM ROW(OLD.scope, OLD.scope_workspace_id, OLD.scope_subject_id)
344
+ OR ROW(NEW.active_revision_id, NEW.active_revision, NEW.active_content_hash)
345
+ IS DISTINCT FROM ROW(OLD.active_revision_id, OLD.active_revision, OLD.active_content_hash)
346
+ OR NEW.superseded_by_preference_id IS DISTINCT FROM OLD.superseded_by_preference_id
347
+ THEN
348
+ RAISE EXCEPTION 'invalid preference rejection transition'
349
+ USING ERRCODE = '23514';
350
+ END IF;
351
+ ELSIF lifecycle_operation = 'deactivate' THEN
352
+ IF OLD.status <> 'active' OR NEW.status <> 'inactive'
353
+ OR OLD.active_revision_id IS NULL OR NEW.active_revision_id IS NOT NULL
354
+ OR NEW.active_revision IS NOT NULL OR NEW.active_content_hash IS NOT NULL
355
+ OR NEW.activation_version <> OLD.activation_version + 1
356
+ OR NEW.scope_version <> OLD.scope_version
357
+ OR ROW(NEW.scope, NEW.scope_workspace_id, NEW.scope_subject_id)
358
+ IS DISTINCT FROM ROW(OLD.scope, OLD.scope_workspace_id, OLD.scope_subject_id)
359
+ OR NEW.superseded_by_preference_id IS DISTINCT FROM OLD.superseded_by_preference_id
360
+ THEN
361
+ RAISE EXCEPTION 'invalid preference deactivation transition'
362
+ USING ERRCODE = '23514';
363
+ END IF;
364
+ ELSIF lifecycle_operation = 'scope' THEN
365
+ IF NEW.status IS DISTINCT FROM OLD.status
366
+ OR NEW.scope = OLD.scope
367
+ OR NEW.scope_version <> OLD.scope_version + 1
368
+ OR NEW.activation_version <> OLD.activation_version
369
+ OR ROW(NEW.active_revision_id, NEW.active_revision, NEW.active_content_hash)
370
+ IS DISTINCT FROM ROW(OLD.active_revision_id, OLD.active_revision, OLD.active_content_hash)
371
+ OR NEW.superseded_by_preference_id IS DISTINCT FROM OLD.superseded_by_preference_id
372
+ THEN
373
+ RAISE EXCEPTION 'invalid preference scope transition'
374
+ USING ERRCODE = '23514';
375
+ END IF;
376
+ ELSIF lifecycle_operation = 'supersede' THEN
377
+ IF OLD.status <> 'active' OR NEW.status <> 'superseded'
378
+ OR NEW.superseded_by_preference_id IS NULL
379
+ OR NEW.superseded_by_preference_id = OLD.id
380
+ OR NEW.scope_version <> OLD.scope_version
381
+ OR NEW.activation_version <> OLD.activation_version
382
+ OR ROW(NEW.scope, NEW.scope_workspace_id, NEW.scope_subject_id)
383
+ IS DISTINCT FROM ROW(OLD.scope, OLD.scope_workspace_id, OLD.scope_subject_id)
384
+ OR ROW(NEW.active_revision_id, NEW.active_revision, NEW.active_content_hash)
385
+ IS DISTINCT FROM ROW(OLD.active_revision_id, OLD.active_revision, OLD.active_content_hash)
386
+ THEN
387
+ RAISE EXCEPTION 'invalid preference supersession transition'
388
+ USING ERRCODE = '23514';
389
+ END IF;
390
+ ELSE
391
+ RAISE EXCEPTION 'preference registry lifecycle operation is invalid'
392
+ USING ERRCODE = '23514';
393
+ END IF;
394
+ RETURN NEW;
395
+ END;
396
+ $$;
397
+
398
+ CREATE TRIGGER preference_registry_preferences_lifecycle_only
399
+ BEFORE UPDATE OR DELETE ON "preference_registry_preferences"
400
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_guard_head_mutation();
401
+
402
+ CREATE OR REPLACE FUNCTION preference_registry_require_proposal_event()
403
+ RETURNS trigger LANGUAGE plpgsql AS $$
404
+ BEGIN
405
+ IF NOT EXISTS (
406
+ SELECT 1
407
+ FROM preference_registry_events event
408
+ WHERE event.account_id = NEW.account_id
409
+ AND event.preference_id = NEW.id
410
+ AND event.type = 'proposal_created'
411
+ AND event.version = 1
412
+ AND event.old_revision_id IS NULL
413
+ AND event.new_revision_id IS NOT NULL
414
+ AND event.old_scope IS NULL
415
+ AND event.new_scope = NEW.scope
416
+ AND event.new_workspace_id IS NOT DISTINCT FROM NEW.scope_workspace_id
417
+ AND event.new_subject_id IS NOT DISTINCT FROM NEW.scope_subject_id
418
+ ) THEN
419
+ RAISE EXCEPTION 'preference proposal requires an exact immutable creation event'
420
+ USING ERRCODE = '23514';
421
+ END IF;
422
+ RETURN NULL;
423
+ END;
424
+ $$;
425
+
426
+ CREATE CONSTRAINT TRIGGER preference_registry_preferences_require_proposal_event
427
+ AFTER INSERT ON "preference_registry_preferences"
428
+ DEFERRABLE INITIALLY DEFERRED
429
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_require_proposal_event();
430
+
431
+ CREATE OR REPLACE FUNCTION preference_registry_validate_snapshot()
432
+ RETURNS trigger LANGUAGE plpgsql AS $$
433
+ BEGIN
434
+ IF NOT EXISTS (
435
+ SELECT 1
436
+ FROM session_turn_attempts attempt
437
+ JOIN session_turns turn
438
+ ON turn.account_id = attempt.account_id
439
+ AND turn.workspace_id = attempt.workspace_id
440
+ AND turn.session_id = attempt.session_id
441
+ AND turn.id = attempt.turn_id
442
+ JOIN sessions session
443
+ ON session.account_id = attempt.account_id
444
+ AND session.workspace_id = attempt.workspace_id
445
+ AND session.id = attempt.session_id
446
+ WHERE attempt.id = NEW.attempt_id
447
+ AND attempt.account_id = NEW.account_id
448
+ AND attempt.workspace_id = NEW.workspace_id
449
+ AND attempt.session_id = NEW.session_id
450
+ AND attempt.turn_id = NEW.turn_id
451
+ AND attempt.execution_generation = NEW.execution_generation
452
+ AND turn.execution_generation = NEW.execution_generation
453
+ AND attempt.state IN ('claimed', 'running')
454
+ AND turn.active_attempt_id = attempt.id
455
+ AND session.active_turn_id = turn.id
456
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
457
+ AND turn.initiator_kind = 'subject'
458
+ AND turn.initiator_subject_id = NEW.initiating_human_subject_id
459
+ AND NOT EXISTS (
460
+ SELECT 1 FROM session_attempt_interruptions interruption
461
+ WHERE interruption.workspace_id = NEW.workspace_id
462
+ AND interruption.attempt_id = NEW.attempt_id
463
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
464
+ )
465
+ ) THEN
466
+ RAISE EXCEPTION 'preference snapshot requires an exact active human-initiated attempt'
467
+ USING ERRCODE = '23514';
468
+ END IF;
469
+
470
+ IF jsonb_typeof(NEW.descriptors) <> 'array' THEN
471
+ RAISE EXCEPTION 'preference snapshot descriptors must be an array'
472
+ USING ERRCODE = '23514';
473
+ END IF;
474
+
475
+ IF (
476
+ SELECT count(*) <> count(DISTINCT descriptor->>'id')
477
+ FROM jsonb_array_elements(NEW.descriptors) descriptor
478
+ ) THEN
479
+ RAISE EXCEPTION 'preference snapshot descriptors must identify unique preferences'
480
+ USING ERRCODE = '23514';
481
+ END IF;
482
+
483
+ IF EXISTS (
484
+ SELECT 1
485
+ FROM jsonb_array_elements(NEW.descriptors) descriptor
486
+ WHERE jsonb_typeof(descriptor) <> 'object'
487
+ OR NOT EXISTS (
488
+ SELECT 1
489
+ FROM preference_registry_preferences preference
490
+ JOIN preference_registry_revisions revision
491
+ ON revision.account_id = preference.account_id
492
+ AND revision.preference_id = preference.id
493
+ AND revision.id = preference.active_revision_id
494
+ WHERE preference.account_id = NEW.account_id
495
+ AND preference.id::text = descriptor->>'id'
496
+ AND preference.stable_key = descriptor->>'stableKey'
497
+ AND preference.scope = descriptor->>'scope'
498
+ AND preference.status = 'active'
499
+ AND preference.activation_version::text = descriptor->>'activeVersion'
500
+ AND revision.id::text = descriptor->>'revisionId'
501
+ AND revision.content_hash = descriptor->>'contentHash'
502
+ AND (revision.expires_at IS NULL OR revision.expires_at > NEW.created_at)
503
+ AND opengeni_private.preference_registry_scope_visible(
504
+ preference.account_id,
505
+ preference.scope,
506
+ preference.scope_workspace_id,
507
+ preference.scope_subject_id
508
+ )
509
+ AND descriptor->>'retrievalHandle' =
510
+ 'preference://' || preference.id::text || '/revisions/' || revision.id::text ||
511
+ '?sha256=' || revision.content_hash
512
+ AND descriptor = jsonb_build_object(
513
+ 'id', preference.id::text,
514
+ 'stableKey', preference.stable_key,
515
+ 'title', revision.title,
516
+ 'description', revision.description,
517
+ 'scope', preference.scope,
518
+ 'activeVersion', preference.activation_version,
519
+ 'revisionId', revision.id::text,
520
+ 'contentHash', revision.content_hash,
521
+ 'precedence', jsonb_build_object(
522
+ 'tier', preference.scope,
523
+ 'rank', revision.precedence_rank,
524
+ 'conflictStrategy', revision.conflict_strategy,
525
+ 'conflictsWith', revision.conflicts_with
526
+ ),
527
+ 'provenance', jsonb_build_object(
528
+ 'source', revision.provenance_source,
529
+ 'sourceIdHash', CASE
530
+ WHEN revision.provenance_source_id IS NULL THEN NULL
531
+ ELSE encode(
532
+ sha256(convert_to(revision.provenance_source_id, 'UTF8')),
533
+ 'hex'
534
+ )
535
+ END,
536
+ 'trust', revision.trust
537
+ ),
538
+ 'expiresAt', CASE
539
+ WHEN revision.expires_at IS NULL THEN NULL
540
+ ELSE to_char(
541
+ revision.expires_at AT TIME ZONE 'UTC',
542
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
543
+ )
544
+ END,
545
+ 'retrievalHandle',
546
+ 'preference://' || preference.id::text || '/revisions/' || revision.id::text ||
547
+ '?sha256=' || revision.content_hash
548
+ )
549
+ )
550
+ ) THEN
551
+ RAISE EXCEPTION 'preference snapshot contains a non-visible or inexact descriptor'
552
+ USING ERRCODE = '23514';
553
+ END IF;
554
+
555
+ RETURN NEW;
556
+ END;
557
+ $$;
558
+
559
+ CREATE TRIGGER preference_registry_snapshots_validate
560
+ BEFORE INSERT ON "preference_registry_snapshots"
561
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_validate_snapshot();
562
+
563
+ CREATE OR REPLACE FUNCTION preference_registry_reject_history_mutation()
564
+ RETURNS trigger LANGUAGE plpgsql AS $$
565
+ BEGIN
566
+ RAISE EXCEPTION 'preference registry history is immutable' USING ERRCODE = '55000';
567
+ END;
568
+ $$;
569
+
570
+ CREATE TRIGGER preference_registry_revisions_immutable
571
+ BEFORE UPDATE OR DELETE ON "preference_registry_revisions"
572
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_reject_history_mutation();
573
+ CREATE TRIGGER preference_registry_events_immutable
574
+ BEFORE UPDATE OR DELETE ON "preference_registry_events"
575
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_reject_history_mutation();
576
+ CREATE TRIGGER preference_registry_snapshots_immutable
577
+ BEFORE UPDATE OR DELETE ON "preference_registry_snapshots"
578
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_reject_history_mutation();
579
+
580
+ CREATE OR REPLACE FUNCTION opengeni_private.preference_registry_scope_visible(
581
+ row_account_id uuid,
582
+ row_scope text,
583
+ row_workspace_id uuid,
584
+ row_subject_id text
585
+ ) RETURNS boolean LANGUAGE sql STABLE AS $$
586
+ SELECT row_account_id = opengeni_private.current_account_id()
587
+ AND (
588
+ row_scope = 'organization'
589
+ OR (row_scope = 'workspace' AND row_workspace_id = opengeni_private.current_workspace_id())
590
+ OR (row_scope = 'user' AND row_subject_id = opengeni_private.current_subject_id())
591
+ );
592
+ $$;
593
+
594
+ DO $snapshot_function$
595
+ DECLARE target_schema text := current_schema();
596
+ BEGIN
597
+ EXECUTE format($ddl$
598
+ CREATE OR REPLACE FUNCTION %I.preference_registry_get_or_create_snapshot(
599
+ p_account_id uuid,
600
+ p_workspace_id uuid,
601
+ p_session_id uuid,
602
+ p_turn_id uuid,
603
+ p_attempt_id uuid,
604
+ p_execution_generation integer
605
+ ) RETURNS SETOF %I.preference_registry_snapshots
606
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
607
+ AS $body$
608
+ DECLARE
609
+ context_account_id uuid;
610
+ context_workspace_id uuid;
611
+ authority_subject_id text;
612
+ snapshot_time timestamptz := transaction_timestamp();
613
+ winner_id uuid;
614
+ canonical_descriptor jsonb;
615
+ canonical_descriptors jsonb := '[]'::jsonb;
616
+ candidate_descriptors jsonb;
617
+ canonical_hash text;
618
+ canonical_truncated boolean := false;
619
+ BEGIN
620
+ context_account_id := NULLIF(
621
+ current_setting('opengeni.account_id', true), ''
622
+ )::uuid;
623
+ context_workspace_id := NULLIF(
624
+ current_setting('opengeni.workspace_id', true), ''
625
+ )::uuid;
626
+ IF context_account_id IS DISTINCT FROM p_account_id
627
+ OR context_workspace_id IS DISTINCT FROM p_workspace_id
628
+ OR p_execution_generation < 1
629
+ THEN
630
+ RAISE EXCEPTION 'preference snapshot requires exact transaction-local tenant authority'
631
+ USING ERRCODE = '42501';
632
+ END IF;
633
+
634
+ SELECT turn.initiator_subject_id INTO authority_subject_id
635
+ FROM workspaces workspace
636
+ JOIN sessions session
637
+ ON session.account_id = workspace.account_id
638
+ AND session.workspace_id = workspace.id
639
+ JOIN session_turns turn
640
+ ON turn.account_id = session.account_id
641
+ AND turn.workspace_id = session.workspace_id
642
+ AND turn.session_id = session.id
643
+ JOIN session_turn_attempts attempt
644
+ ON attempt.account_id = turn.account_id
645
+ AND attempt.workspace_id = turn.workspace_id
646
+ AND attempt.session_id = turn.session_id
647
+ AND attempt.turn_id = turn.id
648
+ WHERE workspace.id = p_workspace_id
649
+ AND workspace.account_id = p_account_id
650
+ AND session.id = p_session_id
651
+ AND session.active_turn_id = p_turn_id
652
+ AND turn.id = p_turn_id
653
+ AND turn.active_attempt_id = p_attempt_id
654
+ AND turn.execution_generation = p_execution_generation
655
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
656
+ AND turn.initiator_kind = 'subject'
657
+ AND length(btrim(turn.initiator_subject_id)) BETWEEN 1 AND 1024
658
+ AND attempt.id = p_attempt_id
659
+ AND attempt.execution_generation = p_execution_generation
660
+ AND attempt.state IN ('claimed', 'running')
661
+ AND NOT EXISTS (
662
+ SELECT 1
663
+ FROM session_attempt_interruptions interruption
664
+ WHERE interruption.workspace_id = attempt.workspace_id
665
+ AND interruption.attempt_id = attempt.id
666
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
667
+ )
668
+ FOR KEY SHARE OF workspace
669
+ FOR SHARE OF session, turn
670
+ FOR UPDATE OF attempt;
671
+ IF authority_subject_id IS NULL THEN
672
+ RAISE EXCEPTION 'preference snapshot requires the exact current human attempt'
673
+ USING ERRCODE = '42501';
674
+ END IF;
675
+ PERFORM set_config('opengeni.subject_id', authority_subject_id, true);
676
+ IF NULLIF(current_setting('opengeni.subject_id', true), '')
677
+ IS DISTINCT FROM authority_subject_id
678
+ THEN
679
+ RAISE EXCEPTION 'preference snapshot human authority was not applied'
680
+ USING ERRCODE = '42501';
681
+ END IF;
682
+
683
+ SELECT snapshot.id INTO winner_id
684
+ FROM preference_registry_snapshots snapshot
685
+ WHERE snapshot.account_id = p_account_id
686
+ AND snapshot.workspace_id = p_workspace_id
687
+ AND snapshot.session_id = p_session_id
688
+ AND snapshot.turn_id = p_turn_id
689
+ AND snapshot.attempt_id = p_attempt_id
690
+ AND snapshot.execution_generation = p_execution_generation
691
+ AND snapshot.initiating_human_subject_id = authority_subject_id
692
+ FOR SHARE;
693
+ IF winner_id IS NOT NULL THEN
694
+ IF EXISTS (
695
+ SELECT 1
696
+ FROM preference_registry_snapshots snapshot
697
+ WHERE snapshot.id = winner_id
698
+ AND (
699
+ snapshot.created_at > transaction_timestamp()
700
+ OR jsonb_typeof(snapshot.descriptors) <> 'array'
701
+ OR jsonb_array_length(snapshot.descriptors) > 64
702
+ OR octet_length(convert_to(snapshot.descriptors::text, 'UTF8')) > 16384
703
+ OR snapshot.descriptor_hash IS DISTINCT FROM encode(
704
+ sha256(convert_to(snapshot.descriptors::text, 'UTF8')), 'hex'
705
+ )
706
+ OR (
707
+ SELECT count(*) <> count(DISTINCT descriptor->>'id')
708
+ FROM jsonb_array_elements(snapshot.descriptors) descriptor
709
+ )
710
+ )
711
+ ) THEN
712
+ RAISE EXCEPTION 'existing preference snapshot winner failed canonical integrity checks'
713
+ USING ERRCODE = '23514';
714
+ END IF;
715
+ RETURN QUERY
716
+ SELECT snapshot.*
717
+ FROM preference_registry_snapshots snapshot
718
+ WHERE snapshot.id = winner_id;
719
+ RETURN;
720
+ END IF;
721
+
722
+ FOR canonical_descriptor IN
723
+ SELECT jsonb_build_object(
724
+ 'id', preference.id::text,
725
+ 'stableKey', preference.stable_key,
726
+ 'title', revision.title,
727
+ 'description', revision.description,
728
+ 'scope', preference.scope,
729
+ 'activeVersion', preference.activation_version,
730
+ 'revisionId', revision.id::text,
731
+ 'contentHash', revision.content_hash,
732
+ 'precedence', jsonb_build_object(
733
+ 'tier', preference.scope,
734
+ 'rank', revision.precedence_rank,
735
+ 'conflictStrategy', revision.conflict_strategy,
736
+ 'conflictsWith', revision.conflicts_with
737
+ ),
738
+ 'provenance', jsonb_build_object(
739
+ 'source', revision.provenance_source,
740
+ 'sourceIdHash', CASE
741
+ WHEN revision.provenance_source_id IS NULL THEN NULL
742
+ ELSE encode(
743
+ sha256(convert_to(revision.provenance_source_id, 'UTF8')),
744
+ 'hex'
745
+ )
746
+ END,
747
+ 'trust', revision.trust
748
+ ),
749
+ 'expiresAt', CASE
750
+ WHEN revision.expires_at IS NULL THEN NULL
751
+ ELSE to_char(
752
+ revision.expires_at AT TIME ZONE 'UTC',
753
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
754
+ )
755
+ END,
756
+ 'retrievalHandle',
757
+ 'preference://' || preference.id::text || '/revisions/' || revision.id::text ||
758
+ '?sha256=' || revision.content_hash
759
+ )
760
+ FROM preference_registry_preferences preference
761
+ JOIN preference_registry_revisions revision
762
+ ON revision.account_id = preference.account_id
763
+ AND revision.preference_id = preference.id
764
+ AND revision.id = preference.active_revision_id
765
+ WHERE preference.account_id = p_account_id
766
+ AND preference.status = 'active'
767
+ AND (revision.expires_at IS NULL OR revision.expires_at > snapshot_time)
768
+ AND opengeni_private.preference_registry_scope_visible(
769
+ preference.account_id,
770
+ preference.scope,
771
+ preference.scope_workspace_id,
772
+ preference.scope_subject_id
773
+ )
774
+ ORDER BY CASE preference.scope
775
+ WHEN 'organization' THEN 0
776
+ WHEN 'workspace' THEN 1
777
+ WHEN 'user' THEN 2
778
+ ELSE 3
779
+ END,
780
+ revision.precedence_rank DESC,
781
+ preference.stable_key,
782
+ preference.id
783
+ FOR SHARE OF preference
784
+ LOOP
785
+ IF jsonb_array_length(canonical_descriptors) >= 64 THEN
786
+ canonical_truncated := true;
787
+ EXIT;
788
+ END IF;
789
+ candidate_descriptors := canonical_descriptors || jsonb_build_array(canonical_descriptor);
790
+ IF octet_length(convert_to(candidate_descriptors::text, 'UTF8')) > 16384 THEN
791
+ canonical_truncated := true;
792
+ EXIT;
793
+ END IF;
794
+ canonical_descriptors := candidate_descriptors;
795
+ END LOOP;
796
+ canonical_hash := encode(
797
+ sha256(convert_to(canonical_descriptors::text, 'UTF8')), 'hex'
798
+ );
799
+
800
+ INSERT INTO preference_registry_snapshots (
801
+ account_id, workspace_id, session_id, turn_id, attempt_id,
802
+ execution_generation, initiating_human_subject_id,
803
+ descriptors, descriptor_hash, truncated, created_at
804
+ ) VALUES (
805
+ p_account_id, p_workspace_id, p_session_id, p_turn_id, p_attempt_id,
806
+ p_execution_generation, authority_subject_id,
807
+ canonical_descriptors, canonical_hash, canonical_truncated, snapshot_time
808
+ )
809
+ ON CONFLICT (account_id, workspace_id, attempt_id) DO NOTHING
810
+ RETURNING id INTO winner_id;
811
+ IF winner_id IS NULL THEN
812
+ SELECT snapshot.id INTO winner_id
813
+ FROM preference_registry_snapshots snapshot
814
+ WHERE snapshot.account_id = p_account_id
815
+ AND snapshot.workspace_id = p_workspace_id
816
+ AND snapshot.session_id = p_session_id
817
+ AND snapshot.turn_id = p_turn_id
818
+ AND snapshot.attempt_id = p_attempt_id
819
+ AND snapshot.execution_generation = p_execution_generation
820
+ AND snapshot.initiating_human_subject_id = authority_subject_id
821
+ FOR SHARE;
822
+ END IF;
823
+ IF winner_id IS NULL THEN
824
+ RAISE EXCEPTION 'preference snapshot winner conflicts with exact attempt authority'
825
+ USING ERRCODE = '40001';
826
+ END IF;
827
+ IF EXISTS (
828
+ SELECT 1
829
+ FROM preference_registry_snapshots snapshot
830
+ WHERE snapshot.id = winner_id
831
+ AND (
832
+ snapshot.descriptors IS DISTINCT FROM canonical_descriptors
833
+ OR snapshot.descriptor_hash IS DISTINCT FROM canonical_hash
834
+ OR snapshot.truncated IS DISTINCT FROM canonical_truncated
835
+ OR snapshot.created_at IS DISTINCT FROM snapshot_time
836
+ )
837
+ ) THEN
838
+ RAISE EXCEPTION 'preference snapshot winner is not the canonical locked snapshot'
839
+ USING ERRCODE = '40001';
840
+ END IF;
841
+ RETURN QUERY
842
+ SELECT snapshot.*
843
+ FROM preference_registry_snapshots snapshot
844
+ WHERE snapshot.id = winner_id;
845
+ END
846
+ $body$
847
+ $ddl$, target_schema, target_schema, target_schema);
848
+ EXECUTE format(
849
+ 'REVOKE ALL ON FUNCTION %I.preference_registry_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) FROM PUBLIC',
850
+ target_schema
851
+ );
852
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
853
+ EXECUTE format(
854
+ 'GRANT EXECUTE ON FUNCTION %I.preference_registry_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) TO opengeni_app',
855
+ target_schema
856
+ );
857
+ END IF;
858
+ END $snapshot_function$;
859
+
860
+ DO $lock_function$
861
+ DECLARE target_schema text := current_schema();
862
+ BEGIN
863
+ EXECUTE format($ddl$
864
+ CREATE OR REPLACE FUNCTION %I.preference_registry_lock_heads(p_preference_ids uuid[])
865
+ RETURNS TABLE (preference_id uuid)
866
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
867
+ AS $body$
868
+ DECLARE
869
+ context_account_id uuid;
870
+ context_workspace_id uuid;
871
+ context_subject_id text;
872
+ requested_count integer;
873
+ locked_count integer;
874
+ BEGIN
875
+ context_account_id := NULLIF(
876
+ current_setting('opengeni.account_id', true), ''
877
+ )::uuid;
878
+ context_workspace_id := NULLIF(
879
+ current_setting('opengeni.workspace_id', true), ''
880
+ )::uuid;
881
+ context_subject_id := NULLIF(
882
+ current_setting('opengeni.subject_id', true), ''
883
+ );
884
+ SELECT count(DISTINCT requested_id) INTO requested_count
885
+ FROM unnest(p_preference_ids) requested_id;
886
+ IF context_account_id IS NULL OR context_workspace_id IS NULL
887
+ OR context_subject_id IS NULL
888
+ OR requested_count < 1 OR requested_count > 2
889
+ OR requested_count <> cardinality(p_preference_ids)
890
+ THEN
891
+ RAISE EXCEPTION 'preference head lock requires exact transaction-local scope'
892
+ USING ERRCODE = '42501';
893
+ END IF;
894
+ PERFORM 1
895
+ FROM preference_registry_preferences candidate
896
+ WHERE candidate.id = ANY(p_preference_ids)
897
+ AND candidate.account_id = context_account_id
898
+ AND opengeni_private.preference_registry_scope_visible(
899
+ candidate.account_id,
900
+ candidate.scope,
901
+ candidate.scope_workspace_id,
902
+ candidate.scope_subject_id
903
+ )
904
+ ORDER BY candidate.id
905
+ FOR UPDATE;
906
+ GET DIAGNOSTICS locked_count = ROW_COUNT;
907
+ IF locked_count <> requested_count THEN
908
+ RAISE EXCEPTION 'preference head was not found in the authorized scope'
909
+ USING ERRCODE = '42501';
910
+ END IF;
911
+ RETURN QUERY
912
+ SELECT candidate.id
913
+ FROM preference_registry_preferences candidate
914
+ WHERE candidate.id = ANY(p_preference_ids)
915
+ ORDER BY candidate.id;
916
+ END
917
+ $body$
918
+ $ddl$, target_schema, target_schema);
919
+ EXECUTE format(
920
+ 'REVOKE ALL ON FUNCTION %I.preference_registry_lock_heads(uuid[]) FROM PUBLIC',
921
+ target_schema
922
+ );
923
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
924
+ EXECUTE format(
925
+ 'GRANT EXECUTE ON FUNCTION %I.preference_registry_lock_heads(uuid[]) TO opengeni_app',
926
+ target_schema
927
+ );
928
+ END IF;
929
+ END $lock_function$;
930
+
931
+ DO $lifecycle_function$
932
+ DECLARE target_schema text := current_schema();
933
+ BEGIN
934
+ EXECUTE format($ddl$
935
+ CREATE OR REPLACE FUNCTION %I.preference_registry_apply_lifecycle(
936
+ p_operation text,
937
+ p_preference_id uuid,
938
+ p_expected_scope_version integer,
939
+ p_expected_revision_id uuid,
940
+ p_revision_id uuid,
941
+ p_new_scope text,
942
+ p_related_preference_id uuid,
943
+ p_actor_subject_id text,
944
+ p_reason text
945
+ ) RETURNS TABLE (event_id uuid)
946
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
947
+ AS $body$
948
+ DECLARE
949
+ preference record;
950
+ replacement record;
951
+ replacement_revision record;
952
+ revision record;
953
+ context_account_id uuid;
954
+ context_workspace_id uuid;
955
+ context_subject_id text;
956
+ context_principal_kind text;
957
+ next_event_version integer;
958
+ event_type text;
959
+ old_revision_id uuid;
960
+ new_revision_id uuid;
961
+ old_scope text;
962
+ old_workspace_id uuid;
963
+ old_subject_id text;
964
+ new_scope text;
965
+ new_workspace_id uuid;
966
+ new_subject_id text;
967
+ related_preference_id uuid;
968
+ lifecycle_lock_count integer;
969
+ BEGIN
970
+ context_account_id := NULLIF(
971
+ current_setting('opengeni.account_id', true), ''
972
+ )::uuid;
973
+ context_workspace_id := NULLIF(
974
+ current_setting('opengeni.workspace_id', true), ''
975
+ )::uuid;
976
+ context_subject_id := NULLIF(
977
+ current_setting('opengeni.subject_id', true), ''
978
+ );
979
+ context_principal_kind := NULLIF(
980
+ current_setting('opengeni.principal_kind', true), ''
981
+ );
982
+ IF context_account_id IS NULL OR context_workspace_id IS NULL
983
+ OR context_subject_id IS NULL
984
+ OR context_principal_kind IS DISTINCT FROM 'human_session'
985
+ OR p_actor_subject_id IS DISTINCT FROM context_subject_id
986
+ THEN
987
+ RAISE EXCEPTION 'preference lifecycle requires exact transaction-local actor authority'
988
+ USING ERRCODE = '42501';
989
+ END IF;
990
+ IF length(btrim(p_actor_subject_id)) NOT BETWEEN 1 AND 1024
991
+ OR length(btrim(p_reason)) NOT BETWEEN 1 AND 4096
992
+ THEN
993
+ RAISE EXCEPTION 'preference lifecycle actor and reason are invalid'
994
+ USING ERRCODE = '22023';
995
+ END IF;
996
+
997
+ IF p_operation = 'supersede' THEN
998
+ IF p_related_preference_id IS NULL OR p_related_preference_id = p_preference_id THEN
999
+ RAISE EXCEPTION 'preference supersession requires a distinct replacement'
1000
+ USING ERRCODE = '22023';
1001
+ END IF;
1002
+ PERFORM 1
1003
+ FROM preference_registry_preferences candidate
1004
+ WHERE candidate.id IN (p_preference_id, p_related_preference_id)
1005
+ AND candidate.account_id = context_account_id
1006
+ AND opengeni_private.preference_registry_scope_visible(
1007
+ candidate.account_id,
1008
+ candidate.scope,
1009
+ candidate.scope_workspace_id,
1010
+ candidate.scope_subject_id
1011
+ )
1012
+ ORDER BY candidate.id
1013
+ FOR UPDATE;
1014
+ GET DIAGNOSTICS lifecycle_lock_count = ROW_COUNT;
1015
+ IF lifecycle_lock_count <> 2 THEN
1016
+ RAISE EXCEPTION 'preference supersession heads were not found in the authorized scope'
1017
+ USING ERRCODE = '42501';
1018
+ END IF;
1019
+ ELSE
1020
+ PERFORM 1
1021
+ FROM preference_registry_preferences candidate
1022
+ WHERE candidate.id = p_preference_id
1023
+ AND candidate.account_id = context_account_id
1024
+ AND opengeni_private.preference_registry_scope_visible(
1025
+ candidate.account_id,
1026
+ candidate.scope,
1027
+ candidate.scope_workspace_id,
1028
+ candidate.scope_subject_id
1029
+ )
1030
+ FOR UPDATE;
1031
+ GET DIAGNOSTICS lifecycle_lock_count = ROW_COUNT;
1032
+ IF lifecycle_lock_count <> 1 THEN
1033
+ RAISE EXCEPTION 'preference lifecycle head was not found in the authorized scope'
1034
+ USING ERRCODE = '42501';
1035
+ END IF;
1036
+ END IF;
1037
+
1038
+ SELECT * INTO preference
1039
+ FROM preference_registry_preferences candidate
1040
+ WHERE candidate.id = p_preference_id
1041
+ AND candidate.account_id = context_account_id;
1042
+ IF NOT FOUND OR NOT opengeni_private.preference_registry_scope_visible(
1043
+ preference.account_id,
1044
+ preference.scope,
1045
+ preference.scope_workspace_id,
1046
+ preference.scope_subject_id
1047
+ ) THEN
1048
+ RAISE EXCEPTION 'preference was not found in the authorized scope'
1049
+ USING ERRCODE = '42501';
1050
+ END IF;
1051
+ IF preference.scope_version IS DISTINCT FROM p_expected_scope_version THEN
1052
+ RAISE EXCEPTION 'preference scope version changed'
1053
+ USING ERRCODE = '40001';
1054
+ END IF;
1055
+ IF preference.active_revision_id IS DISTINCT FROM p_expected_revision_id THEN
1056
+ RAISE EXCEPTION 'preference active revision changed'
1057
+ USING ERRCODE = '40001';
1058
+ END IF;
1059
+ PERFORM set_config(
1060
+ 'opengeni.preference_lifecycle_head_id', preference.id::text, true
1061
+ );
1062
+ PERFORM set_config(
1063
+ 'opengeni.preference_lifecycle_operation', p_operation, true
1064
+ );
1065
+
1066
+ IF p_operation IN ('proposal_created', 'activate', 'correct', 'reject') THEN
1067
+ SELECT * INTO revision
1068
+ FROM preference_registry_revisions candidate
1069
+ WHERE candidate.id = p_revision_id
1070
+ AND candidate.preference_id = preference.id
1071
+ AND candidate.account_id = preference.account_id;
1072
+ IF NOT FOUND THEN
1073
+ RAISE EXCEPTION 'preference lifecycle revision was not found'
1074
+ USING ERRCODE = '23503';
1075
+ END IF;
1076
+ END IF;
1077
+
1078
+ IF p_operation = 'proposal_created' THEN
1079
+ IF preference.status <> 'proposed'
1080
+ OR preference.active_revision_id IS NOT NULL
1081
+ OR EXISTS (
1082
+ SELECT 1 FROM preference_registry_events event
1083
+ WHERE event.preference_id = preference.id
1084
+ )
1085
+ THEN
1086
+ RAISE EXCEPTION 'proposal creation event requires a new inactive proposal'
1087
+ USING ERRCODE = '23514';
1088
+ END IF;
1089
+ event_type := 'proposal_created';
1090
+ new_revision_id := revision.id;
1091
+ new_scope := preference.scope;
1092
+ new_workspace_id := preference.scope_workspace_id;
1093
+ new_subject_id := preference.scope_subject_id;
1094
+ ELSIF p_operation = 'activate' THEN
1095
+ IF preference.status IN ('rejected', 'superseded')
1096
+ OR revision.id IS NOT DISTINCT FROM preference.active_revision_id
1097
+ THEN
1098
+ RAISE EXCEPTION 'preference cannot activate the requested revision'
1099
+ USING ERRCODE = '23514';
1100
+ END IF;
1101
+ UPDATE preference_registry_preferences
1102
+ SET status = 'active',
1103
+ active_revision_id = revision.id,
1104
+ active_revision = revision.revision,
1105
+ active_content_hash = revision.content_hash,
1106
+ activation_version = activation_version + 1,
1107
+ updated_at = clock_timestamp()
1108
+ WHERE id = preference.id;
1109
+ event_type := 'activated';
1110
+ old_revision_id := preference.active_revision_id;
1111
+ new_revision_id := revision.id;
1112
+ ELSIF p_operation = 'correct' THEN
1113
+ IF preference.status IN ('rejected', 'superseded')
1114
+ OR preference.active_revision_id IS NULL
1115
+ OR revision.corrects_revision_id IS DISTINCT FROM preference.active_revision_id
1116
+ OR revision.provenance_source <> 'human'
1117
+ THEN
1118
+ RAISE EXCEPTION 'preference correction does not identify the active immutable revision'
1119
+ USING ERRCODE = '23514';
1120
+ END IF;
1121
+ UPDATE preference_registry_preferences
1122
+ SET status = 'active',
1123
+ active_revision_id = revision.id,
1124
+ active_revision = revision.revision,
1125
+ active_content_hash = revision.content_hash,
1126
+ activation_version = activation_version + 1,
1127
+ updated_at = clock_timestamp()
1128
+ WHERE id = preference.id;
1129
+ event_type := 'corrected';
1130
+ old_revision_id := preference.active_revision_id;
1131
+ new_revision_id := revision.id;
1132
+ ELSIF p_operation = 'reject' THEN
1133
+ IF preference.status <> 'proposed' OR preference.active_revision_id IS NOT NULL THEN
1134
+ RAISE EXCEPTION 'only an inactive proposal can be rejected'
1135
+ USING ERRCODE = '23514';
1136
+ END IF;
1137
+ UPDATE preference_registry_preferences
1138
+ SET status = 'rejected', updated_at = clock_timestamp()
1139
+ WHERE id = preference.id;
1140
+ event_type := 'rejected';
1141
+ new_revision_id := revision.id;
1142
+ ELSIF p_operation = 'deactivate' THEN
1143
+ IF preference.status <> 'active' OR preference.active_revision_id IS NULL THEN
1144
+ RAISE EXCEPTION 'only an active preference can be deactivated'
1145
+ USING ERRCODE = '23514';
1146
+ END IF;
1147
+ UPDATE preference_registry_preferences
1148
+ SET status = 'inactive', active_revision_id = NULL,
1149
+ active_revision = NULL, active_content_hash = NULL,
1150
+ activation_version = activation_version + 1,
1151
+ updated_at = clock_timestamp()
1152
+ WHERE id = preference.id;
1153
+ event_type := 'deactivated';
1154
+ old_revision_id := preference.active_revision_id;
1155
+ ELSIF p_operation = 'scope' THEN
1156
+ IF preference.status IN ('rejected', 'superseded')
1157
+ OR p_new_scope NOT IN ('organization', 'workspace', 'user')
1158
+ OR p_new_scope = preference.scope
1159
+ THEN
1160
+ RAISE EXCEPTION 'preference cannot change to the requested scope'
1161
+ USING ERRCODE = '23514';
1162
+ END IF;
1163
+ old_scope := preference.scope;
1164
+ old_workspace_id := preference.scope_workspace_id;
1165
+ old_subject_id := preference.scope_subject_id;
1166
+ new_scope := p_new_scope;
1167
+ new_workspace_id := CASE WHEN p_new_scope = 'workspace'
1168
+ THEN context_workspace_id ELSE NULL END;
1169
+ new_subject_id := CASE WHEN p_new_scope = 'user'
1170
+ THEN context_subject_id ELSE NULL END;
1171
+ UPDATE preference_registry_preferences
1172
+ SET scope = new_scope,
1173
+ scope_workspace_id = new_workspace_id,
1174
+ scope_subject_id = new_subject_id,
1175
+ scope_version = scope_version + 1,
1176
+ updated_at = clock_timestamp()
1177
+ WHERE id = preference.id;
1178
+ event_type := 'scope_changed';
1179
+ ELSIF p_operation = 'supersede' THEN
1180
+ SELECT * INTO replacement
1181
+ FROM preference_registry_preferences candidate
1182
+ WHERE candidate.id = p_related_preference_id
1183
+ AND candidate.account_id = preference.account_id;
1184
+ IF NOT FOUND
1185
+ OR replacement.status <> 'active'
1186
+ OR replacement.active_revision_id IS NULL
1187
+ OR replacement.scope IS DISTINCT FROM preference.scope
1188
+ OR replacement.scope_workspace_id IS DISTINCT FROM preference.scope_workspace_id
1189
+ OR replacement.scope_subject_id IS DISTINCT FROM preference.scope_subject_id
1190
+ OR NOT opengeni_private.preference_registry_scope_visible(
1191
+ replacement.account_id,
1192
+ replacement.scope,
1193
+ replacement.scope_workspace_id,
1194
+ replacement.scope_subject_id
1195
+ )
1196
+ OR preference.status <> 'active'
1197
+ OR preference.active_revision_id IS NULL
1198
+ THEN
1199
+ RAISE EXCEPTION 'replacement preference is not active in the exact same target'
1200
+ USING ERRCODE = '23514';
1201
+ END IF;
1202
+ SELECT * INTO replacement_revision
1203
+ FROM preference_registry_revisions candidate
1204
+ WHERE candidate.id = replacement.active_revision_id
1205
+ AND candidate.preference_id = replacement.id
1206
+ AND candidate.account_id = replacement.account_id
1207
+ AND (candidate.expires_at IS NULL OR candidate.expires_at > transaction_timestamp());
1208
+ IF NOT FOUND THEN
1209
+ RAISE EXCEPTION 'replacement preference active revision is expired'
1210
+ USING ERRCODE = '23514';
1211
+ END IF;
1212
+ UPDATE preference_registry_preferences
1213
+ SET status = 'superseded',
1214
+ superseded_by_preference_id = replacement.id,
1215
+ updated_at = clock_timestamp()
1216
+ WHERE id = preference.id;
1217
+ event_type := 'superseded';
1218
+ old_revision_id := preference.active_revision_id;
1219
+ related_preference_id := replacement.id;
1220
+ ELSE
1221
+ RAISE EXCEPTION 'unsupported preference lifecycle operation'
1222
+ USING ERRCODE = '22023';
1223
+ END IF;
1224
+
1225
+ PERFORM set_config('opengeni.preference_lifecycle_head_id', '', true);
1226
+ PERFORM set_config('opengeni.preference_lifecycle_operation', '', true);
1227
+
1228
+ SELECT COALESCE(max(event.version), 0) + 1 INTO next_event_version
1229
+ FROM preference_registry_events event
1230
+ WHERE event.preference_id = preference.id;
1231
+ INSERT INTO preference_registry_events (
1232
+ account_id, preference_id, type, version,
1233
+ old_revision_id, new_revision_id,
1234
+ old_scope, old_workspace_id, old_subject_id,
1235
+ new_scope, new_workspace_id, new_subject_id,
1236
+ related_preference_id, actor_subject_id, reason
1237
+ ) VALUES (
1238
+ preference.account_id, preference.id, event_type, next_event_version,
1239
+ old_revision_id, new_revision_id,
1240
+ old_scope, old_workspace_id, old_subject_id,
1241
+ new_scope, new_workspace_id, new_subject_id,
1242
+ related_preference_id, p_actor_subject_id, p_reason
1243
+ ) RETURNING id INTO event_id;
1244
+ RETURN NEXT;
1245
+ END
1246
+ $body$
1247
+ $ddl$, target_schema, target_schema);
1248
+ EXECUTE format(
1249
+ 'REVOKE ALL ON FUNCTION %I.preference_registry_apply_lifecycle(text, uuid, integer, uuid, uuid, text, uuid, text, text) FROM PUBLIC',
1250
+ target_schema
1251
+ );
1252
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1253
+ EXECUTE format(
1254
+ 'GRANT EXECUTE ON FUNCTION %I.preference_registry_apply_lifecycle(text, uuid, integer, uuid, uuid, text, uuid, text, text) TO opengeni_app',
1255
+ target_schema
1256
+ );
1257
+ END IF;
1258
+ END $lifecycle_function$;
1259
+
1260
+ ALTER TABLE "preference_registry_preferences" ENABLE ROW LEVEL SECURITY;
1261
+ ALTER TABLE "preference_registry_preferences" FORCE ROW LEVEL SECURITY;
1262
+ ALTER TABLE "preference_registry_revisions" ENABLE ROW LEVEL SECURITY;
1263
+ ALTER TABLE "preference_registry_revisions" FORCE ROW LEVEL SECURITY;
1264
+ ALTER TABLE "preference_registry_events" ENABLE ROW LEVEL SECURITY;
1265
+ ALTER TABLE "preference_registry_events" FORCE ROW LEVEL SECURITY;
1266
+ ALTER TABLE "preference_registry_snapshots" ENABLE ROW LEVEL SECURITY;
1267
+ ALTER TABLE "preference_registry_snapshots" FORCE ROW LEVEL SECURITY;
1268
+
1269
+ CREATE POLICY preference_registry_scope_isolation ON "preference_registry_preferences"
1270
+ USING (opengeni_private.preference_registry_scope_visible(
1271
+ account_id, scope, scope_workspace_id, scope_subject_id
1272
+ ))
1273
+ WITH CHECK (opengeni_private.preference_registry_scope_visible(
1274
+ account_id, scope, scope_workspace_id, scope_subject_id
1275
+ ));
1276
+
1277
+ CREATE POLICY preference_registry_scope_isolation ON "preference_registry_revisions"
1278
+ USING (EXISTS (
1279
+ SELECT 1 FROM preference_registry_preferences preference
1280
+ WHERE preference.id = preference_registry_revisions.preference_id
1281
+ AND preference.account_id = preference_registry_revisions.account_id
1282
+ ) OR EXISTS (
1283
+ SELECT 1 FROM preference_registry_snapshots snapshot
1284
+ WHERE snapshot.account_id = preference_registry_revisions.account_id
1285
+ AND snapshot.workspace_id = opengeni_private.current_workspace_id()
1286
+ AND snapshot.initiating_human_subject_id = opengeni_private.current_subject_id()
1287
+ AND snapshot.descriptors @> jsonb_build_array(
1288
+ jsonb_build_object(
1289
+ 'id', preference_registry_revisions.preference_id::text,
1290
+ 'revisionId', preference_registry_revisions.id::text,
1291
+ 'contentHash', preference_registry_revisions.content_hash
1292
+ )
1293
+ )
1294
+ ))
1295
+ WITH CHECK (EXISTS (
1296
+ SELECT 1 FROM preference_registry_preferences preference
1297
+ WHERE preference.id = preference_registry_revisions.preference_id
1298
+ AND preference.account_id = preference_registry_revisions.account_id
1299
+ ));
1300
+
1301
+ CREATE POLICY preference_registry_scope_isolation ON "preference_registry_events"
1302
+ USING (EXISTS (
1303
+ SELECT 1 FROM preference_registry_preferences preference
1304
+ WHERE preference.id = preference_registry_events.preference_id
1305
+ AND preference.account_id = preference_registry_events.account_id
1306
+ ))
1307
+ WITH CHECK (EXISTS (
1308
+ SELECT 1 FROM preference_registry_preferences preference
1309
+ WHERE preference.id = preference_registry_events.preference_id
1310
+ AND preference.account_id = preference_registry_events.account_id
1311
+ ));
1312
+
1313
+ CREATE POLICY preference_registry_snapshot_isolation ON "preference_registry_snapshots"
1314
+ USING (
1315
+ opengeni_private.workspace_rls_visible(account_id, workspace_id)
1316
+ AND initiating_human_subject_id = opengeni_private.current_subject_id()
1317
+ )
1318
+ WITH CHECK (
1319
+ opengeni_private.workspace_rls_visible(account_id, workspace_id)
1320
+ AND initiating_human_subject_id = opengeni_private.current_subject_id()
1321
+ );
1322
+
1323
+ DO $grants$
1324
+ DECLARE target_schema text := current_schema();
1325
+ BEGIN
1326
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1327
+ EXECUTE format(
1328
+ 'REVOKE ALL PRIVILEGES ON TABLE %I.preference_registry_preferences, %I.preference_registry_revisions, %I.preference_registry_events, %I.preference_registry_snapshots FROM opengeni_app',
1329
+ target_schema, target_schema, target_schema, target_schema
1330
+ );
1331
+ EXECUTE format(
1332
+ 'GRANT SELECT, INSERT ON TABLE %I.preference_registry_preferences, %I.preference_registry_revisions TO opengeni_app',
1333
+ target_schema, target_schema
1334
+ );
1335
+ EXECUTE format(
1336
+ 'GRANT SELECT ON TABLE %I.preference_registry_events, %I.preference_registry_snapshots TO opengeni_app',
1337
+ target_schema, target_schema
1338
+ );
1339
+ EXECUTE format(
1340
+ 'GRANT USAGE, SELECT ON SEQUENCE %I.preference_registry_revision_seq TO opengeni_app',
1341
+ target_schema
1342
+ );
1343
+ END IF;
1344
+ END $grants$;
1345
+
1346
+ RESET statement_timeout;
1347
+ RESET lock_timeout;