@opengeni/db 0.22.1 → 0.23.0

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 (48) hide show
  1. package/dist/{chunk-CYGFLLMN.js → chunk-3UCHDMKG.js} +656 -282
  2. package/dist/chunk-3UCHDMKG.js.map +1 -0
  3. package/dist/{chunk-BNGEN5QZ.js → chunk-L6ADMZHE.js} +24 -2
  4. package/dist/chunk-L6ADMZHE.js.map +1 -0
  5. package/dist/index.d.ts +42 -3
  6. package/dist/index.js +6919 -3760
  7. package/dist/index.js.map +1 -1
  8. package/dist/provision-roles.js +1 -1
  9. package/dist/runtime-posture.d.ts +3 -3
  10. package/dist/schema.d.ts +1416 -92
  11. package/dist/schema.js +11 -1
  12. package/dist/session-control.d.ts +7 -1
  13. package/dist/session-queue-commands.d.ts +8 -0
  14. package/dist/session-realtime-context.d.ts +56 -0
  15. package/dist/session-realtime-ledger.d.ts +188 -0
  16. package/dist/session-realtime-mirror.d.ts +30 -0
  17. package/dist/session-realtime-state.d.ts +2 -0
  18. package/dist/session-realtime-terminal.d.ts +40 -0
  19. package/dist/session-realtime.d.ts +59 -0
  20. package/dist/workspace-instruction-policies-schema.d.ts +239 -0
  21. package/dist/workspace-instruction-policies.d.ts +44 -0
  22. package/drizzle/0156_slack_reaction_trigger.sql +49 -0
  23. package/drizzle/0157_session_policy_role_snapshots.sql +1146 -0
  24. package/drizzle/0158_session_realtime_mode.sql +88 -0
  25. package/drizzle/0159_session_realtime_ledger.sql +198 -0
  26. package/drizzle/0160_session_realtime_delegation_terminal.sql +38 -0
  27. package/drizzle/0161_session_realtime_context_projection.sql +82 -0
  28. package/drizzle/0162_session_realtime_connection_promotion.sql +53 -0
  29. package/drizzle/0163_session_realtime_delegation_progress.sql +35 -0
  30. package/drizzle/0164_session_realtime_models.sql +28 -0
  31. package/package.json +4 -4
  32. package/src/index.ts +670 -79
  33. package/src/preference-registry.ts +11 -6
  34. package/src/provision-roles.ts +12 -0
  35. package/src/runtime-posture.ts +10 -0
  36. package/src/schema.ts +400 -43
  37. package/src/session-control.ts +596 -21
  38. package/src/session-queue-commands.ts +76 -7
  39. package/src/session-realtime-context.ts +393 -0
  40. package/src/session-realtime-ledger.ts +1790 -0
  41. package/src/session-realtime-mirror.ts +160 -0
  42. package/src/session-realtime-state.ts +25 -0
  43. package/src/session-realtime-terminal.ts +306 -0
  44. package/src/session-realtime.ts +611 -0
  45. package/src/workspace-instruction-policies-schema.ts +41 -0
  46. package/src/workspace-instruction-policies.ts +131 -2
  47. package/dist/chunk-BNGEN5QZ.js.map +0 -1
  48. package/dist/chunk-CYGFLLMN.js.map +0 -1
