@opengeni/db 3.5.2-canary.0 → 3.6.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 (47) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/{chunk-NRGUX3M5.js → chunk-2NT476HD.js} +2 -2
  3. package/dist/{chunk-PERPE2UY.js → chunk-744DFC65.js} +31 -1
  4. package/dist/chunk-744DFC65.js.map +1 -0
  5. package/dist/{chunk-FJHEWYV5.js → chunk-CDMMSUWJ.js} +3 -3
  6. package/dist/{chunk-TRCYF26N.js → chunk-G3732M3T.js} +2 -2
  7. package/dist/{chunk-ORKVXHL2.js → chunk-IW6O6G7R.js} +2 -2
  8. package/dist/{chunk-Y44FKTYU.js → chunk-Q5YUGQVK.js} +3 -3
  9. package/dist/{chunk-U7NKCX5E.js → chunk-UGDVSQ6U.js} +2 -2
  10. package/dist/{chunk-6NYKC5DD.js → chunk-UJ2EW3QI.js} +4729 -4583
  11. package/dist/chunk-UJ2EW3QI.js.map +1 -0
  12. package/dist/{chunk-X6T2P3L3.js → chunk-WW7AJ5IC.js} +2 -2
  13. package/dist/editable-artifact-durable-export.js +2 -2
  14. package/dist/editable-artifacts.js +3 -3
  15. package/dist/index.d.ts +23 -3
  16. package/dist/index.js +2112 -1436
  17. package/dist/index.js.map +1 -1
  18. package/dist/managed-auth-session-sets.js +3 -3
  19. package/dist/provision-roles.js +1 -1
  20. package/dist/runtime-posture.d.ts +5 -4
  21. package/dist/schema.d.ts +7 -6
  22. package/dist/schema.js +7 -1
  23. package/dist/session-background-commands.js +3 -3
  24. package/dist/session-tenancy.js +3 -3
  25. package/dist/video-generation.js +3 -3
  26. package/dist/work-claims-schema.d.ts +975 -0
  27. package/dist/work-claims.d.ts +83 -0
  28. package/drizzle/0365_permission_scoped_work_claims.sql +1306 -0
  29. package/drizzle/0366_session_discovery_title_search_index.sql +5 -0
  30. package/drizzle/0367_session_discovery_goal_search_index.sql +5 -0
  31. package/drizzle/0368_session_discovery_claim_search_index.sql +6 -0
  32. package/package.json +6 -6
  33. package/src/index.ts +1051 -330
  34. package/src/provision-roles.ts +18 -0
  35. package/src/runtime-posture.ts +12 -0
  36. package/src/schema.ts +1 -0
  37. package/src/work-claims-schema.ts +148 -0
  38. package/src/work-claims.ts +200 -0
  39. package/dist/chunk-6NYKC5DD.js.map +0 -1
  40. package/dist/chunk-PERPE2UY.js.map +0 -1
  41. /package/dist/{chunk-NRGUX3M5.js.map → chunk-2NT476HD.js.map} +0 -0
  42. /package/dist/{chunk-FJHEWYV5.js.map → chunk-CDMMSUWJ.js.map} +0 -0
  43. /package/dist/{chunk-TRCYF26N.js.map → chunk-G3732M3T.js.map} +0 -0
  44. /package/dist/{chunk-ORKVXHL2.js.map → chunk-IW6O6G7R.js.map} +0 -0
  45. /package/dist/{chunk-Y44FKTYU.js.map → chunk-Q5YUGQVK.js.map} +0 -0
  46. /package/dist/{chunk-U7NKCX5E.js.map → chunk-UGDVSQ6U.js.map} +0 -0
  47. /package/dist/{chunk-X6T2P3L3.js.map → chunk-WW7AJ5IC.js.map} +0 -0
