@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.
- package/dist/{chunk-CYGFLLMN.js → chunk-3UCHDMKG.js} +656 -282
- package/dist/chunk-3UCHDMKG.js.map +1 -0
- package/dist/{chunk-BNGEN5QZ.js → chunk-L6ADMZHE.js} +24 -2
- package/dist/chunk-L6ADMZHE.js.map +1 -0
- package/dist/index.d.ts +42 -3
- package/dist/index.js +6919 -3760
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +3 -3
- package/dist/schema.d.ts +1416 -92
- package/dist/schema.js +11 -1
- package/dist/session-control.d.ts +7 -1
- package/dist/session-queue-commands.d.ts +8 -0
- package/dist/session-realtime-context.d.ts +56 -0
- package/dist/session-realtime-ledger.d.ts +188 -0
- package/dist/session-realtime-mirror.d.ts +30 -0
- package/dist/session-realtime-state.d.ts +2 -0
- package/dist/session-realtime-terminal.d.ts +40 -0
- package/dist/session-realtime.d.ts +59 -0
- package/dist/workspace-instruction-policies-schema.d.ts +239 -0
- package/dist/workspace-instruction-policies.d.ts +44 -0
- package/drizzle/0156_slack_reaction_trigger.sql +49 -0
- package/drizzle/0157_session_policy_role_snapshots.sql +1146 -0
- package/drizzle/0158_session_realtime_mode.sql +88 -0
- package/drizzle/0159_session_realtime_ledger.sql +198 -0
- package/drizzle/0160_session_realtime_delegation_terminal.sql +38 -0
- package/drizzle/0161_session_realtime_context_projection.sql +82 -0
- package/drizzle/0162_session_realtime_connection_promotion.sql +53 -0
- package/drizzle/0163_session_realtime_delegation_progress.sql +35 -0
- package/drizzle/0164_session_realtime_models.sql +28 -0
- package/package.json +4 -4
- package/src/index.ts +670 -79
- package/src/preference-registry.ts +11 -6
- package/src/provision-roles.ts +12 -0
- package/src/runtime-posture.ts +10 -0
- package/src/schema.ts +400 -43
- package/src/session-control.ts +596 -21
- package/src/session-queue-commands.ts +76 -7
- package/src/session-realtime-context.ts +393 -0
- package/src/session-realtime-ledger.ts +1790 -0
- package/src/session-realtime-mirror.ts +160 -0
- package/src/session-realtime-state.ts +25 -0
- package/src/session-realtime-terminal.ts +306 -0
- package/src/session-realtime.ts +611 -0
- package/src/workspace-instruction-policies-schema.ts +41 -0
- package/src/workspace-instruction-policies.ts +131 -2
- package/dist/chunk-BNGEN5QZ.js.map +0 -1
- 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$;
|