@@ -0,0 +1,1146 @@
1
+ -- deployment-mode: rolling
2
+ -- Freeze the exact active workspace charter/policies and typed policy role for
3
+ -- every accepted attempt. Documents and knowledge sources are not policy
4
+ -- authorities and are intentionally absent from this snapshot.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ ALTER TABLE "sessions"
10
+ ADD COLUMN IF NOT EXISTS "policy_role" text;
11
+
12
+ ALTER TABLE "sessions"
13
+ ADD CONSTRAINT "sessions_policy_role_chk" CHECK (
14
+ "policy_role" IS NULL
15
+ OR (
16
+ "policy_role" = lower(btrim("policy_role"))
17
+ AND length("policy_role") BETWEEN 1 AND 64
18
+ AND "policy_role" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
19
+ AND "policy_role" !~ '--'
20
+ )
21
+ ) NOT VALID;
22
+
23
+ ALTER TABLE "sessions"
24
+ VALIDATE CONSTRAINT "sessions_policy_role_chk";
25
+
26
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_reject_session_role_mutation()
27
+ RETURNS trigger
28
+ LANGUAGE plpgsql
29
+ AS $$
30
+ BEGIN
31
+ IF NEW."policy_role" IS DISTINCT FROM OLD."policy_role" THEN
32
+ RAISE EXCEPTION 'session policy role is immutable after creation'
33
+ USING ERRCODE = '55000';
34
+ END IF;
35
+ RETURN NEW;
36
+ END;
37
+ $$;
38
+
39
+ DROP TRIGGER IF EXISTS sessions_policy_role_immutable ON "sessions";
40
+ CREATE TRIGGER sessions_policy_role_immutable
41
+ BEFORE UPDATE OF "policy_role" ON "sessions"
42
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_reject_session_role_mutation();
43
+
44
+ ALTER TABLE "session_turns"
45
+ ADD COLUMN IF NOT EXISTS "initiating_human_subject_id" text;
46
+
47
+ ALTER TABLE "session_turns"
48
+ ADD CONSTRAINT "session_turns_initiating_human_subject_id_chk" CHECK (
49
+ "initiating_human_subject_id" IS NULL
50
+ OR length(btrim("initiating_human_subject_id")) BETWEEN 1 AND 1024
51
+ ) NOT VALID;
52
+
53
+ ALTER TABLE "session_turns"
54
+ VALIDATE CONSTRAINT "session_turns_initiating_human_subject_id_chk";
55
+
56
+ CREATE OR REPLACE FUNCTION workspace_governance_reject_turn_human_mutation()
57
+ RETURNS trigger
58
+ LANGUAGE plpgsql
59
+ AS $$
60
+ BEGIN
61
+ IF NEW."initiating_human_subject_id" IS DISTINCT FROM OLD."initiating_human_subject_id" THEN
62
+ RAISE EXCEPTION 'turn initiating human is immutable after acceptance'
63
+ USING ERRCODE = '55000';
64
+ END IF;
65
+ RETURN NEW;
66
+ END;
67
+ $$;
68
+
69
+ DROP TRIGGER IF EXISTS session_turns_initiating_human_immutable ON "session_turns";
70
+ CREATE TRIGGER session_turns_initiating_human_immutable
71
+ BEFORE UPDATE OF "initiating_human_subject_id" ON "session_turns"
72
+ FOR EACH ROW EXECUTE FUNCTION workspace_governance_reject_turn_human_mutation();
73
+
74
+ ALTER TABLE "preference_registry_snapshots"
75
+ DROP CONSTRAINT "preference_registry_snapshots_account_id_fkey",
76
+ ADD CONSTRAINT "preference_registry_snapshots_account_id_fkey"
77
+ FOREIGN KEY ("account_id") REFERENCES "managed_accounts"("id") ON DELETE CASCADE NOT VALID,
78
+ DROP CONSTRAINT "preference_registry_snapshots_workspace_id_fkey",
79
+ ADD CONSTRAINT "preference_registry_snapshots_workspace_id_fkey"
80
+ FOREIGN KEY ("workspace_id") REFERENCES "workspaces"("id") ON DELETE CASCADE NOT VALID,
81
+ DROP CONSTRAINT "preference_registry_snapshots_session_id_fkey",
82
+ ADD CONSTRAINT "preference_registry_snapshots_session_id_fkey"
83
+ FOREIGN KEY ("session_id") REFERENCES "sessions"("id") ON DELETE CASCADE NOT VALID,
84
+ DROP CONSTRAINT "preference_registry_snapshots_turn_id_fkey",
85
+ ADD CONSTRAINT "preference_registry_snapshots_turn_id_fkey"
86
+ FOREIGN KEY ("turn_id") REFERENCES "session_turns"("id") ON DELETE CASCADE NOT VALID,
87
+ DROP CONSTRAINT "preference_registry_snapshots_attempt_id_fkey",
88
+ ADD CONSTRAINT "preference_registry_snapshots_attempt_id_fkey"
89
+ FOREIGN KEY ("attempt_id") REFERENCES "session_turn_attempts"("id") ON DELETE CASCADE NOT VALID;
90
+
91
+ ALTER TABLE "preference_registry_snapshots"
92
+ VALIDATE CONSTRAINT "preference_registry_snapshots_account_id_fkey",
93
+ VALIDATE CONSTRAINT "preference_registry_snapshots_workspace_id_fkey",
94
+ VALIDATE CONSTRAINT "preference_registry_snapshots_session_id_fkey",
95
+ VALIDATE CONSTRAINT "preference_registry_snapshots_turn_id_fkey",
96
+ VALIDATE CONSTRAINT "preference_registry_snapshots_attempt_id_fkey";
97
+
98
+ CREATE TABLE "workspace_instruction_policy_snapshots" (
99
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
100
+ "account_id" uuid NOT NULL REFERENCES "managed_accounts"("id") ON DELETE CASCADE,
101
+ "workspace_id" uuid NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
102
+ "session_id" uuid NOT NULL REFERENCES "sessions"("id") ON DELETE CASCADE,
103
+ "turn_id" uuid NOT NULL REFERENCES "session_turns"("id") ON DELETE CASCADE,
104
+ "attempt_id" uuid NOT NULL REFERENCES "session_turn_attempts"("id") ON DELETE CASCADE,
105
+ "execution_generation" integer NOT NULL,
106
+ "policy_role" text,
107
+ "role_source" text NOT NULL,
108
+ "entries" jsonb NOT NULL,
109
+ "entry_hash" text NOT NULL,
110
+ "created_at" timestamptz NOT NULL DEFAULT now(),
111
+ CONSTRAINT "workspace_instruction_policy_snapshots_generation_chk" CHECK (
112
+ "execution_generation" > 0
113
+ ),
114
+ CONSTRAINT "workspace_instruction_policy_snapshots_entries_chk" CHECK (
115
+ jsonb_typeof("entries") = 'array'
116
+ AND jsonb_array_length("entries") <= 3
117
+ ),
118
+ CONSTRAINT "workspace_instruction_policy_snapshots_hash_chk" CHECK (
119
+ "entry_hash" ~ '^[0-9a-f]{64}$'
120
+ AND "entry_hash" = encode(sha256(convert_to("entries"::text, 'UTF8')), 'hex')
121
+ ),
122
+ CONSTRAINT "workspace_instruction_policy_snapshots_role_source_chk" CHECK (
123
+ "role_source" IN (
124
+ 'session_binding',
125
+ 'metadata_fallback',
126
+ 'none',
127
+ 'invalid_metadata_fallback'
128
+ )
129
+ ),
130
+ CONSTRAINT "workspace_instruction_policy_snapshots_role_shape_chk" CHECK (
131
+ (
132
+ "role_source" IN ('session_binding', 'metadata_fallback')
133
+ AND "policy_role" IS NOT NULL
134
+ AND "policy_role" = lower(btrim("policy_role"))
135
+ AND length("policy_role") BETWEEN 1 AND 64
136
+ AND "policy_role" ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
137
+ AND "policy_role" !~ '--'
138
+ )
139
+ OR (
140
+ "role_source" IN ('none', 'invalid_metadata_fallback')
141
+ AND "policy_role" IS NULL
142
+ )
143
+ ),
144
+ CONSTRAINT "workspace_instruction_policy_snapshots_attempt_uq"
145
+ UNIQUE ("account_id", "workspace_id", "attempt_id")
146
+ );
147
+
148
+ CREATE INDEX "workspace_instruction_policy_snapshots_workspace_time_idx"
149
+ ON "workspace_instruction_policy_snapshots" ("workspace_id", "created_at" DESC, "id");
150
+
151
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_normalize_role_key(value text)
152
+ RETURNS text
153
+ LANGUAGE sql
154
+ IMMUTABLE
155
+ STRICT
156
+ AS $$
157
+ SELECT lower(
158
+ regexp_replace(
159
+ regexp_replace(btrim(normalize(value, NFKC)), '[[:space:]]+', '-', 'g'),
160
+ '-+',
161
+ '-',
162
+ 'g'
163
+ )
164
+ );
165
+ $$;
166
+
167
+ REVOKE ALL ON FUNCTION workspace_instruction_policy_normalize_role_key(text) FROM PUBLIC;
168
+
169
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_canonical_snapshot_entries(
170
+ p_account_id uuid,
171
+ p_workspace_id uuid,
172
+ p_policy_role text,
173
+ p_accepted_at timestamptz
174
+ ) RETURNS jsonb
175
+ LANGUAGE sql
176
+ STABLE
177
+ AS $$
178
+ SELECT coalesce(jsonb_agg(candidate.entry ORDER BY candidate.ordinal), '[]'::jsonb)
179
+ FROM (
180
+ SELECT
181
+ CASE
182
+ WHEN event.kind = 'charter' THEN 0
183
+ WHEN event.scope = 'global' THEN 1
184
+ ELSE 2
185
+ END AS ordinal,
186
+ jsonb_build_object(
187
+ 'kind', event.kind,
188
+ 'scope', event.scope,
189
+ 'roleKey', event.role_key,
190
+ 'revisionId', revision.id::text,
191
+ 'revision', revision.revision,
192
+ 'contentHash', revision.content_hash,
193
+ 'activationVersion', event.activation_version,
194
+ 'activatedAt', to_char(
195
+ event.created_at AT TIME ZONE 'UTC',
196
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
197
+ ),
198
+ 'provenance', jsonb_build_object(
199
+ 'source', revision.provenance_source,
200
+ 'sourceIdHash', CASE
201
+ WHEN revision.provenance_source_id IS NULL THEN NULL
202
+ ELSE encode(
203
+ sha256(convert_to(revision.provenance_source_id, 'UTF8')),
204
+ 'hex'
205
+ )
206
+ END
207
+ )
208
+ ) AS entry
209
+ FROM (
210
+ SELECT DISTINCT ON (activation.kind, activation.scope, coalesce(activation.role_key, ''))
211
+ activation.*
212
+ FROM workspace_instruction_policy_activation_events activation
213
+ WHERE activation.account_id = p_account_id
214
+ AND activation.workspace_id = p_workspace_id
215
+ AND activation.created_at <= p_accepted_at
216
+ AND (
217
+ (activation.kind = 'charter' AND activation.scope = 'global')
218
+ OR (activation.kind = 'policy' AND activation.scope = 'global')
219
+ OR (
220
+ p_policy_role IS NOT NULL
221
+ AND activation.kind = 'policy'
222
+ AND activation.scope = 'role'
223
+ AND activation.role_key = p_policy_role
224
+ )
225
+ )
226
+ ORDER BY
227
+ activation.kind,
228
+ activation.scope,
229
+ coalesce(activation.role_key, ''),
230
+ activation.created_at DESC,
231
+ activation.activation_version DESC,
232
+ activation.id DESC
233
+ ) event
234
+ JOIN workspace_instruction_policy_revisions revision
235
+ ON revision.account_id = event.account_id
236
+ AND revision.workspace_id = event.workspace_id
237
+ AND revision.id = event.new_revision_id
238
+ AND revision.revision = event.new_revision
239
+ AND revision.content_hash = event.new_content_hash
240
+ ) candidate;
241
+ $$;
242
+
243
+ REVOKE ALL ON FUNCTION workspace_instruction_policy_canonical_snapshot_entries(
244
+ uuid,
245
+ uuid,
246
+ text,
247
+ timestamptz
248
+ )
249
+ FROM PUBLIC;
250
+
251
+ CREATE OR REPLACE FUNCTION workspace_instruction_policy_validate_snapshot()
252
+ RETURNS trigger
253
+ LANGUAGE plpgsql
254
+ AS $$
255
+ DECLARE
256
+ session_policy_role text;
257
+ session_metadata jsonb;
258
+ metadata_role_present boolean;
259
+ metadata_role_candidate text;
260
+ canonical_policy_role text;
261
+ canonical_role_source text;
262
+ canonical_entries jsonb;
263
+ turn_accepted_at timestamptz;
264
+ BEGIN
265
+ SELECT session.policy_role, session.metadata, turn.created_at
266
+ INTO session_policy_role, session_metadata, turn_accepted_at
267
+ FROM session_turn_attempts attempt
268
+ JOIN session_turns turn
269
+ ON turn.account_id = attempt.account_id
270
+ AND turn.workspace_id = attempt.workspace_id
271
+ AND turn.session_id = attempt.session_id
272
+ AND turn.id = attempt.turn_id
273
+ JOIN sessions session
274
+ ON session.account_id = attempt.account_id
275
+ AND session.workspace_id = attempt.workspace_id
276
+ AND session.id = attempt.session_id
277
+ WHERE attempt.id = NEW.attempt_id
278
+ AND attempt.account_id = NEW.account_id
279
+ AND attempt.workspace_id = NEW.workspace_id
280
+ AND attempt.session_id = NEW.session_id
281
+ AND attempt.turn_id = NEW.turn_id
282
+ AND attempt.execution_generation = NEW.execution_generation
283
+ AND turn.execution_generation = NEW.execution_generation
284
+ AND attempt.state IN ('claimed', 'running')
285
+ AND turn.active_attempt_id = attempt.id
286
+ AND session.active_turn_id = turn.id
287
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
288
+ AND NOT EXISTS (
289
+ SELECT 1
290
+ FROM session_attempt_interruptions interruption
291
+ WHERE interruption.workspace_id = NEW.workspace_id
292
+ AND interruption.attempt_id = NEW.attempt_id
293
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
294
+ );
295
+ IF NOT FOUND THEN
296
+ RAISE EXCEPTION 'instruction-policy snapshot requires an exact active attempt'
297
+ USING ERRCODE = '23514';
298
+ END IF;
299
+
300
+ IF session_policy_role IS NOT NULL THEN
301
+ canonical_policy_role := session_policy_role;
302
+ canonical_role_source := 'session_binding';
303
+ ELSE
304
+ metadata_role_present := coalesce(session_metadata ? 'role', false);
305
+ IF metadata_role_present AND jsonb_typeof(session_metadata->'role') = 'string' THEN
306
+ metadata_role_candidate := workspace_instruction_policy_normalize_role_key(
307
+ session_metadata->>'role'
308
+ );
309
+ END IF;
310
+ IF metadata_role_candidate IS NOT NULL
311
+ AND length(metadata_role_candidate) BETWEEN 1 AND 64
312
+ AND metadata_role_candidate ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
313
+ AND metadata_role_candidate !~ '--'
314
+ THEN
315
+ canonical_policy_role := metadata_role_candidate;
316
+ canonical_role_source := 'metadata_fallback';
317
+ ELSIF metadata_role_present THEN
318
+ canonical_policy_role := NULL;
319
+ canonical_role_source := 'invalid_metadata_fallback';
320
+ ELSE
321
+ canonical_policy_role := NULL;
322
+ canonical_role_source := 'none';
323
+ END IF;
324
+ END IF;
325
+
326
+ IF NEW.policy_role IS DISTINCT FROM canonical_policy_role
327
+ OR NEW.role_source IS DISTINCT FROM canonical_role_source
328
+ THEN
329
+ RAISE EXCEPTION 'instruction-policy snapshot role does not match the immutable session binding'
330
+ USING ERRCODE = '23514';
331
+ END IF;
332
+
333
+ canonical_entries := workspace_instruction_policy_canonical_snapshot_entries(
334
+ NEW.account_id,
335
+ NEW.workspace_id,
336
+ canonical_policy_role,
337
+ turn_accepted_at
338
+ );
339
+ IF NEW.entries IS DISTINCT FROM canonical_entries
340
+ OR NEW.entry_hash IS DISTINCT FROM encode(
341
+ sha256(convert_to(canonical_entries::text, 'UTF8')),
342
+ 'hex'
343
+ )
344
+ THEN
345
+ RAISE EXCEPTION 'instruction-policy snapshot is not the canonical locked policy set'
346
+ USING ERRCODE = '23514';
347
+ END IF;
348
+
349
+ RETURN NEW;
350
+ END;
351
+ $$;
352
+
353
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_snapshots_validate
354
+ ON "workspace_instruction_policy_snapshots";
355
+ CREATE TRIGGER workspace_instruction_policy_snapshots_validate
356
+ BEFORE INSERT ON "workspace_instruction_policy_snapshots"
357
+ FOR EACH ROW EXECUTE FUNCTION workspace_instruction_policy_validate_snapshot();
358
+
359
+ CREATE OR REPLACE FUNCTION workspace_governance_snapshot_reject_mutation()
360
+ RETURNS trigger
361
+ LANGUAGE plpgsql
362
+ AS $$
363
+ BEGIN
364
+ -- Parent lifecycle deletion is the sole mutation exception. PostgreSQL's
365
+ -- cascading constraint trigger runs only after its parent row is absent;
366
+ -- require both that nested trigger context and one missing ownership edge so
367
+ -- direct or unrelated-trigger deletes remain fail-closed.
368
+ IF TG_OP = 'DELETE'
369
+ AND pg_trigger_depth() > 1
370
+ AND (
371
+ NOT EXISTS (SELECT 1 FROM "managed_accounts" WHERE "id" = OLD."account_id")
372
+ OR NOT EXISTS (SELECT 1 FROM "workspaces" WHERE "id" = OLD."workspace_id")
373
+ OR NOT EXISTS (SELECT 1 FROM "sessions" WHERE "id" = OLD."session_id")
374
+ OR NOT EXISTS (SELECT 1 FROM "session_turns" WHERE "id" = OLD."turn_id")
375
+ OR NOT EXISTS (SELECT 1 FROM "session_turn_attempts" WHERE "id" = OLD."attempt_id")
376
+ )
377
+ THEN
378
+ RETURN OLD;
379
+ END IF;
380
+ RAISE EXCEPTION 'accepted-turn governance snapshots are immutable'
381
+ USING ERRCODE = '55000';
382
+ END;
383
+ $$;
384
+
385
+ DROP TRIGGER IF EXISTS workspace_instruction_policy_snapshots_immutable
386
+ ON "workspace_instruction_policy_snapshots";
387
+ CREATE TRIGGER workspace_instruction_policy_snapshots_immutable
388
+ BEFORE UPDATE OR DELETE ON "workspace_instruction_policy_snapshots"
389
+ FOR EACH ROW EXECUTE FUNCTION workspace_governance_snapshot_reject_mutation();
390
+
391
+ DROP TRIGGER IF EXISTS preference_registry_snapshots_immutable
392
+ ON "preference_registry_snapshots";
393
+ CREATE TRIGGER preference_registry_snapshots_immutable
394
+ BEFORE UPDATE OR DELETE ON "preference_registry_snapshots"
395
+ FOR EACH ROW EXECUTE FUNCTION workspace_governance_snapshot_reject_mutation();
396
+
397
+ DO $snapshot_function$
398
+ DECLARE target_schema text := current_schema();
399
+ BEGIN
400
+ EXECUTE format($ddl$
401
+ CREATE OR REPLACE FUNCTION %I.workspace_instruction_policy_get_or_create_snapshot(
402
+ p_account_id uuid,
403
+ p_workspace_id uuid,
404
+ p_session_id uuid,
405
+ p_turn_id uuid,
406
+ p_attempt_id uuid,
407
+ p_execution_generation integer
408
+ ) RETURNS SETOF %I.workspace_instruction_policy_snapshots
409
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
410
+ AS $body$
411
+ DECLARE
412
+ context_account_id uuid;
413
+ context_workspace_id uuid;
414
+ session_policy_role text;
415
+ session_metadata jsonb;
416
+ metadata_role_present boolean;
417
+ metadata_role_candidate text;
418
+ canonical_policy_role text;
419
+ canonical_role_source text;
420
+ canonical_entries jsonb;
421
+ canonical_hash text;
422
+ turn_accepted_at timestamptz;
423
+ snapshot_time timestamptz := transaction_timestamp();
424
+ winner_id uuid;
425
+ BEGIN
426
+ context_account_id := NULLIF(
427
+ current_setting('opengeni.account_id', true), ''
428
+ )::uuid;
429
+ context_workspace_id := NULLIF(
430
+ current_setting('opengeni.workspace_id', true), ''
431
+ )::uuid;
432
+ IF context_account_id IS DISTINCT FROM p_account_id
433
+ OR context_workspace_id IS DISTINCT FROM p_workspace_id
434
+ OR p_execution_generation < 1
435
+ THEN
436
+ RAISE EXCEPTION 'instruction-policy snapshot requires exact transaction-local tenant authority'
437
+ USING ERRCODE = '42501';
438
+ END IF;
439
+
440
+ SELECT session.policy_role, session.metadata, turn.created_at
441
+ INTO session_policy_role, session_metadata, turn_accepted_at
442
+ FROM workspaces workspace
443
+ JOIN sessions session
444
+ ON session.account_id = workspace.account_id
445
+ AND session.workspace_id = workspace.id
446
+ JOIN session_turns turn
447
+ ON turn.account_id = session.account_id
448
+ AND turn.workspace_id = session.workspace_id
449
+ AND turn.session_id = session.id
450
+ JOIN session_turn_attempts attempt
451
+ ON attempt.account_id = turn.account_id
452
+ AND attempt.workspace_id = turn.workspace_id
453
+ AND attempt.session_id = turn.session_id
454
+ AND attempt.turn_id = turn.id
455
+ WHERE workspace.id = p_workspace_id
456
+ AND workspace.account_id = p_account_id
457
+ AND session.id = p_session_id
458
+ AND session.active_turn_id = p_turn_id
459
+ AND turn.id = p_turn_id
460
+ AND turn.active_attempt_id = p_attempt_id
461
+ AND turn.execution_generation = p_execution_generation
462
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
463
+ AND attempt.id = p_attempt_id
464
+ AND attempt.execution_generation = p_execution_generation
465
+ AND attempt.state IN ('claimed', 'running')
466
+ AND NOT EXISTS (
467
+ SELECT 1
468
+ FROM session_attempt_interruptions interruption
469
+ WHERE interruption.workspace_id = attempt.workspace_id
470
+ AND interruption.attempt_id = attempt.id
471
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
472
+ )
473
+ FOR KEY SHARE OF workspace
474
+ FOR SHARE OF session, turn
475
+ FOR UPDATE OF attempt;
476
+ IF NOT FOUND THEN
477
+ RAISE EXCEPTION 'instruction-policy snapshot requires the exact current attempt'
478
+ USING ERRCODE = '42501';
479
+ END IF;
480
+
481
+ SELECT snapshot.id INTO winner_id
482
+ FROM workspace_instruction_policy_snapshots snapshot
483
+ WHERE snapshot.account_id = p_account_id
484
+ AND snapshot.workspace_id = p_workspace_id
485
+ AND snapshot.session_id = p_session_id
486
+ AND snapshot.turn_id = p_turn_id
487
+ AND snapshot.attempt_id = p_attempt_id
488
+ AND snapshot.execution_generation = p_execution_generation
489
+ FOR SHARE;
490
+ IF winner_id IS NOT NULL THEN
491
+ IF EXISTS (
492
+ SELECT 1
493
+ FROM workspace_instruction_policy_snapshots snapshot
494
+ WHERE snapshot.id = winner_id
495
+ AND (
496
+ snapshot.created_at > transaction_timestamp()
497
+ OR jsonb_typeof(snapshot.entries) <> 'array'
498
+ OR jsonb_array_length(snapshot.entries) > 3
499
+ OR snapshot.entry_hash IS DISTINCT FROM encode(
500
+ sha256(convert_to(snapshot.entries::text, 'UTF8')),
501
+ 'hex'
502
+ )
503
+ OR (
504
+ snapshot.role_source IN ('session_binding', 'metadata_fallback')
505
+ AND snapshot.policy_role IS NULL
506
+ )
507
+ OR (
508
+ snapshot.role_source IN ('none', 'invalid_metadata_fallback')
509
+ AND snapshot.policy_role IS NOT NULL
510
+ )
511
+ )
512
+ ) THEN
513
+ RAISE EXCEPTION 'existing instruction-policy snapshot failed canonical integrity checks'
514
+ USING ERRCODE = '23514';
515
+ END IF;
516
+ RETURN QUERY
517
+ SELECT snapshot.*
518
+ FROM workspace_instruction_policy_snapshots snapshot
519
+ WHERE snapshot.id = winner_id;
520
+ RETURN;
521
+ END IF;
522
+
523
+ IF session_policy_role IS NOT NULL THEN
524
+ canonical_policy_role := session_policy_role;
525
+ canonical_role_source := 'session_binding';
526
+ ELSE
527
+ metadata_role_present := coalesce(session_metadata ? 'role', false);
528
+ IF metadata_role_present AND jsonb_typeof(session_metadata->'role') = 'string' THEN
529
+ metadata_role_candidate := workspace_instruction_policy_normalize_role_key(
530
+ session_metadata->>'role'
531
+ );
532
+ END IF;
533
+ IF metadata_role_candidate IS NOT NULL
534
+ AND length(metadata_role_candidate) BETWEEN 1 AND 64
535
+ AND metadata_role_candidate ~ '^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$'
536
+ AND metadata_role_candidate !~ '--'
537
+ THEN
538
+ canonical_policy_role := metadata_role_candidate;
539
+ canonical_role_source := 'metadata_fallback';
540
+ ELSIF metadata_role_present THEN
541
+ canonical_policy_role := NULL;
542
+ canonical_role_source := 'invalid_metadata_fallback';
543
+ ELSE
544
+ canonical_policy_role := NULL;
545
+ canonical_role_source := 'none';
546
+ END IF;
547
+ END IF;
548
+
549
+ PERFORM 1
550
+ FROM workspace_instruction_policy_activation_events event
551
+ WHERE event.account_id = p_account_id
552
+ AND event.workspace_id = p_workspace_id
553
+ AND event.created_at <= turn_accepted_at
554
+ AND (
555
+ (event.kind = 'charter' AND event.scope = 'global')
556
+ OR (event.kind = 'policy' AND event.scope = 'global')
557
+ OR (
558
+ canonical_policy_role IS NOT NULL
559
+ AND event.kind = 'policy'
560
+ AND event.scope = 'role'
561
+ AND event.role_key = canonical_policy_role
562
+ )
563
+ )
564
+ ORDER BY CASE
565
+ WHEN event.kind = 'charter' THEN 0
566
+ WHEN event.scope = 'global' THEN 1
567
+ ELSE 2
568
+ END,
569
+ event.created_at,
570
+ event.activation_version
571
+ FOR SHARE OF event;
572
+
573
+ canonical_entries := workspace_instruction_policy_canonical_snapshot_entries(
574
+ p_account_id,
575
+ p_workspace_id,
576
+ canonical_policy_role,
577
+ turn_accepted_at
578
+ );
579
+ canonical_hash := encode(
580
+ sha256(convert_to(canonical_entries::text, 'UTF8')),
581
+ 'hex'
582
+ );
583
+
584
+ INSERT INTO workspace_instruction_policy_snapshots (
585
+ account_id,
586
+ workspace_id,
587
+ session_id,
588
+ turn_id,
589
+ attempt_id,
590
+ execution_generation,
591
+ policy_role,
592
+ role_source,
593
+ entries,
594
+ entry_hash,
595
+ created_at
596
+ ) VALUES (
597
+ p_account_id,
598
+ p_workspace_id,
599
+ p_session_id,
600
+ p_turn_id,
601
+ p_attempt_id,
602
+ p_execution_generation,
603
+ canonical_policy_role,
604
+ canonical_role_source,
605
+ canonical_entries,
606
+ canonical_hash,
607
+ snapshot_time
608
+ )
609
+ ON CONFLICT (account_id, workspace_id, attempt_id) DO NOTHING
610
+ RETURNING id INTO winner_id;
611
+ IF winner_id IS NULL THEN
612
+ SELECT snapshot.id INTO winner_id
613
+ FROM workspace_instruction_policy_snapshots snapshot
614
+ WHERE snapshot.account_id = p_account_id
615
+ AND snapshot.workspace_id = p_workspace_id
616
+ AND snapshot.session_id = p_session_id
617
+ AND snapshot.turn_id = p_turn_id
618
+ AND snapshot.attempt_id = p_attempt_id
619
+ AND snapshot.execution_generation = p_execution_generation
620
+ FOR SHARE;
621
+ END IF;
622
+ IF winner_id IS NULL THEN
623
+ RAISE EXCEPTION 'instruction-policy snapshot winner conflicts with exact attempt authority'
624
+ USING ERRCODE = '40001';
625
+ END IF;
626
+ IF EXISTS (
627
+ SELECT 1
628
+ FROM workspace_instruction_policy_snapshots snapshot
629
+ WHERE snapshot.id = winner_id
630
+ AND (
631
+ snapshot.policy_role IS DISTINCT FROM canonical_policy_role
632
+ OR snapshot.role_source IS DISTINCT FROM canonical_role_source
633
+ OR snapshot.entries IS DISTINCT FROM canonical_entries
634
+ OR snapshot.entry_hash IS DISTINCT FROM canonical_hash
635
+ OR snapshot.created_at IS DISTINCT FROM snapshot_time
636
+ )
637
+ ) THEN
638
+ RAISE EXCEPTION 'instruction-policy snapshot winner is not the canonical locked snapshot'
639
+ USING ERRCODE = '40001';
640
+ END IF;
641
+ RETURN QUERY
642
+ SELECT snapshot.*
643
+ FROM workspace_instruction_policy_snapshots snapshot
644
+ WHERE snapshot.id = winner_id;
645
+ END
646
+ $body$
647
+ $ddl$, target_schema, target_schema, target_schema);
648
+ EXECUTE format(
649
+ 'REVOKE ALL ON FUNCTION %I.workspace_instruction_policy_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) FROM PUBLIC',
650
+ target_schema
651
+ );
652
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
653
+ EXECUTE format(
654
+ 'GRANT EXECUTE ON FUNCTION %I.workspace_instruction_policy_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) TO opengeni_app',
655
+ target_schema
656
+ );
657
+ END IF;
658
+ END $snapshot_function$;
659
+
660
+ ALTER TABLE "workspace_instruction_policy_snapshots" ENABLE ROW LEVEL SECURITY;
661
+ ALTER TABLE "workspace_instruction_policy_snapshots" FORCE ROW LEVEL SECURITY;
662
+
663
+ CREATE POLICY workspace_isolation ON "workspace_instruction_policy_snapshots"
664
+ USING (opengeni_private.workspace_rls_visible("account_id", "workspace_id"))
665
+ WITH CHECK (opengeni_private.workspace_rls_visible("account_id", "workspace_id"));
666
+
667
+ DO $runtime_grants$
668
+ DECLARE target_schema text := current_schema();
669
+ BEGIN
670
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
671
+ EXECUTE format(
672
+ 'REVOKE ALL PRIVILEGES ON TABLE %I.workspace_instruction_policy_snapshots FROM opengeni_app',
673
+ target_schema
674
+ );
675
+ EXECUTE format(
676
+ 'GRANT SELECT ON TABLE %I.workspace_instruction_policy_snapshots TO opengeni_app',
677
+ target_schema
678
+ );
679
+ END IF;
680
+ END $runtime_grants$;
681
+
682
+ -- The structured preference registry remains the sole preference authority.
683
+ -- This migration changes only the accepted-attempt delivery boundary by
684
+ -- reconstructing the exact active descriptor set from immutable lifecycle
685
+ -- events at the turn's acceptance timestamp.
686
+ CREATE OR REPLACE FUNCTION preference_registry_canonical_snapshot_at(
687
+ p_account_id uuid,
688
+ p_workspace_id uuid,
689
+ p_initiating_human_subject_id text,
690
+ p_accepted_at timestamptz
691
+ ) RETURNS TABLE (canonical_descriptors jsonb, canonical_truncated boolean)
692
+ LANGUAGE plpgsql
693
+ STABLE
694
+ AS $$
695
+ DECLARE
696
+ canonical_descriptor jsonb;
697
+ candidate_descriptors jsonb;
698
+ BEGIN
699
+ canonical_descriptors := '[]'::jsonb;
700
+ canonical_truncated := false;
701
+
702
+ FOR canonical_descriptor IN
703
+ WITH state_event AS (
704
+ SELECT DISTINCT ON (event.preference_id)
705
+ event.preference_id,
706
+ event.type,
707
+ event.new_revision_id
708
+ FROM preference_registry_events event
709
+ WHERE event.account_id = p_account_id
710
+ AND event.created_at <= p_accepted_at
711
+ AND event.type IN (
712
+ 'proposal_created',
713
+ 'activated',
714
+ 'corrected',
715
+ 'rejected',
716
+ 'deactivated',
717
+ 'superseded'
718
+ )
719
+ ORDER BY
720
+ event.preference_id,
721
+ event.created_at DESC,
722
+ event.version DESC,
723
+ event.id DESC
724
+ ), scope_event AS (
725
+ SELECT DISTINCT ON (event.preference_id)
726
+ event.preference_id,
727
+ event.new_scope AS scope,
728
+ event.new_workspace_id AS scope_workspace_id,
729
+ event.new_subject_id AS scope_subject_id
730
+ FROM preference_registry_events event
731
+ WHERE event.account_id = p_account_id
732
+ AND event.created_at <= p_accepted_at
733
+ AND event.new_scope IS NOT NULL
734
+ ORDER BY
735
+ event.preference_id,
736
+ event.created_at DESC,
737
+ event.version DESC,
738
+ event.id DESC
739
+ ), activation_version AS (
740
+ SELECT
741
+ event.preference_id,
742
+ count(*)::integer AS value
743
+ FROM preference_registry_events event
744
+ WHERE event.account_id = p_account_id
745
+ AND event.created_at <= p_accepted_at
746
+ AND event.type IN ('activated', 'corrected', 'deactivated')
747
+ GROUP BY event.preference_id
748
+ )
749
+ SELECT jsonb_build_object(
750
+ 'id', preference.id::text,
751
+ 'stableKey', preference.stable_key,
752
+ 'title', revision.title,
753
+ 'description', revision.description,
754
+ 'scope', scope.scope,
755
+ 'activeVersion', coalesce(activation.value, 0),
756
+ 'revisionId', revision.id::text,
757
+ 'contentHash', revision.content_hash,
758
+ 'precedence', jsonb_build_object(
759
+ 'tier', scope.scope,
760
+ 'rank', revision.precedence_rank,
761
+ 'conflictStrategy', revision.conflict_strategy,
762
+ 'conflictsWith', revision.conflicts_with
763
+ ),
764
+ 'provenance', jsonb_build_object(
765
+ 'source', revision.provenance_source,
766
+ 'sourceIdHash', CASE
767
+ WHEN revision.provenance_source_id IS NULL THEN NULL
768
+ ELSE encode(
769
+ sha256(convert_to(revision.provenance_source_id, 'UTF8')),
770
+ 'hex'
771
+ )
772
+ END,
773
+ 'trust', revision.trust
774
+ ),
775
+ 'expiresAt', CASE
776
+ WHEN revision.expires_at IS NULL THEN NULL
777
+ ELSE to_char(
778
+ revision.expires_at AT TIME ZONE 'UTC',
779
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
780
+ )
781
+ END,
782
+ 'retrievalHandle',
783
+ 'preference://' || preference.id::text || '/revisions/' || revision.id::text ||
784
+ '?sha256=' || revision.content_hash
785
+ )
786
+ FROM state_event state
787
+ JOIN scope_event scope ON scope.preference_id = state.preference_id
788
+ JOIN preference_registry_preferences preference
789
+ ON preference.account_id = p_account_id
790
+ AND preference.id = state.preference_id
791
+ JOIN preference_registry_revisions revision
792
+ ON revision.account_id = preference.account_id
793
+ AND revision.preference_id = preference.id
794
+ AND revision.id = state.new_revision_id
795
+ LEFT JOIN activation_version activation
796
+ ON activation.preference_id = preference.id
797
+ WHERE state.type IN ('activated', 'corrected')
798
+ AND (revision.expires_at IS NULL OR revision.expires_at > p_accepted_at)
799
+ AND (
800
+ scope.scope = 'organization'
801
+ OR (
802
+ scope.scope = 'workspace'
803
+ AND scope.scope_workspace_id = p_workspace_id
804
+ AND scope.scope_subject_id IS NULL
805
+ )
806
+ OR (
807
+ scope.scope = 'user'
808
+ AND scope.scope_workspace_id IS NULL
809
+ AND scope.scope_subject_id = p_initiating_human_subject_id
810
+ )
811
+ )
812
+ ORDER BY CASE scope.scope
813
+ WHEN 'organization' THEN 0
814
+ WHEN 'workspace' THEN 1
815
+ WHEN 'user' THEN 2
816
+ ELSE 3
817
+ END,
818
+ revision.precedence_rank DESC,
819
+ preference.stable_key,
820
+ preference.id
821
+ LOOP
822
+ IF jsonb_array_length(canonical_descriptors) >= 64 THEN
823
+ canonical_truncated := true;
824
+ EXIT;
825
+ END IF;
826
+ candidate_descriptors := canonical_descriptors || jsonb_build_array(canonical_descriptor);
827
+ IF octet_length(convert_to(candidate_descriptors::text, 'UTF8')) > 16384 THEN
828
+ canonical_truncated := true;
829
+ EXIT;
830
+ END IF;
831
+ canonical_descriptors := candidate_descriptors;
832
+ END LOOP;
833
+
834
+ RETURN NEXT;
835
+ END;
836
+ $$;
837
+
838
+ REVOKE ALL ON FUNCTION preference_registry_canonical_snapshot_at(
839
+ uuid,
840
+ uuid,
841
+ text,
842
+ timestamptz
843
+ ) FROM PUBLIC;
844
+
845
+ CREATE OR REPLACE FUNCTION preference_registry_validate_snapshot()
846
+ RETURNS trigger
847
+ LANGUAGE plpgsql
848
+ AS $$
849
+ DECLARE
850
+ authority_subject_id text;
851
+ turn_accepted_at timestamptz;
852
+ expected_descriptors jsonb;
853
+ expected_truncated boolean;
854
+ BEGIN
855
+ SELECT
856
+ coalesce(
857
+ turn.initiating_human_subject_id,
858
+ CASE WHEN turn.initiator_kind = 'subject' THEN turn.initiator_subject_id END
859
+ ),
860
+ turn.created_at
861
+ INTO authority_subject_id, turn_accepted_at
862
+ FROM session_turn_attempts attempt
863
+ JOIN session_turns turn
864
+ ON turn.account_id = attempt.account_id
865
+ AND turn.workspace_id = attempt.workspace_id
866
+ AND turn.session_id = attempt.session_id
867
+ AND turn.id = attempt.turn_id
868
+ JOIN sessions session
869
+ ON session.account_id = attempt.account_id
870
+ AND session.workspace_id = attempt.workspace_id
871
+ AND session.id = attempt.session_id
872
+ WHERE attempt.id = NEW.attempt_id
873
+ AND attempt.account_id = NEW.account_id
874
+ AND attempt.workspace_id = NEW.workspace_id
875
+ AND attempt.session_id = NEW.session_id
876
+ AND attempt.turn_id = NEW.turn_id
877
+ AND attempt.execution_generation = NEW.execution_generation
878
+ AND turn.execution_generation = NEW.execution_generation
879
+ AND attempt.state IN ('claimed', 'running')
880
+ AND turn.active_attempt_id = attempt.id
881
+ AND session.active_turn_id = turn.id
882
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
883
+ AND NOT EXISTS (
884
+ SELECT 1
885
+ FROM session_attempt_interruptions interruption
886
+ WHERE interruption.workspace_id = NEW.workspace_id
887
+ AND interruption.attempt_id = NEW.attempt_id
888
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
889
+ );
890
+ IF authority_subject_id IS NULL
891
+ OR length(btrim(authority_subject_id)) NOT BETWEEN 1 AND 1024
892
+ OR NEW.initiating_human_subject_id IS DISTINCT FROM authority_subject_id
893
+ THEN
894
+ RAISE EXCEPTION 'preference snapshot requires exact immutable initiating-human authority'
895
+ USING ERRCODE = '23514';
896
+ END IF;
897
+
898
+ SELECT result.canonical_descriptors, result.canonical_truncated
899
+ INTO expected_descriptors, expected_truncated
900
+ FROM preference_registry_canonical_snapshot_at(
901
+ NEW.account_id,
902
+ NEW.workspace_id,
903
+ authority_subject_id,
904
+ turn_accepted_at
905
+ ) result;
906
+
907
+ IF NEW.descriptors IS DISTINCT FROM expected_descriptors
908
+ OR NEW.truncated IS DISTINCT FROM expected_truncated
909
+ OR NEW.descriptor_hash IS DISTINCT FROM encode(
910
+ sha256(convert_to(expected_descriptors::text, 'UTF8')),
911
+ 'hex'
912
+ )
913
+ THEN
914
+ RAISE EXCEPTION 'preference snapshot is not the canonical accepted-turn descriptor set'
915
+ USING ERRCODE = '23514';
916
+ END IF;
917
+
918
+ RETURN NEW;
919
+ END;
920
+ $$;
921
+
922
+ DROP TRIGGER IF EXISTS preference_registry_snapshots_validate
923
+ ON "preference_registry_snapshots";
924
+ CREATE TRIGGER preference_registry_snapshots_validate
925
+ BEFORE INSERT ON "preference_registry_snapshots"
926
+ FOR EACH ROW EXECUTE FUNCTION preference_registry_validate_snapshot();
927
+
928
+ DO $preference_snapshot_function$
929
+ DECLARE target_schema text := current_schema();
930
+ BEGIN
931
+ EXECUTE format($ddl$
932
+ CREATE OR REPLACE FUNCTION %I.preference_registry_get_or_create_snapshot(
933
+ p_account_id uuid,
934
+ p_workspace_id uuid,
935
+ p_session_id uuid,
936
+ p_turn_id uuid,
937
+ p_attempt_id uuid,
938
+ p_execution_generation integer
939
+ ) RETURNS SETOF %I.preference_registry_snapshots
940
+ LANGUAGE plpgsql SECURITY DEFINER SET search_path = %I, pg_catalog
941
+ AS $body$
942
+ DECLARE
943
+ context_account_id uuid;
944
+ context_workspace_id uuid;
945
+ authority_subject_id text;
946
+ turn_accepted_at timestamptz;
947
+ canonical_descriptors jsonb;
948
+ canonical_truncated boolean;
949
+ canonical_hash text;
950
+ snapshot_time timestamptz := transaction_timestamp();
951
+ winner_id uuid;
952
+ BEGIN
953
+ context_account_id := NULLIF(
954
+ current_setting('opengeni.account_id', true), ''
955
+ )::uuid;
956
+ context_workspace_id := NULLIF(
957
+ current_setting('opengeni.workspace_id', true), ''
958
+ )::uuid;
959
+ IF context_account_id IS DISTINCT FROM p_account_id
960
+ OR context_workspace_id IS DISTINCT FROM p_workspace_id
961
+ OR p_execution_generation < 1
962
+ THEN
963
+ RAISE EXCEPTION 'preference snapshot requires exact transaction-local tenant authority'
964
+ USING ERRCODE = '42501';
965
+ END IF;
966
+
967
+ SELECT
968
+ coalesce(
969
+ turn.initiating_human_subject_id,
970
+ CASE WHEN turn.initiator_kind = 'subject' THEN turn.initiator_subject_id END
971
+ ),
972
+ turn.created_at
973
+ INTO authority_subject_id, turn_accepted_at
974
+ FROM workspaces workspace
975
+ JOIN sessions session
976
+ ON session.account_id = workspace.account_id
977
+ AND session.workspace_id = workspace.id
978
+ JOIN session_turns turn
979
+ ON turn.account_id = session.account_id
980
+ AND turn.workspace_id = session.workspace_id
981
+ AND turn.session_id = session.id
982
+ JOIN session_turn_attempts attempt
983
+ ON attempt.account_id = turn.account_id
984
+ AND attempt.workspace_id = turn.workspace_id
985
+ AND attempt.session_id = turn.session_id
986
+ AND attempt.turn_id = turn.id
987
+ WHERE workspace.id = p_workspace_id
988
+ AND workspace.account_id = p_account_id
989
+ AND session.id = p_session_id
990
+ AND session.active_turn_id = p_turn_id
991
+ AND turn.id = p_turn_id
992
+ AND turn.active_attempt_id = p_attempt_id
993
+ AND turn.execution_generation = p_execution_generation
994
+ AND turn.status IN ('running', 'requires_action', 'recovering', 'waiting_capacity')
995
+ AND attempt.id = p_attempt_id
996
+ AND attempt.execution_generation = p_execution_generation
997
+ AND attempt.state IN ('claimed', 'running')
998
+ AND NOT EXISTS (
999
+ SELECT 1
1000
+ FROM session_attempt_interruptions interruption
1001
+ WHERE interruption.workspace_id = attempt.workspace_id
1002
+ AND interruption.attempt_id = attempt.id
1003
+ AND interruption.state IN ('pending', 'delivered', 'acknowledged')
1004
+ )
1005
+ FOR KEY SHARE OF workspace
1006
+ FOR SHARE OF session, turn
1007
+ FOR UPDATE OF attempt;
1008
+ IF authority_subject_id IS NULL
1009
+ OR length(btrim(authority_subject_id)) NOT BETWEEN 1 AND 1024
1010
+ THEN
1011
+ RAISE EXCEPTION 'preference snapshot requires an immutable initiating human'
1012
+ USING ERRCODE = '42501';
1013
+ END IF;
1014
+
1015
+ PERFORM set_config('opengeni.subject_id', authority_subject_id, true);
1016
+ IF NULLIF(current_setting('opengeni.subject_id', true), '')
1017
+ IS DISTINCT FROM authority_subject_id
1018
+ THEN
1019
+ RAISE EXCEPTION 'preference snapshot human authority was not applied'
1020
+ USING ERRCODE = '42501';
1021
+ END IF;
1022
+
1023
+ SELECT snapshot.id INTO winner_id
1024
+ FROM preference_registry_snapshots snapshot
1025
+ WHERE snapshot.account_id = p_account_id
1026
+ AND snapshot.workspace_id = p_workspace_id
1027
+ AND snapshot.session_id = p_session_id
1028
+ AND snapshot.turn_id = p_turn_id
1029
+ AND snapshot.attempt_id = p_attempt_id
1030
+ AND snapshot.execution_generation = p_execution_generation
1031
+ AND snapshot.initiating_human_subject_id = authority_subject_id
1032
+ FOR SHARE;
1033
+ IF winner_id IS NOT NULL THEN
1034
+ IF EXISTS (
1035
+ SELECT 1
1036
+ FROM preference_registry_snapshots snapshot
1037
+ WHERE snapshot.id = winner_id
1038
+ AND (
1039
+ snapshot.created_at > transaction_timestamp()
1040
+ OR jsonb_typeof(snapshot.descriptors) <> 'array'
1041
+ OR jsonb_array_length(snapshot.descriptors) > 64
1042
+ OR octet_length(convert_to(snapshot.descriptors::text, 'UTF8')) > 16384
1043
+ OR snapshot.descriptor_hash IS DISTINCT FROM encode(
1044
+ sha256(convert_to(snapshot.descriptors::text, 'UTF8')),
1045
+ 'hex'
1046
+ )
1047
+ )
1048
+ ) THEN
1049
+ RAISE EXCEPTION 'existing preference snapshot failed canonical integrity checks'
1050
+ USING ERRCODE = '23514';
1051
+ END IF;
1052
+ RETURN QUERY
1053
+ SELECT snapshot.*
1054
+ FROM preference_registry_snapshots snapshot
1055
+ WHERE snapshot.id = winner_id;
1056
+ RETURN;
1057
+ END IF;
1058
+
1059
+ SELECT result.canonical_descriptors, result.canonical_truncated
1060
+ INTO canonical_descriptors, canonical_truncated
1061
+ FROM preference_registry_canonical_snapshot_at(
1062
+ p_account_id,
1063
+ p_workspace_id,
1064
+ authority_subject_id,
1065
+ turn_accepted_at
1066
+ ) result;
1067
+ canonical_hash := encode(
1068
+ sha256(convert_to(canonical_descriptors::text, 'UTF8')),
1069
+ 'hex'
1070
+ );
1071
+
1072
+ INSERT INTO preference_registry_snapshots (
1073
+ account_id,
1074
+ workspace_id,
1075
+ session_id,
1076
+ turn_id,
1077
+ attempt_id,
1078
+ execution_generation,
1079
+ initiating_human_subject_id,
1080
+ descriptors,
1081
+ descriptor_hash,
1082
+ truncated,
1083
+ created_at
1084
+ ) VALUES (
1085
+ p_account_id,
1086
+ p_workspace_id,
1087
+ p_session_id,
1088
+ p_turn_id,
1089
+ p_attempt_id,
1090
+ p_execution_generation,
1091
+ authority_subject_id,
1092
+ canonical_descriptors,
1093
+ canonical_hash,
1094
+ canonical_truncated,
1095
+ snapshot_time
1096
+ )
1097
+ ON CONFLICT (account_id, workspace_id, attempt_id) DO NOTHING
1098
+ RETURNING id INTO winner_id;
1099
+ IF winner_id IS NULL THEN
1100
+ SELECT snapshot.id INTO winner_id
1101
+ FROM preference_registry_snapshots snapshot
1102
+ WHERE snapshot.account_id = p_account_id
1103
+ AND snapshot.workspace_id = p_workspace_id
1104
+ AND snapshot.session_id = p_session_id
1105
+ AND snapshot.turn_id = p_turn_id
1106
+ AND snapshot.attempt_id = p_attempt_id
1107
+ AND snapshot.execution_generation = p_execution_generation
1108
+ AND snapshot.initiating_human_subject_id = authority_subject_id
1109
+ FOR SHARE;
1110
+ END IF;
1111
+ IF winner_id IS NULL THEN
1112
+ RAISE EXCEPTION 'preference snapshot winner conflicts with exact attempt authority'
1113
+ USING ERRCODE = '40001';
1114
+ END IF;
1115
+ IF EXISTS (
1116
+ SELECT 1
1117
+ FROM preference_registry_snapshots snapshot
1118
+ WHERE snapshot.id = winner_id
1119
+ AND (
1120
+ snapshot.descriptors IS DISTINCT FROM canonical_descriptors
1121
+ OR snapshot.descriptor_hash IS DISTINCT FROM canonical_hash
1122
+ OR snapshot.truncated IS DISTINCT FROM canonical_truncated
1123
+ OR snapshot.created_at IS DISTINCT FROM snapshot_time
1124
+ )
1125
+ ) THEN
1126
+ RAISE EXCEPTION 'preference snapshot winner is not the canonical accepted-turn snapshot'
1127
+ USING ERRCODE = '40001';
1128
+ END IF;
1129
+ RETURN QUERY
1130
+ SELECT snapshot.*
1131
+ FROM preference_registry_snapshots snapshot
1132
+ WHERE snapshot.id = winner_id;
1133
+ END
1134
+ $body$
1135
+ $ddl$, target_schema, target_schema, target_schema);
1136
+ EXECUTE format(
1137
+ 'REVOKE ALL ON FUNCTION %I.preference_registry_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) FROM PUBLIC',
1138
+ target_schema
1139
+ );
1140
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1141
+ EXECUTE format(
1142
+ 'GRANT EXECUTE ON FUNCTION %I.preference_registry_get_or_create_snapshot(uuid, uuid, uuid, uuid, uuid, integer) TO opengeni_app',
1143
+ target_schema
1144
+ );
1145
+ END IF;
1146
+ END $preference_snapshot_function$;