@@ -0,0 +1,1306 @@
1
+ -- deployment-mode: rolling
2
+ -- Durable, non-exclusive work claims for permission-scoped discovery. Claims
3
+ -- are evidence only: they grant no authority, acquire no lock/lease, and never
4
+ -- block another session from claiming the same typed subject.
5
+
6
+ SET LOCAL lock_timeout = '5s';
7
+ SET LOCAL statement_timeout = '10min';
8
+
9
+ -- Freeze the active data schema into every SECURITY DEFINER routine. This is
10
+ -- public for standalone installs and the host-selected schema when embedded.
11
+ SELECT pg_catalog.set_config(
12
+ 'search_path',
13
+ pg_catalog.format('%I, pg_catalog, pg_temp', pg_catalog.current_schema()),
14
+ true
15
+ );
16
+
17
+ CREATE TABLE "session_work_claims" (
18
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
19
+ "account_id" uuid NOT NULL,
20
+ "workspace_id" uuid NOT NULL,
21
+ "session_id" uuid NOT NULL,
22
+ "root_session_id" uuid NOT NULL,
23
+ "subject_namespace" text NOT NULL,
24
+ "subject_type" text NOT NULL,
25
+ "canonical_key" text NOT NULL,
26
+ "subject_digest" text NOT NULL,
27
+ "display_label" text,
28
+ "role" text NOT NULL,
29
+ "state" text DEFAULT 'active' NOT NULL,
30
+ "revision" integer DEFAULT 1 NOT NULL,
31
+ "provenance" text NOT NULL,
32
+ "version_kind" text,
33
+ "version_value" text,
34
+ "observed_at" timestamptz NOT NULL,
35
+ "created_at" timestamptz DEFAULT now() NOT NULL,
36
+ "updated_at" timestamptz DEFAULT now() NOT NULL,
37
+ "settled_at" timestamptz,
38
+ CONSTRAINT "session_work_claims_workspace_account_fk"
39
+ FOREIGN KEY ("workspace_id", "account_id")
40
+ REFERENCES "workspaces" ("id", "account_id") ON DELETE CASCADE,
41
+ CONSTRAINT "session_work_claims_session_fk"
42
+ FOREIGN KEY ("workspace_id", "session_id")
43
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
44
+ CONSTRAINT "session_work_claims_root_session_fk"
45
+ FOREIGN KEY ("workspace_id", "root_session_id")
46
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
47
+ CONSTRAINT "session_work_claims_namespace_check" CHECK (
48
+ "subject_namespace" = lower(btrim("subject_namespace"))
49
+ AND octet_length("subject_namespace") BETWEEN 1 AND 64
50
+ AND "subject_namespace" ~ '^[a-z0-9]([a-z0-9._:-]{0,62}[a-z0-9])?$'
51
+ ),
52
+ CONSTRAINT "session_work_claims_type_check" CHECK (
53
+ "subject_type" IN (
54
+ 'repository','branch','pull_request','issue','artifact','release','ci_run','other'
55
+ )
56
+ ),
57
+ CONSTRAINT "session_work_claims_key_check" CHECK (
58
+ "canonical_key" = btrim("canonical_key")
59
+ AND "canonical_key" = normalize("canonical_key", NFKC)
60
+ AND octet_length("canonical_key") BETWEEN 1 AND 512
61
+ AND "canonical_key" !~ '[[:cntrl:]]'
62
+ ),
63
+ CONSTRAINT "session_work_claims_digest_check" CHECK (
64
+ "subject_digest" ~ '^[0-9a-f]{64}$'
65
+ ),
66
+ CONSTRAINT "session_work_claims_label_check" CHECK (
67
+ "display_label" IS NULL OR (
68
+ "display_label" = btrim("display_label")
69
+ AND "display_label" = normalize("display_label", NFKC)
70
+ AND octet_length("display_label") BETWEEN 1 AND 256
71
+ AND "display_label" !~ '[[:cntrl:]]'
72
+ )
73
+ ),
74
+ CONSTRAINT "session_work_claims_role_check" CHECK (
75
+ "role" IN ('working','reviewing','monitoring','delivering')
76
+ ),
77
+ CONSTRAINT "session_work_claims_state_check" CHECK (
78
+ "state" IN ('active','released','superseded','stale')
79
+ ),
80
+ CONSTRAINT "session_work_claims_revision_check" CHECK ("revision" > 0),
81
+ CONSTRAINT "session_work_claims_provenance_check" CHECK (
82
+ "provenance" IN (
83
+ 'explicit_agent','user_api','trusted_integration','session_resource','system_lifecycle'
84
+ )
85
+ ),
86
+ CONSTRAINT "session_work_claims_version_check" CHECK (
87
+ ("version_kind" IS NULL AND "version_value" IS NULL)
88
+ OR (
89
+ "version_kind" IN (
90
+ 'git_commit','branch_head','pull_request_head','artifact_version',
91
+ 'release_version','ci_run','other'
92
+ )
93
+ AND "version_value" = btrim("version_value")
94
+ AND "version_value" = normalize("version_value", NFKC)
95
+ AND octet_length("version_value") BETWEEN 1 AND 256
96
+ AND "version_value" !~ '[[:cntrl:]]'
97
+ )
98
+ ),
99
+ CONSTRAINT "session_work_claims_lifecycle_check" CHECK (
100
+ ("state" = 'active' AND "settled_at" IS NULL)
101
+ OR ("state" <> 'active' AND "settled_at" IS NOT NULL)
102
+ ),
103
+ CONSTRAINT "session_work_claims_timestamp_check" CHECK (
104
+ "observed_at" >= "created_at"
105
+ AND "updated_at" >= "observed_at"
106
+ AND ("settled_at" IS NULL OR "settled_at" >= "created_at")
107
+ )
108
+ );
109
+
110
+ CREATE UNIQUE INDEX "session_work_claims_workspace_id_uq"
111
+ ON "session_work_claims" ("workspace_id", "id");
112
+ CREATE UNIQUE INDEX "session_work_claims_active_identity_uq"
113
+ ON "session_work_claims" (
114
+ "workspace_id", "session_id", "subject_namespace", "subject_type", "subject_digest", "role"
115
+ ) WHERE "state" = 'active';
116
+ CREATE INDEX "session_work_claims_session_state_idx"
117
+ ON "session_work_claims" (
118
+ "workspace_id", "session_id", "state", "updated_at" DESC, "id" DESC
119
+ );
120
+ CREATE INDEX "session_work_claims_subject_state_idx"
121
+ ON "session_work_claims" (
122
+ "workspace_id", "subject_namespace", "subject_type", "subject_digest", "state",
123
+ "updated_at" DESC, "id" DESC
124
+ );
125
+
126
+ CREATE TABLE "session_work_claim_revisions" (
127
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
128
+ "account_id" uuid NOT NULL,
129
+ "workspace_id" uuid NOT NULL,
130
+ "claim_id" uuid NOT NULL,
131
+ "session_id" uuid NOT NULL,
132
+ "root_session_id" uuid NOT NULL,
133
+ "operation_id" uuid NOT NULL,
134
+ "input_hash" text NOT NULL,
135
+ "mutation_kind" text NOT NULL,
136
+ "prior_revision" integer,
137
+ "resulting_revision" integer NOT NULL,
138
+ "subject_namespace" text NOT NULL,
139
+ "subject_type" text NOT NULL,
140
+ "canonical_key" text NOT NULL,
141
+ "subject_digest" text NOT NULL,
142
+ "display_label" text,
143
+ "role" text NOT NULL,
144
+ "state" text NOT NULL,
145
+ "provenance" text NOT NULL,
146
+ "version_kind" text,
147
+ "version_value" text,
148
+ "observed_at" timestamptz NOT NULL,
149
+ "claim_created_at" timestamptz NOT NULL,
150
+ "claim_updated_at" timestamptz NOT NULL,
151
+ "settled_at" timestamptz,
152
+ "actor_kind" text NOT NULL,
153
+ "actor_subject_id" text NOT NULL,
154
+ "actor_session_id" uuid,
155
+ "actor_turn_id" uuid,
156
+ "actor_attempt_id" uuid,
157
+ "actor_execution_generation" integer,
158
+ "reason" text,
159
+ "created_at" timestamptz DEFAULT now() NOT NULL,
160
+ CONSTRAINT "session_work_claim_revisions_workspace_account_fk"
161
+ FOREIGN KEY ("workspace_id", "account_id")
162
+ REFERENCES "workspaces" ("id", "account_id") ON DELETE CASCADE,
163
+ CONSTRAINT "session_work_claim_revisions_claim_fk"
164
+ FOREIGN KEY ("workspace_id", "claim_id")
165
+ REFERENCES "session_work_claims" ("workspace_id", "id") ON DELETE CASCADE,
166
+ CONSTRAINT "session_work_claim_revisions_session_fk"
167
+ FOREIGN KEY ("workspace_id", "session_id")
168
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
169
+ CONSTRAINT "session_work_claim_revisions_root_session_fk"
170
+ FOREIGN KEY ("workspace_id", "root_session_id")
171
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE CASCADE,
172
+ CONSTRAINT "session_work_claim_revisions_actor_session_fk"
173
+ FOREIGN KEY ("workspace_id", "actor_session_id")
174
+ REFERENCES "sessions" ("workspace_id", "id") ON DELETE RESTRICT,
175
+ CONSTRAINT "session_work_claim_revisions_actor_turn_fk"
176
+ FOREIGN KEY ("workspace_id", "actor_turn_id")
177
+ REFERENCES "session_turns" ("workspace_id", "id") ON DELETE RESTRICT,
178
+ CONSTRAINT "session_work_claim_revisions_actor_attempt_fk"
179
+ FOREIGN KEY ("workspace_id", "actor_attempt_id")
180
+ REFERENCES "session_turn_attempts" ("workspace_id", "id") ON DELETE RESTRICT,
181
+ CONSTRAINT "session_work_claim_revisions_hash_check" CHECK (
182
+ "input_hash" ~ '^[0-9a-f]{64}$' AND "subject_digest" ~ '^[0-9a-f]{64}$'
183
+ ),
184
+ CONSTRAINT "session_work_claim_revisions_mutation_check" CHECK (
185
+ "mutation_kind" IN ('created','updated','released','superseded','stale')
186
+ AND (("mutation_kind" = 'created' AND "prior_revision" IS NULL AND "resulting_revision" = 1)
187
+ OR ("mutation_kind" <> 'created' AND "prior_revision" > 0
188
+ AND "resulting_revision" = "prior_revision" + 1))
189
+ ),
190
+ CONSTRAINT "session_work_claim_revisions_actor_check" CHECK (
191
+ "actor_kind" IN ('agent_attempt','human','integration','system')
192
+ AND octet_length("actor_subject_id") BETWEEN 1 AND 4096
193
+ AND (
194
+ ("actor_kind" = 'agent_attempt'
195
+ AND "actor_session_id" IS NOT NULL
196
+ AND "actor_turn_id" IS NOT NULL
197
+ AND "actor_attempt_id" IS NOT NULL
198
+ AND "actor_execution_generation" > 0)
199
+ OR
200
+ ("actor_kind" <> 'agent_attempt'
201
+ AND "actor_attempt_id" IS NULL
202
+ AND "actor_execution_generation" IS NULL)
203
+ )
204
+ ),
205
+ CONSTRAINT "session_work_claim_revisions_reason_check" CHECK (
206
+ "reason" IS NULL OR "reason" IN (
207
+ 'completed','cancelled','failed','superseded','no_longer_active',
208
+ 'corrected','external_state_changed','other'
209
+ )
210
+ )
211
+ );
212
+
213
+ CREATE UNIQUE INDEX "session_work_claim_revisions_workspace_operation_uq"
214
+ ON "session_work_claim_revisions" ("workspace_id", "operation_id");
215
+ CREATE UNIQUE INDEX "session_work_claim_revisions_claim_revision_uq"
216
+ ON "session_work_claim_revisions" ("workspace_id", "claim_id", "resulting_revision");
217
+ CREATE INDEX "session_work_claim_revisions_session_timeline_idx"
218
+ ON "session_work_claim_revisions" (
219
+ "workspace_id", "session_id", "created_at" DESC, "id" DESC
220
+ );
221
+
222
+ CREATE TABLE "session_work_claim_write_capabilities" (
223
+ "backend_pid" integer NOT NULL,
224
+ "transaction_id" xid8 NOT NULL,
225
+ "capability_id" uuid NOT NULL,
226
+ PRIMARY KEY ("backend_pid", "transaction_id", "capability_id")
227
+ );
228
+
229
+ ALTER TABLE "session_work_claims" ENABLE ROW LEVEL SECURITY;
230
+ ALTER TABLE "session_work_claims" FORCE ROW LEVEL SECURITY;
231
+ ALTER TABLE "session_work_claim_revisions" ENABLE ROW LEVEL SECURITY;
232
+ ALTER TABLE "session_work_claim_revisions" FORCE ROW LEVEL SECURITY;
233
+ ALTER TABLE "session_work_claim_write_capabilities" ENABLE ROW LEVEL SECURITY;
234
+ ALTER TABLE "session_work_claim_write_capabilities" FORCE ROW LEVEL SECURITY;
235
+
236
+ CREATE OR REPLACE FUNCTION opengeni_private.session_work_claim_capability_active()
237
+ RETURNS boolean
238
+ LANGUAGE sql
239
+ STABLE
240
+ SECURITY DEFINER
241
+ SET search_path FROM CURRENT
242
+ AS $$
243
+ SELECT EXISTS (
244
+ SELECT 1 FROM session_work_claim_write_capabilities capability
245
+ WHERE capability.backend_pid = pg_backend_pid()
246
+ AND capability.transaction_id = pg_current_xact_id_if_assigned()
247
+ AND capability.capability_id = nullif(
248
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
249
+ )::uuid
250
+ )
251
+ $$;
252
+ REVOKE ALL ON FUNCTION opengeni_private.session_work_claim_capability_active() FROM PUBLIC;
253
+
254
+ DO $work_claim_policies$
255
+ DECLARE
256
+ data_schema text := current_schema();
257
+ migration_owner text := current_user;
258
+ BEGIN
259
+ EXECUTE format(
260
+ 'CREATE POLICY session_work_claim_capability_owner ON %I.session_work_claim_write_capabilities '
261
+ || 'FOR ALL USING (current_user = %L) WITH CHECK (current_user = %L)',
262
+ data_schema, migration_owner, migration_owner
263
+ );
264
+ END
265
+ $work_claim_policies$;
266
+
267
+ CREATE POLICY session_work_claims_tenant ON "session_work_claims"
268
+ USING (
269
+ "account_id" = opengeni_private.current_account_id()
270
+ AND "workspace_id" = opengeni_private.current_workspace_id()
271
+ )
272
+ WITH CHECK (
273
+ "account_id" = opengeni_private.current_account_id()
274
+ AND "workspace_id" = opengeni_private.current_workspace_id()
275
+ );
276
+ CREATE POLICY session_visibility_isolation ON "session_work_claims" AS RESTRICTIVE
277
+ FOR SELECT
278
+ USING (
279
+ session_reference_visible("account_id", "workspace_id", "session_id")
280
+ OR opengeni_private.session_work_claim_capability_active()
281
+ )
282
+ ;
283
+ CREATE POLICY session_work_claims_lifecycle_insert ON "session_work_claims" AS RESTRICTIVE
284
+ FOR INSERT WITH CHECK (EXISTS (
285
+ SELECT 1 FROM "session_work_claim_write_capabilities" capability
286
+ WHERE capability."backend_pid" = pg_backend_pid()
287
+ AND capability."transaction_id" = pg_current_xact_id()
288
+ AND capability."capability_id" = nullif(
289
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
290
+ )::uuid
291
+ ));
292
+ CREATE POLICY session_work_claims_lifecycle_update ON "session_work_claims" AS RESTRICTIVE
293
+ FOR UPDATE USING (EXISTS (
294
+ SELECT 1 FROM "session_work_claim_write_capabilities" capability
295
+ WHERE capability."backend_pid" = pg_backend_pid()
296
+ AND capability."transaction_id" = pg_current_xact_id()
297
+ AND capability."capability_id" = nullif(
298
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
299
+ )::uuid
300
+ )) WITH CHECK (EXISTS (
301
+ SELECT 1 FROM "session_work_claim_write_capabilities" capability
302
+ WHERE capability."backend_pid" = pg_backend_pid()
303
+ AND capability."transaction_id" = pg_current_xact_id()
304
+ AND capability."capability_id" = nullif(
305
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
306
+ )::uuid
307
+ ));
308
+ CREATE POLICY session_work_claims_lifecycle_delete ON "session_work_claims" AS RESTRICTIVE
309
+ FOR DELETE USING (EXISTS (
310
+ SELECT 1 FROM "session_work_claim_write_capabilities" capability
311
+ WHERE capability."backend_pid" = pg_backend_pid()
312
+ AND capability."transaction_id" = pg_current_xact_id()
313
+ AND capability."capability_id" = nullif(
314
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
315
+ )::uuid
316
+ ));
317
+
318
+ CREATE POLICY session_work_claim_revisions_tenant ON "session_work_claim_revisions"
319
+ USING (
320
+ "account_id" = opengeni_private.current_account_id()
321
+ AND "workspace_id" = opengeni_private.current_workspace_id()
322
+ )
323
+ WITH CHECK (
324
+ "account_id" = opengeni_private.current_account_id()
325
+ AND "workspace_id" = opengeni_private.current_workspace_id()
326
+ );
327
+ CREATE POLICY session_visibility_isolation ON "session_work_claim_revisions" AS RESTRICTIVE
328
+ FOR SELECT
329
+ USING (
330
+ session_reference_visible("account_id", "workspace_id", "session_id")
331
+ OR opengeni_private.session_work_claim_capability_active()
332
+ )
333
+ ;
334
+ CREATE POLICY session_work_claim_revisions_lifecycle_insert
335
+ ON "session_work_claim_revisions" AS RESTRICTIVE
336
+ FOR INSERT WITH CHECK (EXISTS (
337
+ SELECT 1 FROM "session_work_claim_write_capabilities" capability
338
+ WHERE capability."backend_pid" = pg_backend_pid()
339
+ AND capability."transaction_id" = pg_current_xact_id()
340
+ AND capability."capability_id" = nullif(
341
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
342
+ )::uuid
343
+ ));
344
+ CREATE POLICY session_work_claim_revisions_immutable_update
345
+ ON "session_work_claim_revisions" AS RESTRICTIVE
346
+ FOR UPDATE USING (false) WITH CHECK (false);
347
+ CREATE POLICY session_work_claim_revisions_immutable_delete
348
+ ON "session_work_claim_revisions" AS RESTRICTIVE
349
+ FOR DELETE USING (false);
350
+
351
+ CREATE OR REPLACE FUNCTION session_work_claim_subject_digest(
352
+ p_namespace text, p_type text, p_canonical_key text
353
+ ) RETURNS text
354
+ LANGUAGE sql
355
+ IMMUTABLE
356
+ STRICT
357
+ SET search_path FROM CURRENT
358
+ AS $$
359
+ SELECT encode(sha256(convert_to(
360
+ p_namespace || chr(31) || p_type || chr(31) || p_canonical_key,
361
+ 'UTF8'
362
+ )), 'hex')
363
+ $$;
364
+
365
+ CREATE OR REPLACE FUNCTION guard_session_work_claim_mutation()
366
+ RETURNS trigger
367
+ LANGUAGE plpgsql
368
+ SECURITY DEFINER
369
+ SET search_path FROM CURRENT
370
+ AS $$
371
+ DECLARE
372
+ has_capability boolean;
373
+ BEGIN
374
+ IF TG_OP = 'DELETE'
375
+ AND pg_trigger_depth() > 1
376
+ AND (
377
+ NOT EXISTS (SELECT 1 FROM workspaces WHERE id = OLD.workspace_id)
378
+ OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.session_id)
379
+ )
380
+ THEN
381
+ RETURN OLD;
382
+ END IF;
383
+ SELECT EXISTS (
384
+ SELECT 1 FROM session_work_claim_write_capabilities capability
385
+ WHERE capability.backend_pid = pg_backend_pid()
386
+ AND capability.transaction_id = pg_current_xact_id()
387
+ AND capability.capability_id = nullif(
388
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
389
+ )::uuid
390
+ ) INTO has_capability;
391
+ IF NOT has_capability THEN
392
+ RAISE EXCEPTION 'work claim mutation requires lifecycle authority'
393
+ USING ERRCODE = '42501';
394
+ END IF;
395
+ IF TG_OP = 'DELETE' THEN
396
+ RAISE EXCEPTION 'work claims are settled, never deleted'
397
+ USING ERRCODE = '42501';
398
+ END IF;
399
+ IF NEW.subject_digest <> session_work_claim_subject_digest(
400
+ NEW.subject_namespace, NEW.subject_type, NEW.canonical_key
401
+ ) THEN
402
+ RAISE EXCEPTION 'work claim subject digest is invalid' USING ERRCODE = '23514';
403
+ END IF;
404
+ IF TG_OP = 'INSERT' THEN
405
+ IF NEW.state <> 'active' OR NEW.revision <> 1 OR NEW.settled_at IS NOT NULL THEN
406
+ RAISE EXCEPTION 'work claim create projection is invalid' USING ERRCODE = '23514';
407
+ END IF;
408
+ RETURN NEW;
409
+ END IF;
410
+ IF NEW.account_id IS DISTINCT FROM OLD.account_id
411
+ OR NEW.workspace_id IS DISTINCT FROM OLD.workspace_id
412
+ OR NEW.session_id IS DISTINCT FROM OLD.session_id
413
+ OR NEW.root_session_id IS DISTINCT FROM OLD.root_session_id
414
+ OR NEW.subject_namespace IS DISTINCT FROM OLD.subject_namespace
415
+ OR NEW.subject_type IS DISTINCT FROM OLD.subject_type
416
+ OR NEW.canonical_key IS DISTINCT FROM OLD.canonical_key
417
+ OR NEW.subject_digest IS DISTINCT FROM OLD.subject_digest
418
+ OR NEW.role IS DISTINCT FROM OLD.role
419
+ OR NEW.provenance IS DISTINCT FROM OLD.provenance
420
+ OR NEW.created_at IS DISTINCT FROM OLD.created_at
421
+ OR OLD.state <> 'active'
422
+ OR NEW.revision <> OLD.revision + 1
423
+ OR NEW.updated_at <= OLD.updated_at
424
+ THEN
425
+ RAISE EXCEPTION 'work claim update violates immutable identity or revision fencing'
426
+ USING ERRCODE = '23514';
427
+ END IF;
428
+ IF NEW.state = 'active' THEN
429
+ IF NEW.settled_at IS NOT NULL OR NEW.observed_at < OLD.observed_at THEN
430
+ RAISE EXCEPTION 'active work claim refresh is invalid' USING ERRCODE = '23514';
431
+ END IF;
432
+ ELSIF NEW.state IN ('released','superseded','stale') THEN
433
+ IF NEW.settled_at IS NULL
434
+ OR NEW.display_label IS DISTINCT FROM OLD.display_label
435
+ OR NEW.version_kind IS DISTINCT FROM OLD.version_kind
436
+ OR NEW.version_value IS DISTINCT FROM OLD.version_value
437
+ OR NEW.observed_at IS DISTINCT FROM OLD.observed_at
438
+ THEN
439
+ RAISE EXCEPTION 'work claim settlement may only change lifecycle fields'
440
+ USING ERRCODE = '23514';
441
+ END IF;
442
+ ELSE
443
+ RAISE EXCEPTION 'work claim state transition is invalid' USING ERRCODE = '23514';
444
+ END IF;
445
+ RETURN NEW;
446
+ END;
447
+ $$;
448
+
449
+ CREATE TRIGGER session_work_claims_guard_mutation
450
+ BEFORE INSERT OR UPDATE OR DELETE ON "session_work_claims"
451
+ FOR EACH ROW EXECUTE FUNCTION guard_session_work_claim_mutation();
452
+
453
+ CREATE OR REPLACE FUNCTION guard_session_work_claim_revision_mutation()
454
+ RETURNS trigger
455
+ LANGUAGE plpgsql
456
+ SECURITY DEFINER
457
+ SET search_path FROM CURRENT
458
+ AS $$
459
+ DECLARE
460
+ claim_row session_work_claims%ROWTYPE;
461
+ BEGIN
462
+ IF TG_OP = 'DELETE'
463
+ AND pg_trigger_depth() > 1
464
+ AND (
465
+ NOT EXISTS (SELECT 1 FROM workspaces WHERE id = OLD.workspace_id)
466
+ OR NOT EXISTS (SELECT 1 FROM sessions WHERE id = OLD.session_id)
467
+ OR NOT EXISTS (
468
+ SELECT 1 FROM session_work_claims
469
+ WHERE workspace_id = OLD.workspace_id AND id = OLD.claim_id
470
+ )
471
+ )
472
+ THEN
473
+ RETURN OLD;
474
+ END IF;
475
+ IF TG_OP <> 'INSERT' THEN
476
+ RAISE EXCEPTION 'work claim revisions are immutable' USING ERRCODE = '42501';
477
+ END IF;
478
+ IF NOT EXISTS (
479
+ SELECT 1 FROM session_work_claim_write_capabilities capability
480
+ WHERE capability.backend_pid = pg_backend_pid()
481
+ AND capability.transaction_id = pg_current_xact_id()
482
+ AND capability.capability_id = nullif(
483
+ current_setting('opengeni.session_work_claim_write_capability', true), ''
484
+ )::uuid
485
+ ) THEN
486
+ RAISE EXCEPTION 'work claim revision requires lifecycle authority'
487
+ USING ERRCODE = '42501';
488
+ END IF;
489
+ SELECT * INTO STRICT claim_row FROM session_work_claims claim
490
+ WHERE claim.workspace_id = NEW.workspace_id AND claim.id = NEW.claim_id;
491
+ IF NEW.account_id IS DISTINCT FROM claim_row.account_id
492
+ OR NEW.session_id IS DISTINCT FROM claim_row.session_id
493
+ OR NEW.root_session_id IS DISTINCT FROM claim_row.root_session_id
494
+ OR NEW.resulting_revision IS DISTINCT FROM claim_row.revision
495
+ OR NEW.subject_namespace IS DISTINCT FROM claim_row.subject_namespace
496
+ OR NEW.subject_type IS DISTINCT FROM claim_row.subject_type
497
+ OR NEW.canonical_key IS DISTINCT FROM claim_row.canonical_key
498
+ OR NEW.subject_digest IS DISTINCT FROM claim_row.subject_digest
499
+ OR NEW.display_label IS DISTINCT FROM claim_row.display_label
500
+ OR NEW.role IS DISTINCT FROM claim_row.role
501
+ OR NEW.state IS DISTINCT FROM claim_row.state
502
+ OR NEW.provenance IS DISTINCT FROM claim_row.provenance
503
+ OR NEW.version_kind IS DISTINCT FROM claim_row.version_kind
504
+ OR NEW.version_value IS DISTINCT FROM claim_row.version_value
505
+ OR NEW.observed_at IS DISTINCT FROM claim_row.observed_at
506
+ OR NEW.claim_created_at IS DISTINCT FROM claim_row.created_at
507
+ OR NEW.claim_updated_at IS DISTINCT FROM claim_row.updated_at
508
+ OR NEW.settled_at IS DISTINCT FROM claim_row.settled_at
509
+ THEN
510
+ RAISE EXCEPTION 'work claim revision does not match its durable head receipt'
511
+ USING ERRCODE = '23514';
512
+ END IF;
513
+ RETURN NEW;
514
+ END;
515
+ $$;
516
+
517
+ CREATE TRIGGER session_work_claim_revisions_guard_mutation
518
+ BEFORE INSERT OR UPDATE OR DELETE ON "session_work_claim_revisions"
519
+ FOR EACH ROW EXECUTE FUNCTION guard_session_work_claim_revision_mutation();
520
+
521
+ CREATE OR REPLACE FUNCTION resolve_session_work_claim_attempt_authority(
522
+ p_account_id uuid,
523
+ p_workspace_id uuid,
524
+ p_session_id uuid,
525
+ p_turn_id uuid,
526
+ p_attempt_id uuid,
527
+ p_execution_generation integer
528
+ ) RETURNS TABLE (
529
+ root_session_id uuid,
530
+ actor_subject_id text
531
+ )
532
+ LANGUAGE plpgsql
533
+ SECURITY DEFINER
534
+ SET search_path FROM CURRENT
535
+ AS $$
536
+ DECLARE
537
+ session_row sessions%ROWTYPE;
538
+ turn_row session_turns%ROWTYPE;
539
+ previous_subject_id text := current_setting('opengeni.subject_id', true);
540
+ previous_initiating_human_subject_id text := current_setting(
541
+ 'opengeni.initiating_human_subject_id', true
542
+ );
543
+ visibility_context_set boolean := false;
544
+ BEGIN
545
+ IF p_account_id IS NULL OR p_workspace_id IS NULL OR p_session_id IS NULL
546
+ OR p_turn_id IS NULL OR p_attempt_id IS NULL
547
+ OR p_execution_generation IS NULL OR p_execution_generation < 1
548
+ OR p_account_id IS DISTINCT FROM opengeni_private.current_account_id()
549
+ OR p_workspace_id IS DISTINCT FROM opengeni_private.current_workspace_id()
550
+ THEN
551
+ RAISE EXCEPTION 'work claims require exact tenant and attempt authority'
552
+ USING ERRCODE = '42501';
553
+ END IF;
554
+
555
+ PERFORM 1 FROM workspaces workspace
556
+ WHERE workspace.account_id = p_account_id AND workspace.id = p_workspace_id
557
+ FOR KEY SHARE;
558
+ IF NOT FOUND THEN
559
+ RAISE EXCEPTION 'work claim workspace is unavailable' USING ERRCODE = '42501';
560
+ END IF;
561
+
562
+ SELECT * INTO session_row FROM sessions session
563
+ WHERE session.account_id = p_account_id
564
+ AND session.workspace_id = p_workspace_id
565
+ AND session.id = p_session_id
566
+ FOR NO KEY UPDATE;
567
+ IF NOT FOUND OR session_row.status IN ('failed','cancelled') THEN
568
+ RAISE EXCEPTION 'work claim session is unavailable' USING ERRCODE = '42501';
569
+ END IF;
570
+ IF EXISTS (
571
+ SELECT 1 FROM session_goals goal
572
+ WHERE goal.account_id = p_account_id
573
+ AND goal.workspace_id = p_workspace_id
574
+ AND goal.session_id = p_session_id
575
+ AND goal.status = 'completed'
576
+ ) THEN
577
+ RAISE EXCEPTION 'completed session goals cannot create or mutate work claims'
578
+ USING ERRCODE = '42501';
579
+ END IF;
580
+
581
+ SELECT turn.* INTO turn_row FROM session_turns turn
582
+ WHERE turn.account_id = p_account_id
583
+ AND turn.workspace_id = p_workspace_id
584
+ AND turn.session_id = p_session_id
585
+ AND turn.id = p_turn_id
586
+ AND turn.active_attempt_id = p_attempt_id
587
+ AND session_row.active_turn_id = p_turn_id
588
+ AND turn.execution_generation = p_execution_generation
589
+ AND turn.status IN ('running','requires_action','recovering','waiting_capacity')
590
+ FOR UPDATE;
591
+ IF NOT FOUND THEN
592
+ RAISE EXCEPTION 'work claims require the exact current turn' USING ERRCODE = '42501';
593
+ END IF;
594
+
595
+ PERFORM 1 FROM session_turn_attempts attempt
596
+ WHERE attempt.account_id = p_account_id
597
+ AND attempt.workspace_id = p_workspace_id
598
+ AND attempt.session_id = p_session_id
599
+ AND attempt.turn_id = p_turn_id
600
+ AND attempt.id = p_attempt_id
601
+ AND attempt.execution_generation = p_execution_generation
602
+ AND attempt.state IN ('claimed','running')
603
+ AND NOT EXISTS (
604
+ SELECT 1 FROM session_attempt_interruptions interruption
605
+ WHERE interruption.workspace_id = attempt.workspace_id
606
+ AND interruption.attempt_id = attempt.id
607
+ AND interruption.state IN ('pending','delivered','acknowledged')
608
+ )
609
+ FOR UPDATE;
610
+ IF NOT FOUND THEN
611
+ RAISE EXCEPTION 'work claims require the exact current attempt' USING ERRCODE = '42501';
612
+ END IF;
613
+
614
+ PERFORM set_config(
615
+ 'opengeni.subject_id',
616
+ CASE WHEN turn_row.initiator_kind = 'subject' THEN turn_row.initiator_subject_id ELSE '' END,
617
+ true
618
+ );
619
+ PERFORM set_config(
620
+ 'opengeni.initiating_human_subject_id',
621
+ coalesce(turn_row.initiating_human_subject_id, ''),
622
+ true
623
+ );
624
+ visibility_context_set := true;
625
+ IF NOT (
626
+ session_row.visibility = 'workspace_shared'
627
+ OR session_private_actor_visible(
628
+ session_row.account_id,
629
+ session_row.workspace_id,
630
+ session_row.owner_organization_membership_id,
631
+ session_row.owner_subject_id
632
+ )
633
+ ) THEN
634
+ RAISE EXCEPTION 'work claim session is not visible to this actor'
635
+ USING ERRCODE = '42501';
636
+ END IF;
637
+ PERFORM set_config('opengeni.subject_id', coalesce(previous_subject_id, ''), true);
638
+ PERFORM set_config(
639
+ 'opengeni.initiating_human_subject_id',
640
+ coalesce(previous_initiating_human_subject_id, ''),
641
+ true
642
+ );
643
+ visibility_context_set := false;
644
+
645
+ root_session_id := session_row.root_session_id;
646
+ actor_subject_id := turn_row.initiator_subject_id;
647
+ RETURN NEXT;
648
+ EXCEPTION WHEN OTHERS THEN
649
+ IF visibility_context_set THEN
650
+ PERFORM set_config('opengeni.subject_id', coalesce(previous_subject_id, ''), true);
651
+ PERFORM set_config(
652
+ 'opengeni.initiating_human_subject_id',
653
+ coalesce(previous_initiating_human_subject_id, ''),
654
+ true
655
+ );
656
+ END IF;
657
+ RAISE;
658
+ END;
659
+ $$;
660
+
661
+ CREATE OR REPLACE FUNCTION upsert_session_work_claim_for_attempt(
662
+ p_account_id uuid,
663
+ p_workspace_id uuid,
664
+ p_session_id uuid,
665
+ p_turn_id uuid,
666
+ p_attempt_id uuid,
667
+ p_execution_generation integer,
668
+ p_operation_id uuid,
669
+ p_expected_revision integer,
670
+ p_subject_namespace text,
671
+ p_subject_type text,
672
+ p_canonical_key text,
673
+ p_display_label text,
674
+ p_role text,
675
+ p_version_kind text,
676
+ p_version_value text
677
+ ) RETURNS TABLE (
678
+ claim_id uuid, session_id uuid, root_session_id uuid,
679
+ subject_namespace text, subject_type text, canonical_key text,
680
+ display_label text, role text, state text, revision integer,
681
+ provenance text, version_kind text, version_value text,
682
+ observed_at timestamptz, created_at timestamptz, updated_at timestamptz,
683
+ settled_at timestamptz, mutation_kind text, replayed boolean
684
+ )
685
+ LANGUAGE plpgsql
686
+ SECURITY DEFINER
687
+ SET search_path FROM CURRENT
688
+ AS $$
689
+ DECLARE
690
+ authority record;
691
+ existing_revision session_work_claim_revisions%ROWTYPE;
692
+ claim_row session_work_claims%ROWTYPE;
693
+ calculated_digest text;
694
+ calculated_input_hash text;
695
+ mutation_value text;
696
+ now_value timestamptz := clock_timestamp();
697
+ write_capability_id uuid := gen_random_uuid();
698
+ previous_capability text := current_setting(
699
+ 'opengeni.session_work_claim_write_capability', true
700
+ );
701
+ BEGIN
702
+ PERFORM acquire_session_tenancy_fence(p_workspace_id);
703
+ SELECT * INTO STRICT authority FROM resolve_session_work_claim_attempt_authority(
704
+ p_account_id, p_workspace_id, p_session_id, p_turn_id,
705
+ p_attempt_id, p_execution_generation
706
+ );
707
+ IF p_operation_id IS NULL OR p_expected_revision IS NULL OR p_expected_revision < 0
708
+ OR p_subject_namespace IS NULL
709
+ OR p_subject_namespace <> lower(btrim(p_subject_namespace))
710
+ OR octet_length(p_subject_namespace) NOT BETWEEN 1 AND 64
711
+ OR p_subject_namespace !~ '^[a-z0-9]([a-z0-9._:-]{0,62}[a-z0-9])?$'
712
+ OR p_subject_type NOT IN (
713
+ 'repository','branch','pull_request','issue','artifact','release','ci_run','other'
714
+ )
715
+ OR p_canonical_key IS NULL OR p_canonical_key <> btrim(p_canonical_key)
716
+ OR p_canonical_key <> normalize(p_canonical_key, NFKC)
717
+ OR octet_length(p_canonical_key) NOT BETWEEN 1 AND 512
718
+ OR p_canonical_key ~ '[[:cntrl:]]'
719
+ OR (p_display_label IS NOT NULL AND (
720
+ p_display_label <> btrim(p_display_label)
721
+ OR p_display_label <> normalize(p_display_label, NFKC)
722
+ OR octet_length(p_display_label) NOT BETWEEN 1 AND 256
723
+ OR p_display_label ~ '[[:cntrl:]]'
724
+ ))
725
+ OR p_role NOT IN ('working','reviewing','monitoring','delivering')
726
+ OR ((p_version_kind IS NULL) <> (p_version_value IS NULL))
727
+ OR (p_version_kind IS NOT NULL AND (
728
+ p_version_kind NOT IN (
729
+ 'git_commit','branch_head','pull_request_head','artifact_version',
730
+ 'release_version','ci_run','other'
731
+ )
732
+ OR p_version_value <> btrim(p_version_value)
733
+ OR p_version_value <> normalize(p_version_value, NFKC)
734
+ OR octet_length(p_version_value) NOT BETWEEN 1 AND 256
735
+ OR p_version_value ~ '[[:cntrl:]]'
736
+ ))
737
+ THEN
738
+ RAISE EXCEPTION 'work claim upsert input is invalid' USING ERRCODE = '22023';
739
+ END IF;
740
+
741
+ calculated_digest := session_work_claim_subject_digest(
742
+ p_subject_namespace, p_subject_type, p_canonical_key
743
+ );
744
+ calculated_input_hash := encode(sha256(convert_to(jsonb_build_object(
745
+ 'accountId', p_account_id,
746
+ 'workspaceId', p_workspace_id,
747
+ 'sessionId', p_session_id,
748
+ 'turnId', p_turn_id,
749
+ 'expectedRevision', p_expected_revision,
750
+ 'subjectNamespace', p_subject_namespace,
751
+ 'subjectType', p_subject_type,
752
+ 'canonicalKey', p_canonical_key,
753
+ 'displayLabel', p_display_label,
754
+ 'role', p_role,
755
+ 'versionKind', p_version_kind,
756
+ 'versionValue', p_version_value
757
+ )::text, 'UTF8')), 'hex');
758
+
759
+ INSERT INTO session_work_claim_write_capabilities (
760
+ backend_pid, transaction_id, capability_id
761
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), write_capability_id);
762
+ PERFORM set_config(
763
+ 'opengeni.session_work_claim_write_capability', write_capability_id::text, true
764
+ );
765
+
766
+ SELECT * INTO existing_revision FROM session_work_claim_revisions revision_row
767
+ WHERE revision_row.workspace_id = p_workspace_id
768
+ AND revision_row.operation_id = p_operation_id
769
+ FOR SHARE;
770
+ IF FOUND THEN
771
+ IF existing_revision.input_hash <> calculated_input_hash
772
+ OR existing_revision.session_id <> p_session_id
773
+ OR existing_revision.actor_turn_id <> p_turn_id
774
+ OR existing_revision.mutation_kind NOT IN ('created','updated')
775
+ THEN
776
+ RAISE EXCEPTION 'work claim operation conflicts with another input or logical turn'
777
+ USING ERRCODE = '23505';
778
+ END IF;
779
+ claim_row.id := existing_revision.claim_id;
780
+ claim_row.session_id := existing_revision.session_id;
781
+ claim_row.root_session_id := existing_revision.root_session_id;
782
+ claim_row.subject_namespace := existing_revision.subject_namespace;
783
+ claim_row.subject_type := existing_revision.subject_type;
784
+ claim_row.canonical_key := existing_revision.canonical_key;
785
+ claim_row.display_label := existing_revision.display_label;
786
+ claim_row.role := existing_revision.role;
787
+ claim_row.state := existing_revision.state;
788
+ claim_row.revision := existing_revision.resulting_revision;
789
+ claim_row.provenance := existing_revision.provenance;
790
+ claim_row.version_kind := existing_revision.version_kind;
791
+ claim_row.version_value := existing_revision.version_value;
792
+ claim_row.observed_at := existing_revision.observed_at;
793
+ claim_row.created_at := existing_revision.claim_created_at;
794
+ claim_row.updated_at := existing_revision.claim_updated_at;
795
+ claim_row.settled_at := existing_revision.settled_at;
796
+ mutation_value := existing_revision.mutation_kind;
797
+ replayed := true;
798
+ ELSE
799
+ SELECT * INTO claim_row FROM session_work_claims claim
800
+ WHERE claim.workspace_id = p_workspace_id
801
+ AND claim.session_id = p_session_id
802
+ AND claim.subject_namespace = p_subject_namespace
803
+ AND claim.subject_type = p_subject_type
804
+ AND claim.subject_digest = calculated_digest
805
+ AND claim.role = p_role
806
+ AND claim.state = 'active'
807
+ FOR UPDATE;
808
+ IF FOUND THEN
809
+ IF claim_row.revision <> p_expected_revision THEN
810
+ RAISE EXCEPTION 'work claim upsert revision conflict' USING ERRCODE = '40001';
811
+ END IF;
812
+ UPDATE session_work_claims claim SET
813
+ display_label = p_display_label,
814
+ version_kind = p_version_kind,
815
+ version_value = p_version_value,
816
+ observed_at = now_value,
817
+ revision = claim.revision + 1,
818
+ updated_at = now_value
819
+ WHERE claim.workspace_id = p_workspace_id
820
+ AND claim.id = claim_row.id
821
+ AND claim.state = 'active'
822
+ AND claim.revision = p_expected_revision
823
+ RETURNING * INTO STRICT claim_row;
824
+ mutation_value := 'updated';
825
+ ELSE
826
+ IF p_expected_revision <> 0 THEN
827
+ RAISE EXCEPTION 'work claim create revision conflict' USING ERRCODE = '40001';
828
+ END IF;
829
+ IF (SELECT count(*) FROM session_work_claims claim
830
+ WHERE claim.workspace_id = p_workspace_id
831
+ AND claim.session_id = p_session_id
832
+ AND claim.state = 'active') >= 64
833
+ THEN
834
+ RAISE EXCEPTION 'work claim active-record limit reached' USING ERRCODE = '54000';
835
+ END IF;
836
+ INSERT INTO session_work_claims (
837
+ account_id, workspace_id, session_id, root_session_id,
838
+ subject_namespace, subject_type, canonical_key, subject_digest,
839
+ display_label, role, provenance, version_kind, version_value,
840
+ observed_at, created_at, updated_at
841
+ ) VALUES (
842
+ p_account_id, p_workspace_id, p_session_id, authority.root_session_id,
843
+ p_subject_namespace, p_subject_type, p_canonical_key, calculated_digest,
844
+ p_display_label, p_role, 'explicit_agent', p_version_kind, p_version_value,
845
+ now_value, now_value, now_value
846
+ ) RETURNING * INTO claim_row;
847
+ mutation_value := 'created';
848
+ END IF;
849
+
850
+ INSERT INTO session_work_claim_revisions (
851
+ account_id, workspace_id, claim_id, session_id, root_session_id,
852
+ operation_id, input_hash, mutation_kind, prior_revision, resulting_revision,
853
+ subject_namespace, subject_type, canonical_key, subject_digest, display_label,
854
+ role, state, provenance, version_kind, version_value, observed_at,
855
+ claim_created_at, claim_updated_at, settled_at, actor_kind, actor_subject_id,
856
+ actor_session_id, actor_turn_id, actor_attempt_id, actor_execution_generation
857
+ ) VALUES (
858
+ p_account_id, p_workspace_id, claim_row.id, p_session_id, claim_row.root_session_id,
859
+ p_operation_id, calculated_input_hash, mutation_value,
860
+ CASE WHEN mutation_value = 'created' THEN NULL ELSE p_expected_revision END,
861
+ claim_row.revision, claim_row.subject_namespace, claim_row.subject_type,
862
+ claim_row.canonical_key, claim_row.subject_digest, claim_row.display_label,
863
+ claim_row.role, claim_row.state, claim_row.provenance, claim_row.version_kind,
864
+ claim_row.version_value, claim_row.observed_at, claim_row.created_at,
865
+ claim_row.updated_at, claim_row.settled_at, 'agent_attempt', authority.actor_subject_id,
866
+ p_session_id, p_turn_id, p_attempt_id, p_execution_generation
867
+ );
868
+ UPDATE sessions session SET updated_at = now_value
869
+ WHERE session.workspace_id = p_workspace_id AND session.id = p_session_id;
870
+ replayed := false;
871
+ END IF;
872
+
873
+ claim_id := claim_row.id;
874
+ session_id := claim_row.session_id;
875
+ root_session_id := claim_row.root_session_id;
876
+ subject_namespace := claim_row.subject_namespace;
877
+ subject_type := claim_row.subject_type;
878
+ canonical_key := claim_row.canonical_key;
879
+ display_label := claim_row.display_label;
880
+ role := claim_row.role;
881
+ state := claim_row.state;
882
+ revision := claim_row.revision;
883
+ provenance := claim_row.provenance;
884
+ version_kind := claim_row.version_kind;
885
+ version_value := claim_row.version_value;
886
+ observed_at := claim_row.observed_at;
887
+ created_at := claim_row.created_at;
888
+ updated_at := claim_row.updated_at;
889
+ settled_at := claim_row.settled_at;
890
+ mutation_kind := mutation_value;
891
+ DELETE FROM session_work_claim_write_capabilities capability
892
+ WHERE capability.backend_pid = pg_backend_pid()
893
+ AND capability.transaction_id = pg_current_xact_id()
894
+ AND capability.capability_id = write_capability_id;
895
+ PERFORM set_config(
896
+ 'opengeni.session_work_claim_write_capability', coalesce(previous_capability, ''), true
897
+ );
898
+ RETURN NEXT;
899
+ EXCEPTION WHEN OTHERS THEN
900
+ PERFORM set_config(
901
+ 'opengeni.session_work_claim_write_capability', coalesce(previous_capability, ''), true
902
+ );
903
+ RAISE;
904
+ END;
905
+ $$;
906
+
907
+ CREATE OR REPLACE FUNCTION release_session_work_claim_for_attempt(
908
+ p_account_id uuid,
909
+ p_workspace_id uuid,
910
+ p_session_id uuid,
911
+ p_turn_id uuid,
912
+ p_attempt_id uuid,
913
+ p_execution_generation integer,
914
+ p_operation_id uuid,
915
+ p_claim_id uuid,
916
+ p_expected_revision integer,
917
+ p_reason text
918
+ ) RETURNS TABLE (
919
+ claim_id uuid, session_id uuid, root_session_id uuid,
920
+ subject_namespace text, subject_type text, canonical_key text,
921
+ display_label text, role text, state text, revision integer,
922
+ provenance text, version_kind text, version_value text,
923
+ observed_at timestamptz, created_at timestamptz, updated_at timestamptz,
924
+ settled_at timestamptz, mutation_kind text, replayed boolean
925
+ )
926
+ LANGUAGE plpgsql
927
+ SECURITY DEFINER
928
+ SET search_path FROM CURRENT
929
+ AS $$
930
+ DECLARE
931
+ authority record;
932
+ existing_revision session_work_claim_revisions%ROWTYPE;
933
+ claim_row session_work_claims%ROWTYPE;
934
+ calculated_input_hash text;
935
+ now_value timestamptz := clock_timestamp();
936
+ write_capability_id uuid := gen_random_uuid();
937
+ previous_capability text := current_setting(
938
+ 'opengeni.session_work_claim_write_capability', true
939
+ );
940
+ BEGIN
941
+ PERFORM acquire_session_tenancy_fence(p_workspace_id);
942
+ SELECT * INTO STRICT authority FROM resolve_session_work_claim_attempt_authority(
943
+ p_account_id, p_workspace_id, p_session_id, p_turn_id,
944
+ p_attempt_id, p_execution_generation
945
+ );
946
+ IF p_operation_id IS NULL OR p_claim_id IS NULL
947
+ OR p_expected_revision IS NULL OR p_expected_revision < 1
948
+ OR p_reason NOT IN (
949
+ 'completed','cancelled','failed','superseded','no_longer_active',
950
+ 'corrected','external_state_changed','other'
951
+ )
952
+ THEN
953
+ RAISE EXCEPTION 'work claim release input is invalid' USING ERRCODE = '22023';
954
+ END IF;
955
+ calculated_input_hash := encode(sha256(convert_to(jsonb_build_object(
956
+ 'accountId', p_account_id,
957
+ 'workspaceId', p_workspace_id,
958
+ 'sessionId', p_session_id,
959
+ 'turnId', p_turn_id,
960
+ 'claimId', p_claim_id,
961
+ 'expectedRevision', p_expected_revision,
962
+ 'reason', p_reason
963
+ )::text, 'UTF8')), 'hex');
964
+
965
+ INSERT INTO session_work_claim_write_capabilities (
966
+ backend_pid, transaction_id, capability_id
967
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), write_capability_id);
968
+ PERFORM set_config(
969
+ 'opengeni.session_work_claim_write_capability', write_capability_id::text, true
970
+ );
971
+
972
+ SELECT * INTO existing_revision FROM session_work_claim_revisions revision_row
973
+ WHERE revision_row.workspace_id = p_workspace_id
974
+ AND revision_row.operation_id = p_operation_id
975
+ FOR SHARE;
976
+ IF FOUND THEN
977
+ IF existing_revision.input_hash <> calculated_input_hash
978
+ OR existing_revision.session_id <> p_session_id
979
+ OR existing_revision.actor_turn_id <> p_turn_id
980
+ OR existing_revision.mutation_kind <> 'released'
981
+ THEN
982
+ RAISE EXCEPTION 'work claim release conflicts with another input or logical turn'
983
+ USING ERRCODE = '23505';
984
+ END IF;
985
+ claim_row.id := existing_revision.claim_id;
986
+ claim_row.session_id := existing_revision.session_id;
987
+ claim_row.root_session_id := existing_revision.root_session_id;
988
+ claim_row.subject_namespace := existing_revision.subject_namespace;
989
+ claim_row.subject_type := existing_revision.subject_type;
990
+ claim_row.canonical_key := existing_revision.canonical_key;
991
+ claim_row.display_label := existing_revision.display_label;
992
+ claim_row.role := existing_revision.role;
993
+ claim_row.state := existing_revision.state;
994
+ claim_row.revision := existing_revision.resulting_revision;
995
+ claim_row.provenance := existing_revision.provenance;
996
+ claim_row.version_kind := existing_revision.version_kind;
997
+ claim_row.version_value := existing_revision.version_value;
998
+ claim_row.observed_at := existing_revision.observed_at;
999
+ claim_row.created_at := existing_revision.claim_created_at;
1000
+ claim_row.updated_at := existing_revision.claim_updated_at;
1001
+ claim_row.settled_at := existing_revision.settled_at;
1002
+ replayed := true;
1003
+ ELSE
1004
+ SELECT * INTO claim_row FROM session_work_claims claim
1005
+ WHERE claim.workspace_id = p_workspace_id
1006
+ AND claim.session_id = p_session_id
1007
+ AND claim.id = p_claim_id
1008
+ FOR UPDATE;
1009
+ IF NOT FOUND THEN
1010
+ RAISE EXCEPTION 'work claim is unavailable' USING ERRCODE = 'P0002';
1011
+ END IF;
1012
+ IF claim_row.state <> 'active' OR claim_row.revision <> p_expected_revision THEN
1013
+ RAISE EXCEPTION 'work claim release revision conflict' USING ERRCODE = '40001';
1014
+ END IF;
1015
+ UPDATE session_work_claims claim SET
1016
+ state = 'released',
1017
+ revision = claim.revision + 1,
1018
+ settled_at = now_value,
1019
+ updated_at = now_value
1020
+ WHERE claim.workspace_id = p_workspace_id
1021
+ AND claim.id = p_claim_id
1022
+ AND claim.state = 'active'
1023
+ AND claim.revision = p_expected_revision
1024
+ RETURNING * INTO STRICT claim_row;
1025
+ INSERT INTO session_work_claim_revisions (
1026
+ account_id, workspace_id, claim_id, session_id, root_session_id,
1027
+ operation_id, input_hash, mutation_kind, prior_revision, resulting_revision,
1028
+ subject_namespace, subject_type, canonical_key, subject_digest, display_label,
1029
+ role, state, provenance, version_kind, version_value, observed_at,
1030
+ claim_created_at, claim_updated_at, settled_at, actor_kind, actor_subject_id,
1031
+ actor_session_id, actor_turn_id, actor_attempt_id, actor_execution_generation, reason
1032
+ ) VALUES (
1033
+ p_account_id, p_workspace_id, claim_row.id, p_session_id, claim_row.root_session_id,
1034
+ p_operation_id, calculated_input_hash, 'released', p_expected_revision,
1035
+ claim_row.revision, claim_row.subject_namespace, claim_row.subject_type,
1036
+ claim_row.canonical_key, claim_row.subject_digest, claim_row.display_label,
1037
+ claim_row.role, claim_row.state, claim_row.provenance, claim_row.version_kind,
1038
+ claim_row.version_value, claim_row.observed_at, claim_row.created_at,
1039
+ claim_row.updated_at, claim_row.settled_at, 'agent_attempt', authority.actor_subject_id,
1040
+ p_session_id, p_turn_id, p_attempt_id, p_execution_generation, p_reason
1041
+ );
1042
+ UPDATE sessions session SET updated_at = now_value
1043
+ WHERE session.workspace_id = p_workspace_id AND session.id = p_session_id;
1044
+ replayed := false;
1045
+ END IF;
1046
+
1047
+ claim_id := claim_row.id;
1048
+ session_id := claim_row.session_id;
1049
+ root_session_id := claim_row.root_session_id;
1050
+ subject_namespace := claim_row.subject_namespace;
1051
+ subject_type := claim_row.subject_type;
1052
+ canonical_key := claim_row.canonical_key;
1053
+ display_label := claim_row.display_label;
1054
+ role := claim_row.role;
1055
+ state := claim_row.state;
1056
+ revision := claim_row.revision;
1057
+ provenance := claim_row.provenance;
1058
+ version_kind := claim_row.version_kind;
1059
+ version_value := claim_row.version_value;
1060
+ observed_at := claim_row.observed_at;
1061
+ created_at := claim_row.created_at;
1062
+ updated_at := claim_row.updated_at;
1063
+ settled_at := claim_row.settled_at;
1064
+ mutation_kind := 'released';
1065
+ DELETE FROM session_work_claim_write_capabilities capability
1066
+ WHERE capability.backend_pid = pg_backend_pid()
1067
+ AND capability.transaction_id = pg_current_xact_id()
1068
+ AND capability.capability_id = write_capability_id;
1069
+ PERFORM set_config(
1070
+ 'opengeni.session_work_claim_write_capability', coalesce(previous_capability, ''), true
1071
+ );
1072
+ RETURN NEXT;
1073
+ EXCEPTION WHEN OTHERS THEN
1074
+ PERFORM set_config(
1075
+ 'opengeni.session_work_claim_write_capability', coalesce(previous_capability, ''), true
1076
+ );
1077
+ RAISE;
1078
+ END;
1079
+ $$;
1080
+
1081
+ CREATE OR REPLACE FUNCTION settle_active_session_work_claims(
1082
+ p_account_id uuid,
1083
+ p_workspace_id uuid,
1084
+ p_session_id uuid,
1085
+ p_state text,
1086
+ p_reason text
1087
+ ) RETURNS integer
1088
+ LANGUAGE plpgsql
1089
+ SECURITY DEFINER
1090
+ SET search_path FROM CURRENT
1091
+ AS $$
1092
+ DECLARE
1093
+ claim_row session_work_claims%ROWTYPE;
1094
+ now_value timestamptz := clock_timestamp();
1095
+ settled_count integer := 0;
1096
+ write_capability_id uuid := gen_random_uuid();
1097
+ previous_account_id text := current_setting('opengeni.account_id', true);
1098
+ previous_workspace_id text := current_setting('opengeni.workspace_id', true);
1099
+ previous_capability text := current_setting(
1100
+ 'opengeni.session_work_claim_write_capability', true
1101
+ );
1102
+ BEGIN
1103
+ IF p_state NOT IN ('released','superseded','stale')
1104
+ OR p_reason NOT IN ('completed','cancelled','failed','superseded')
1105
+ THEN
1106
+ RAISE EXCEPTION 'work claim lifecycle settlement is invalid' USING ERRCODE = '22023';
1107
+ END IF;
1108
+ PERFORM set_config('opengeni.account_id', p_account_id::text, true);
1109
+ PERFORM set_config('opengeni.workspace_id', p_workspace_id::text, true);
1110
+ INSERT INTO session_work_claim_write_capabilities (
1111
+ backend_pid, transaction_id, capability_id
1112
+ ) VALUES (pg_backend_pid(), pg_current_xact_id(), write_capability_id);
1113
+ PERFORM set_config(
1114
+ 'opengeni.session_work_claim_write_capability', write_capability_id::text, true
1115
+ );
1116
+ FOR claim_row IN
1117
+ SELECT * FROM session_work_claims claim
1118
+ WHERE claim.account_id = p_account_id
1119
+ AND claim.workspace_id = p_workspace_id
1120
+ AND claim.session_id = p_session_id
1121
+ AND claim.state = 'active'
1122
+ ORDER BY claim.id
1123
+ FOR UPDATE
1124
+ LOOP
1125
+ UPDATE session_work_claims claim SET
1126
+ state = p_state,
1127
+ revision = claim.revision + 1,
1128
+ settled_at = now_value,
1129
+ updated_at = now_value
1130
+ WHERE claim.workspace_id = p_workspace_id
1131
+ AND claim.id = claim_row.id
1132
+ AND claim.state = 'active'
1133
+ AND claim.revision = claim_row.revision
1134
+ RETURNING * INTO STRICT claim_row;
1135
+ INSERT INTO session_work_claim_revisions (
1136
+ account_id, workspace_id, claim_id, session_id, root_session_id,
1137
+ operation_id, input_hash, mutation_kind, prior_revision, resulting_revision,
1138
+ subject_namespace, subject_type, canonical_key, subject_digest, display_label,
1139
+ role, state, provenance, version_kind, version_value, observed_at,
1140
+ claim_created_at, claim_updated_at, settled_at, actor_kind, actor_subject_id,
1141
+ reason
1142
+ ) VALUES (
1143
+ claim_row.account_id, claim_row.workspace_id, claim_row.id,
1144
+ claim_row.session_id, claim_row.root_session_id, gen_random_uuid(),
1145
+ encode(sha256(convert_to(jsonb_build_object(
1146
+ 'claimId', claim_row.id,
1147
+ 'priorRevision', claim_row.revision - 1,
1148
+ 'state', p_state,
1149
+ 'reason', p_reason
1150
+ )::text, 'UTF8')), 'hex'),
1151
+ p_state, claim_row.revision - 1, claim_row.revision,
1152
+ claim_row.subject_namespace, claim_row.subject_type, claim_row.canonical_key,
1153
+ claim_row.subject_digest, claim_row.display_label, claim_row.role, claim_row.state,
1154
+ claim_row.provenance, claim_row.version_kind, claim_row.version_value,
1155
+ claim_row.observed_at, claim_row.created_at, claim_row.updated_at,
1156
+ claim_row.settled_at, 'system', 'system:lifecycle', p_reason
1157
+ );
1158
+ settled_count := settled_count + 1;
1159
+ END LOOP;
1160
+ DELETE FROM session_work_claim_write_capabilities capability
1161
+ WHERE capability.backend_pid = pg_backend_pid()
1162
+ AND capability.transaction_id = pg_current_xact_id()
1163
+ AND capability.capability_id = write_capability_id;
1164
+ PERFORM set_config(
1165
+ 'opengeni.session_work_claim_write_capability', coalesce(previous_capability, ''), true
1166
+ );
1167
+ PERFORM set_config('opengeni.account_id', coalesce(previous_account_id, ''), true);
1168
+ PERFORM set_config('opengeni.workspace_id', coalesce(previous_workspace_id, ''), true);
1169
+ RETURN settled_count;
1170
+ EXCEPTION WHEN OTHERS THEN
1171
+ PERFORM set_config(
1172
+ 'opengeni.session_work_claim_write_capability', coalesce(previous_capability, ''), true
1173
+ );
1174
+ PERFORM set_config('opengeni.account_id', coalesce(previous_account_id, ''), true);
1175
+ PERFORM set_config('opengeni.workspace_id', coalesce(previous_workspace_id, ''), true);
1176
+ RAISE;
1177
+ END;
1178
+ $$;
1179
+
1180
+ CREATE OR REPLACE FUNCTION settle_session_work_claims_on_session_terminal()
1181
+ RETURNS trigger
1182
+ LANGUAGE plpgsql
1183
+ SECURITY DEFINER
1184
+ SET search_path FROM CURRENT
1185
+ AS $$
1186
+ BEGIN
1187
+ IF NEW.status IS DISTINCT FROM OLD.status AND NEW.status = 'cancelled' THEN
1188
+ PERFORM settle_active_session_work_claims(
1189
+ NEW.account_id, NEW.workspace_id, NEW.id, 'released', 'cancelled'
1190
+ );
1191
+ ELSIF NEW.status IS DISTINCT FROM OLD.status AND NEW.status = 'failed' THEN
1192
+ PERFORM settle_active_session_work_claims(
1193
+ NEW.account_id, NEW.workspace_id, NEW.id, 'stale', 'failed'
1194
+ );
1195
+ END IF;
1196
+ RETURN NEW;
1197
+ END;
1198
+ $$;
1199
+
1200
+ CREATE TRIGGER sessions_settle_work_claims_terminal
1201
+ AFTER UPDATE OF "status" ON "sessions"
1202
+ FOR EACH ROW
1203
+ WHEN (NEW."status" IS DISTINCT FROM OLD."status")
1204
+ EXECUTE FUNCTION settle_session_work_claims_on_session_terminal();
1205
+
1206
+ CREATE OR REPLACE FUNCTION settle_session_work_claims_on_goal_completion()
1207
+ RETURNS trigger
1208
+ LANGUAGE plpgsql
1209
+ SECURITY DEFINER
1210
+ SET search_path FROM CURRENT
1211
+ AS $$
1212
+ BEGIN
1213
+ IF NEW.status IS DISTINCT FROM OLD.status AND NEW.status = 'completed' THEN
1214
+ PERFORM settle_active_session_work_claims(
1215
+ NEW.account_id, NEW.workspace_id, NEW.session_id, 'released', 'completed'
1216
+ );
1217
+ END IF;
1218
+ RETURN NEW;
1219
+ END;
1220
+ $$;
1221
+
1222
+ CREATE TRIGGER session_goals_settle_work_claims_completed
1223
+ AFTER UPDATE OF "status" ON "session_goals"
1224
+ FOR EACH ROW
1225
+ WHEN (NEW."status" IS DISTINCT FROM OLD."status" AND NEW."status" = 'completed')
1226
+ EXECUTE FUNCTION settle_session_work_claims_on_goal_completion();
1227
+
1228
+ DO $work_claim_search_paths$
1229
+ DECLARE
1230
+ data_schema text := current_schema();
1231
+ BEGIN
1232
+ EXECUTE format(
1233
+ 'ALTER FUNCTION opengeni_private.session_work_claim_capability_active() '
1234
+ || 'SET search_path = %I, pg_catalog', data_schema
1235
+ );
1236
+ EXECUTE format(
1237
+ 'ALTER FUNCTION %1$I.session_work_claim_subject_digest(text,text,text) '
1238
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1239
+ );
1240
+ EXECUTE format(
1241
+ 'ALTER FUNCTION %1$I.guard_session_work_claim_mutation() '
1242
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1243
+ );
1244
+ EXECUTE format(
1245
+ 'ALTER FUNCTION %1$I.guard_session_work_claim_revision_mutation() '
1246
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1247
+ );
1248
+ EXECUTE format(
1249
+ 'ALTER FUNCTION %1$I.resolve_session_work_claim_attempt_authority(uuid,uuid,uuid,uuid,uuid,integer) '
1250
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1251
+ );
1252
+ EXECUTE format(
1253
+ 'ALTER FUNCTION %1$I.upsert_session_work_claim_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,integer,text,text,text,text,text,text,text) '
1254
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1255
+ );
1256
+ EXECUTE format(
1257
+ 'ALTER FUNCTION %1$I.release_session_work_claim_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text) '
1258
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1259
+ );
1260
+ EXECUTE format(
1261
+ 'ALTER FUNCTION %1$I.settle_active_session_work_claims(uuid,uuid,uuid,text,text) '
1262
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1263
+ );
1264
+ EXECUTE format(
1265
+ 'ALTER FUNCTION %1$I.settle_session_work_claims_on_session_terminal() '
1266
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1267
+ );
1268
+ EXECUTE format(
1269
+ 'ALTER FUNCTION %1$I.settle_session_work_claims_on_goal_completion() '
1270
+ || 'SET search_path = %1$I, pg_catalog', data_schema
1271
+ );
1272
+ END
1273
+ $work_claim_search_paths$;
1274
+
1275
+ REVOKE ALL ON "session_work_claims" FROM PUBLIC;
1276
+ REVOKE ALL ON "session_work_claim_revisions" FROM PUBLIC;
1277
+ REVOKE ALL ON "session_work_claim_write_capabilities" FROM PUBLIC;
1278
+ REVOKE ALL ON FUNCTION opengeni_private.session_work_claim_capability_active() FROM PUBLIC;
1279
+ REVOKE ALL ON FUNCTION session_work_claim_subject_digest(text,text,text) FROM PUBLIC;
1280
+ REVOKE ALL ON FUNCTION guard_session_work_claim_mutation() FROM PUBLIC;
1281
+ REVOKE ALL ON FUNCTION guard_session_work_claim_revision_mutation() FROM PUBLIC;
1282
+ REVOKE ALL ON FUNCTION resolve_session_work_claim_attempt_authority(uuid,uuid,uuid,uuid,uuid,integer) FROM PUBLIC;
1283
+ REVOKE ALL ON FUNCTION upsert_session_work_claim_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,integer,text,text,text,text,text,text,text) FROM PUBLIC;
1284
+ REVOKE ALL ON FUNCTION release_session_work_claim_for_attempt(uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text) FROM PUBLIC;
1285
+ REVOKE ALL ON FUNCTION settle_active_session_work_claims(uuid,uuid,uuid,text,text) FROM PUBLIC;
1286
+ REVOKE ALL ON FUNCTION settle_session_work_claims_on_session_terminal() FROM PUBLIC;
1287
+ REVOKE ALL ON FUNCTION settle_session_work_claims_on_goal_completion() FROM PUBLIC;
1288
+
1289
+ DO $work_claim_runtime_grants$
1290
+ BEGIN
1291
+ IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'opengeni_app') THEN
1292
+ REVOKE ALL ON "session_work_claims" FROM opengeni_app;
1293
+ REVOKE ALL ON "session_work_claim_revisions" FROM opengeni_app;
1294
+ REVOKE ALL ON "session_work_claim_write_capabilities" FROM opengeni_app;
1295
+ GRANT SELECT ON "session_work_claims" TO opengeni_app;
1296
+ GRANT EXECUTE ON FUNCTION opengeni_private.session_work_claim_capability_active()
1297
+ TO opengeni_app;
1298
+ GRANT EXECUTE ON FUNCTION upsert_session_work_claim_for_attempt(
1299
+ uuid,uuid,uuid,uuid,uuid,integer,uuid,integer,text,text,text,text,text,text,text
1300
+ ) TO opengeni_app;
1301
+ GRANT EXECUTE ON FUNCTION release_session_work_claim_for_attempt(
1302
+ uuid,uuid,uuid,uuid,uuid,integer,uuid,uuid,integer,text
1303
+ ) TO opengeni_app;
1304
+ END IF;
1305
+ END
1306
+ $work_claim_runtime_grants$;