@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
@@ -6,6 +6,7 @@ import {
6
6
  RUNTIME_READ_INSERT_UPDATE_TABLES,
7
7
  RUNTIME_READ_ONLY_TABLES,
8
8
  RUNTIME_READ_UPDATE_TABLES,
9
+ WORK_CLAIM_CAPABILITY_ROUTINES,
9
10
  } from "./runtime-posture";
10
11
  import {
11
12
  classifyRoleRelationships,
@@ -482,6 +483,7 @@ async function grantAppRoleIfSchemaExists(
482
483
  const runtimeReadUpdateTables = `ARRAY[${RUNTIME_READ_UPDATE_TABLES.map(literal).join(", ")}]`;
483
484
  const runtimeReadInsertTables = `ARRAY[${RUNTIME_READ_INSERT_TABLES.map(literal).join(", ")}]`;
484
485
  const runtimeReadInsertUpdateTables = `ARRAY[${RUNTIME_READ_INSERT_UPDATE_TABLES.map(literal).join(", ")}]`;
486
+ const workClaimCapabilityRoutines = `ARRAY[${WORK_CLAIM_CAPABILITY_ROUTINES.map(literal).join(", ")}]`;
485
487
  const organizationMembershipLifecycleRoutines = `ARRAY[${[
486
488
  "list_self_organization_memberships(text)",
487
489
  "list_self_organization_invitations(text)",
@@ -997,6 +999,22 @@ BEGIN
997
999
  ${literal(role)}
998
1000
  );
999
1001
  END IF;
1002
+ -- Migration 0365 creates the advisory work-claim mutation capabilities
1003
+ -- before a custom runtime role may exist. Re-converge their exact grants
1004
+ -- for migrate-then-provision installs without exposing claim history or
1005
+ -- the write-capability ledger to direct runtime DML.
1006
+ FOREACH routine_signature IN ARRAY ${workClaimCapabilityRoutines} LOOP
1007
+ IF to_regprocedure(format('%I.%s', ${literal(schema)}, routine_signature)) IS NOT NULL THEN
1008
+ EXECUTE format(
1009
+ 'REVOKE ALL ON FUNCTION %I.%s FROM PUBLIC',
1010
+ ${literal(schema)}, routine_signature
1011
+ );
1012
+ EXECUTE format(
1013
+ 'GRANT EXECUTE ON FUNCTION %I.%s TO %I',
1014
+ ${literal(schema)}, routine_signature, ${literal(role)}
1015
+ );
1016
+ END IF;
1017
+ END LOOP;
1000
1018
  IF to_regprocedure(
1001
1019
  format(
1002
1020
  '%I.workspace_instruction_policy_get_or_create_snapshot(uuid,uuid,uuid,uuid,uuid,integer)',
@@ -377,6 +377,10 @@ const TASK_NOTE_CAPABILITY_ROUTINES = [
377
377
  "replace_task_note_for_attempt(uuid, uuid, uuid, uuid, uuid, integer, uuid, uuid, uuid, uuid, integer, text, text, integer, text)",
378
378
  "resolve_task_note_knowledge_promotion_source(uuid, uuid, uuid, uuid, uuid, integer, uuid, integer, text, text, text)",
379
379
  ] as const;
380
+ export const WORK_CLAIM_CAPABILITY_ROUTINES = [
381
+ "upsert_session_work_claim_for_attempt(uuid, uuid, uuid, uuid, uuid, integer, uuid, integer, text, text, text, text, text, text, text)",
382
+ "release_session_work_claim_for_attempt(uuid, uuid, uuid, uuid, uuid, integer, uuid, uuid, integer, text)",
383
+ ] as const;
380
384
  const COMPANY_BRAIN_CONTEXT_SELECTION_ROUTINE =
381
385
  "company_brain_context_get_or_create_selection(uuid, uuid, uuid, uuid, uuid, integer)";
382
386
  const COMPANY_BRAIN_CONTEXT_INSPECTION_ROUTINE =
@@ -500,6 +504,7 @@ const SESSION_AUTHORITY_ROUTINES = new Set<string>([
500
504
  ...PRIVATE_SESSION_CREATE_CAPABILITY_ROUTINES,
501
505
  TRANSITION_SESSION_VISIBILITY_ROUTINE,
502
506
  ...TASK_NOTE_CAPABILITY_ROUTINES,
507
+ ...WORK_CLAIM_CAPABILITY_ROUTINES,
503
508
  COMPANY_BRAIN_CONTEXT_SELECTION_ROUTINE,
504
509
  COMPANY_BRAIN_CONTEXT_INSPECTION_ROUTINE,
505
510
  ]);
@@ -557,6 +562,7 @@ export const RUNTIME_TARGET_SCHEMA_CAPABILITY_ROUTINES = [
557
562
  ...MANAGED_AUTH_SESSION_SET_ROUTINES,
558
563
  ...ORGANIZATION_RECOVERY_ROUTINES,
559
564
  ...TASK_NOTE_CAPABILITY_ROUTINES,
565
+ ...WORK_CLAIM_CAPABILITY_ROUTINES,
560
566
  SESSION_PRIVATE_ACTOR_VISIBLE_ROUTINE,
561
567
  SESSION_REFERENCE_VISIBLE_ROUTINE,
562
568
  SESSION_VISIBILITY_LIFECYCLE_CAPABILITY_ROUTINE,
@@ -868,6 +874,9 @@ export const FORCE_RLS_TABLES = [
868
874
  "session_turns",
869
875
  "session_variable_set_attachments",
870
876
  "session_visibility_write_capabilities",
877
+ "session_work_claim_revisions",
878
+ "session_work_claim_write_capabilities",
879
+ "session_work_claims",
871
880
  "session_workflow_wake_outbox",
872
881
  "sessions",
873
882
  "site_auth_connections",
@@ -1136,6 +1145,7 @@ export const RUNTIME_READ_ONLY_TABLES = [
1136
1145
  "preference_registry_events",
1137
1146
  "preference_registry_snapshots",
1138
1147
  "session_tenancy_activations",
1148
+ "session_work_claims",
1139
1149
  "slack_installation_bindings",
1140
1150
  "slack_task_policy_activation_events",
1141
1151
  "slack_task_policy_heads",
@@ -1330,6 +1340,8 @@ export const PROTECTED_NO_DIRECT_DML_TABLES = [
1330
1340
  "session_tenancy_greenfield_activation_evidence",
1331
1341
  "session_variable_set_attachments",
1332
1342
  "session_visibility_write_capabilities",
1343
+ "session_work_claim_revisions",
1344
+ "session_work_claim_write_capabilities",
1333
1345
  "task_note_events",
1334
1346
  "task_note_knowledge_promotion_capabilities",
1335
1347
  "task_note_replacement_receipts",
package/src/schema.ts CHANGED
@@ -12483,6 +12483,7 @@ export * from "./preference-registry-schema";
12483
12483
  export * from "./memory-governance-schema";
12484
12484
  export * from "./scoped-knowledge-schema";
12485
12485
  export * from "./task-notes-schema";
12486
+ export * from "./work-claims-schema";
12486
12487
  export * from "./company-brain-context-selection-schema";
12487
12488
  export * from "./governed-learning-evaluator-schema";
12488
12489
  export * from "./governed-learning-activation-schema";
@@ -0,0 +1,148 @@
1
+ import { sql } from "drizzle-orm";
2
+ import {
3
+ customType,
4
+ index,
5
+ integer,
6
+ pgTable,
7
+ primaryKey,
8
+ text,
9
+ timestamp,
10
+ uniqueIndex,
11
+ uuid,
12
+ } from "drizzle-orm/pg-core";
13
+
14
+ const xid8 = customType<{ data: string; driverData: string }>({
15
+ dataType: () => "xid8",
16
+ });
17
+
18
+ // Exact lifecycle-only mutation, immutable revisions, FORCE RLS, and runtime
19
+ // grants are owned by the work-claim migration. The application role receives
20
+ // SELECT on the compact head plus EXECUTE on exact-attempt mutation routines,
21
+ // never direct DML authority.
22
+ export const sessionWorkClaims = pgTable(
23
+ "session_work_claims",
24
+ {
25
+ id: uuid("id").primaryKey().defaultRandom(),
26
+ accountId: uuid("account_id").notNull(),
27
+ workspaceId: uuid("workspace_id").notNull(),
28
+ sessionId: uuid("session_id").notNull(),
29
+ rootSessionId: uuid("root_session_id").notNull(),
30
+ subjectNamespace: text("subject_namespace").notNull(),
31
+ subjectType: text("subject_type").notNull(),
32
+ canonicalKey: text("canonical_key").notNull(),
33
+ subjectDigest: text("subject_digest").notNull(),
34
+ displayLabel: text("display_label"),
35
+ role: text("role").notNull(),
36
+ state: text("state").notNull().default("active"),
37
+ revision: integer("revision").notNull().default(1),
38
+ provenance: text("provenance").notNull(),
39
+ versionKind: text("version_kind"),
40
+ versionValue: text("version_value"),
41
+ observedAt: timestamp("observed_at", { withTimezone: true }).notNull(),
42
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
43
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow(),
44
+ settledAt: timestamp("settled_at", { withTimezone: true }),
45
+ },
46
+ (table) => ({
47
+ workspaceIdentity: uniqueIndex("session_work_claims_workspace_id_uq").on(
48
+ table.workspaceId,
49
+ table.id,
50
+ ),
51
+ activeIdentity: uniqueIndex("session_work_claims_active_identity_uq")
52
+ .on(
53
+ table.workspaceId,
54
+ table.sessionId,
55
+ table.subjectNamespace,
56
+ table.subjectType,
57
+ table.subjectDigest,
58
+ table.role,
59
+ )
60
+ .where(sql`${table.state} = 'active'`),
61
+ sessionState: index("session_work_claims_session_state_idx").on(
62
+ table.workspaceId,
63
+ table.sessionId,
64
+ table.state,
65
+ table.updatedAt,
66
+ table.id,
67
+ ),
68
+ subjectState: index("session_work_claims_subject_state_idx").on(
69
+ table.workspaceId,
70
+ table.subjectNamespace,
71
+ table.subjectType,
72
+ table.subjectDigest,
73
+ table.state,
74
+ table.updatedAt,
75
+ table.id,
76
+ ),
77
+ }),
78
+ );
79
+
80
+ export const sessionWorkClaimRevisions = pgTable(
81
+ "session_work_claim_revisions",
82
+ {
83
+ id: uuid("id").primaryKey().defaultRandom(),
84
+ accountId: uuid("account_id").notNull(),
85
+ workspaceId: uuid("workspace_id").notNull(),
86
+ claimId: uuid("claim_id").notNull(),
87
+ sessionId: uuid("session_id").notNull(),
88
+ rootSessionId: uuid("root_session_id").notNull(),
89
+ operationId: uuid("operation_id").notNull(),
90
+ inputHash: text("input_hash").notNull(),
91
+ mutationKind: text("mutation_kind").notNull(),
92
+ priorRevision: integer("prior_revision"),
93
+ resultingRevision: integer("resulting_revision").notNull(),
94
+ subjectNamespace: text("subject_namespace").notNull(),
95
+ subjectType: text("subject_type").notNull(),
96
+ canonicalKey: text("canonical_key").notNull(),
97
+ subjectDigest: text("subject_digest").notNull(),
98
+ displayLabel: text("display_label"),
99
+ role: text("role").notNull(),
100
+ state: text("state").notNull(),
101
+ provenance: text("provenance").notNull(),
102
+ versionKind: text("version_kind"),
103
+ versionValue: text("version_value"),
104
+ observedAt: timestamp("observed_at", { withTimezone: true }).notNull(),
105
+ claimCreatedAt: timestamp("claim_created_at", { withTimezone: true }).notNull(),
106
+ claimUpdatedAt: timestamp("claim_updated_at", { withTimezone: true }).notNull(),
107
+ settledAt: timestamp("settled_at", { withTimezone: true }),
108
+ actorKind: text("actor_kind").notNull(),
109
+ actorSubjectId: text("actor_subject_id").notNull(),
110
+ actorSessionId: uuid("actor_session_id"),
111
+ actorTurnId: uuid("actor_turn_id"),
112
+ actorAttemptId: uuid("actor_attempt_id"),
113
+ actorExecutionGeneration: integer("actor_execution_generation"),
114
+ reason: text("reason"),
115
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
116
+ },
117
+ (table) => ({
118
+ operation: uniqueIndex("session_work_claim_revisions_workspace_operation_uq").on(
119
+ table.workspaceId,
120
+ table.operationId,
121
+ ),
122
+ claimRevision: uniqueIndex("session_work_claim_revisions_claim_revision_uq").on(
123
+ table.workspaceId,
124
+ table.claimId,
125
+ table.resultingRevision,
126
+ ),
127
+ sessionTimeline: index("session_work_claim_revisions_session_timeline_idx").on(
128
+ table.workspaceId,
129
+ table.sessionId,
130
+ table.createdAt,
131
+ table.id,
132
+ ),
133
+ }),
134
+ );
135
+
136
+ export const sessionWorkClaimWriteCapabilities = pgTable(
137
+ "session_work_claim_write_capabilities",
138
+ {
139
+ backendPid: integer("backend_pid").notNull(),
140
+ transactionId: xid8("transaction_id").notNull(),
141
+ capabilityId: uuid("capability_id").notNull(),
142
+ },
143
+ (table) => ({
144
+ identity: primaryKey({
145
+ columns: [table.backendPid, table.transactionId, table.capabilityId],
146
+ }),
147
+ }),
148
+ );
@@ -0,0 +1,200 @@
1
+ import {
2
+ WorkClaim,
3
+ WorkClaimCanonicalKey,
4
+ WorkClaimDisplayLabel,
5
+ WorkClaimMutationKind,
6
+ WorkClaimMutationResult,
7
+ WorkClaimNamespace,
8
+ WorkClaimReleaseReason,
9
+ WorkClaimRole,
10
+ WorkClaimSubjectType,
11
+ WorkClaimVersionKind,
12
+ WorkClaimVersionValue,
13
+ normalizeWorkClaimCanonicalKey,
14
+ normalizeWorkClaimDisplayLabel,
15
+ normalizeWorkClaimNamespace,
16
+ type WorkClaimRole as WorkClaimRoleType,
17
+ type WorkClaimSubjectType as WorkClaimSubjectTypeType,
18
+ type WorkClaimVersionKind as WorkClaimVersionKindType,
19
+ } from "@opengeni/contracts";
20
+ import { sql } from "drizzle-orm";
21
+ import type { Database } from "./database";
22
+ import { rawRows, withSessionActivityRlsContext } from "./database";
23
+
24
+ export type WorkClaimAttemptClaims = {
25
+ accountId: string;
26
+ workspaceId: string;
27
+ sessionId: string;
28
+ turnId: string;
29
+ attemptId: string;
30
+ executionGeneration: number;
31
+ };
32
+
33
+ export type UpsertWorkClaimInput = WorkClaimAttemptClaims & {
34
+ operationId: string;
35
+ expectedRevision: number;
36
+ subjectNamespace: string;
37
+ subjectType: WorkClaimSubjectTypeType;
38
+ canonicalKey: string;
39
+ displayLabel?: string | null;
40
+ role: WorkClaimRoleType;
41
+ version?: {
42
+ kind: WorkClaimVersionKindType;
43
+ value: string;
44
+ } | null;
45
+ };
46
+
47
+ export type ReleaseWorkClaimInput = WorkClaimAttemptClaims & {
48
+ operationId: string;
49
+ claimId: string;
50
+ expectedRevision: number;
51
+ reason: string;
52
+ };
53
+
54
+ type WorkClaimMutationRow = {
55
+ claim_id: string;
56
+ session_id: string;
57
+ root_session_id: string;
58
+ subject_namespace: string;
59
+ subject_type: string;
60
+ canonical_key: string;
61
+ display_label: string | null;
62
+ role: string;
63
+ state: string;
64
+ revision: number;
65
+ provenance: string;
66
+ version_kind: string | null;
67
+ version_value: string | null;
68
+ observed_at: Date | string;
69
+ created_at: Date | string;
70
+ updated_at: Date | string;
71
+ settled_at: Date | string | null;
72
+ mutation_kind: string;
73
+ replayed: boolean;
74
+ };
75
+
76
+ function iso(value: Date | string): string {
77
+ return (value instanceof Date ? value : new Date(value)).toISOString();
78
+ }
79
+
80
+ function assertAttemptClaims(claims: WorkClaimAttemptClaims): void {
81
+ if (!Number.isSafeInteger(claims.executionGeneration) || claims.executionGeneration < 1) {
82
+ throw new Error("Work claims require a positive exact execution generation");
83
+ }
84
+ }
85
+
86
+ function mutationResultFromRow(row: WorkClaimMutationRow) {
87
+ const version =
88
+ row.version_kind === null || row.version_value === null
89
+ ? null
90
+ : {
91
+ kind: WorkClaimVersionKind.parse(row.version_kind),
92
+ value: WorkClaimVersionValue.parse(row.version_value),
93
+ };
94
+ return WorkClaimMutationResult.parse({
95
+ claim: WorkClaim.parse({
96
+ id: row.claim_id,
97
+ sessionId: row.session_id,
98
+ rootSessionId: row.root_session_id,
99
+ subject: {
100
+ namespace: row.subject_namespace,
101
+ type: row.subject_type,
102
+ canonicalKey: row.canonical_key,
103
+ displayLabel: row.display_label,
104
+ },
105
+ role: row.role,
106
+ state: row.state,
107
+ revision: Number(row.revision),
108
+ provenance: row.provenance,
109
+ version,
110
+ observedAt: iso(row.observed_at),
111
+ createdAt: iso(row.created_at),
112
+ updatedAt: iso(row.updated_at),
113
+ settledAt: row.settled_at === null ? null : iso(row.settled_at),
114
+ }),
115
+ mutation: WorkClaimMutationKind.parse(row.mutation_kind),
116
+ replayed: row.replayed,
117
+ });
118
+ }
119
+
120
+ export async function upsertWorkClaim(db: Database, input: UpsertWorkClaimInput) {
121
+ assertAttemptClaims(input);
122
+ if (!Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0) {
123
+ throw new Error("Work claim expectedRevision must be a non-negative safe integer");
124
+ }
125
+ const subjectNamespace = WorkClaimNamespace.parse(
126
+ normalizeWorkClaimNamespace(input.subjectNamespace),
127
+ );
128
+ const subjectType = WorkClaimSubjectType.parse(input.subjectType);
129
+ const canonicalKey = WorkClaimCanonicalKey.parse(
130
+ normalizeWorkClaimCanonicalKey(input.canonicalKey),
131
+ );
132
+ const role = WorkClaimRole.parse(input.role);
133
+ const displayLabel =
134
+ input.displayLabel === undefined || input.displayLabel === null
135
+ ? null
136
+ : WorkClaimDisplayLabel.parse(normalizeWorkClaimDisplayLabel(input.displayLabel));
137
+ const version = input.version
138
+ ? {
139
+ kind: WorkClaimVersionKind.parse(input.version.kind),
140
+ value: WorkClaimVersionValue.parse(normalizeWorkClaimCanonicalKey(input.version.value)),
141
+ }
142
+ : null;
143
+ const rows = await withSessionActivityRlsContext(
144
+ db,
145
+ { accountId: input.accountId, workspaceId: input.workspaceId },
146
+ async (tx) =>
147
+ await rawRows<WorkClaimMutationRow>(
148
+ tx,
149
+ sql`SELECT * FROM upsert_session_work_claim_for_attempt(
150
+ ${input.accountId}::uuid,
151
+ ${input.workspaceId}::uuid,
152
+ ${input.sessionId}::uuid,
153
+ ${input.turnId}::uuid,
154
+ ${input.attemptId}::uuid,
155
+ ${input.executionGeneration}::integer,
156
+ ${input.operationId}::uuid,
157
+ ${input.expectedRevision}::integer,
158
+ ${subjectNamespace}::text,
159
+ ${subjectType}::text,
160
+ ${canonicalKey}::text,
161
+ ${displayLabel}::text,
162
+ ${role}::text,
163
+ ${version?.kind ?? null}::text,
164
+ ${version?.value ?? null}::text
165
+ )`,
166
+ ),
167
+ );
168
+ if (rows.length !== 1) throw new Error("Work claim upsert returned no durable receipt");
169
+ return mutationResultFromRow(rows[0]!);
170
+ }
171
+
172
+ export async function releaseWorkClaim(db: Database, input: ReleaseWorkClaimInput) {
173
+ assertAttemptClaims(input);
174
+ if (!Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 1) {
175
+ throw new Error("Work claim release requires a positive expectedRevision");
176
+ }
177
+ const reason = WorkClaimReleaseReason.parse(input.reason);
178
+ const rows = await withSessionActivityRlsContext(
179
+ db,
180
+ { accountId: input.accountId, workspaceId: input.workspaceId },
181
+ async (tx) =>
182
+ await rawRows<WorkClaimMutationRow>(
183
+ tx,
184
+ sql`SELECT * FROM release_session_work_claim_for_attempt(
185
+ ${input.accountId}::uuid,
186
+ ${input.workspaceId}::uuid,
187
+ ${input.sessionId}::uuid,
188
+ ${input.turnId}::uuid,
189
+ ${input.attemptId}::uuid,
190
+ ${input.executionGeneration}::integer,
191
+ ${input.operationId}::uuid,
192
+ ${input.claimId}::uuid,
193
+ ${input.expectedRevision}::integer,
194
+ ${reason}::text
195
+ )`,
196
+ ),
197
+ );
198
+ if (rows.length !== 1) throw new Error("Work claim release returned no durable receipt");
199
+ return mutationResultFromRow(rows[0]!);
200
+ }