@opengeni/db 0.14.3 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ELMUCYZF.js → chunk-4RUK5CON.js} +183 -42
- package/dist/chunk-4RUK5CON.js.map +1 -0
- package/dist/chunk-HZD7KVAR.js +4648 -0
- package/dist/chunk-HZD7KVAR.js.map +1 -0
- package/dist/{chunk-Y5WZZVQK.js → chunk-SK7YYHBI.js} +5 -2
- package/dist/chunk-SK7YYHBI.js.map +1 -0
- package/dist/codex-token-resolver.d.ts +64 -0
- package/dist/connection-token-resolver.d.ts +90 -0
- package/dist/environment-crypto.d.ts +11 -0
- package/dist/event-payload-sanitizer.d.ts +32 -0
- package/dist/index.d.ts +6217 -10
- package/dist/index.js +6930 -2708
- package/dist/index.js.map +1 -1
- package/dist/insights.d.ts +157 -0
- package/dist/memory-domain.d.ts +44 -0
- package/dist/migrate.d.ts +5 -7
- package/dist/migrate.js +1 -1
- package/dist/new-session-drafts.d.ts +49 -0
- package/dist/persistence-errors.d.ts +69 -0
- package/dist/preference-registry-schema.d.ts +1147 -0
- package/dist/preference-registry.d.ts +878 -0
- package/dist/provision-roles.d.ts +4 -6489
- package/dist/provision-roles.js +1 -1
- package/dist/role-relationships.d.ts +35 -0
- package/dist/runtime-posture-cli.d.ts +1 -0
- package/dist/runtime-posture.d.ts +103 -0
- package/dist/schema.d.ts +23533 -3
- package/dist/schema.js +17 -1
- package/dist/session-control.d.ts +331 -0
- package/dist/session-queue-commands.d.ts +186 -0
- package/dist/session-tool-call-settlement.d.ts +32 -0
- package/dist/turn-initiator.d.ts +28 -0
- package/dist/workspace-instruction-policies-schema.d.ts +735 -0
- package/dist/workspace-instruction-policies.d.ts +64 -0
- package/drizzle/0136_unified_session_tool_policy.sql +1 -1
- package/drizzle/0137_preference_registry.sql +1347 -0
- package/drizzle/0138_sandbox_checkpoint_artifacts_and_deadlines.sql +1074 -0
- package/drizzle/0139_codex_provider_artifact_invalidations.sql +51 -0
- package/drizzle/0140_sandbox_restore_and_reaper_fences.sql +276 -0
- package/drizzle/0142_sandbox_archive_capture_gate.sql +80 -0
- package/drizzle/0143_session_codex_compaction_mode.sql +20 -0
- package/drizzle/0144_sandbox_viewer_force_drain_gate.sql +95 -0
- package/drizzle/0145_model_call_facts.sql +96 -0
- package/drizzle/0146_slack_bot_delete_idempotency.sql +105 -0
- package/drizzle/0147_draft_latency_mode.sql +12 -0
- package/drizzle/0148_session_turn_latency_mode.sql +12 -0
- package/package.json +6 -6
- package/src/index.ts +4995 -421
- package/src/insights.ts +837 -0
- package/src/migrate.ts +9 -1
- package/src/new-session-drafts.ts +4 -0
- package/src/preference-registry-schema.ts +170 -0
- package/src/preference-registry.ts +1220 -0
- package/src/provision-roles.ts +95 -22
- package/src/role-relationships.ts +132 -0
- package/src/runtime-posture.ts +28 -26
- package/src/schema.ts +478 -5
- package/src/session-queue-commands.ts +39 -6
- package/dist/chunk-DW24CKCT.js +0 -4046
- package/dist/chunk-DW24CKCT.js.map +0 -1
- package/dist/chunk-ELMUCYZF.js.map +0 -1
- package/dist/chunk-Y5WZZVQK.js.map +0 -1
- 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;
|