@opengeni/db 0.31.1 → 0.36.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 (122) hide show
  1. package/dist/browser-auth.d.ts +209 -1
  2. package/dist/browser-downloads.d.ts +51 -0
  3. package/dist/browser-identities.d.ts +11 -2
  4. package/dist/browser-sessions.d.ts +83 -1
  5. package/dist/browser-state-artifacts.d.ts +75 -1
  6. package/dist/canonical-human-identities.d.ts +48 -0
  7. package/dist/canonical-human-identities.js +28 -0
  8. package/dist/canonical-human-identities.js.map +1 -0
  9. package/dist/capability-integrations.d.ts +8 -6
  10. package/dist/{chunk-MWDVXQOL.js → chunk-5KTIEDX7.js} +5773 -4212
  11. package/dist/chunk-5KTIEDX7.js.map +1 -0
  12. package/dist/{chunk-NBE6CHRI.js → chunk-CQZ4QSP5.js} +49 -9
  13. package/dist/chunk-CQZ4QSP5.js.map +1 -0
  14. package/dist/chunk-F32YT7MP.js +117 -0
  15. package/dist/chunk-F32YT7MP.js.map +1 -0
  16. package/dist/{chunk-ISI2TIUG.js → chunk-JC5QPLUM.js} +30 -3
  17. package/dist/chunk-JC5QPLUM.js.map +1 -0
  18. package/dist/{chunk-F5FT7BEZ.js → chunk-P3RE4D2B.js} +2 -2
  19. package/dist/chunk-TAUTWOS5.js +259 -0
  20. package/dist/chunk-TAUTWOS5.js.map +1 -0
  21. package/dist/{chunk-YHZXQ7HP.js → chunk-VKLB2ZGI.js} +446 -19
  22. package/dist/chunk-VKLB2ZGI.js.map +1 -0
  23. package/dist/database.d.ts +5 -0
  24. package/dist/editable-artifact-durable-export.js +2 -2
  25. package/dist/editable-artifacts.js +3 -3
  26. package/dist/index.d.ts +438 -29
  27. package/dist/index.js +35640 -28969
  28. package/dist/index.js.map +1 -1
  29. package/dist/insights.d.ts +23 -7
  30. package/dist/integration-bindings.d.ts +24 -24
  31. package/dist/integration-facets.d.ts +99 -0
  32. package/dist/interaction-revisions.d.ts +11 -0
  33. package/dist/interaction-schema.d.ts +698 -34
  34. package/dist/provision-roles.js +1 -1
  35. package/dist/runtime-posture.d.ts +8 -7
  36. package/dist/schema.d.ts +9464 -5412
  37. package/dist/schema.js +51 -9
  38. package/dist/session-control.d.ts +29 -3
  39. package/dist/session-tenancy.d.ts +44 -0
  40. package/dist/session-tenancy.js +16 -0
  41. package/dist/session-tenancy.js.map +1 -0
  42. package/dist/slack-task-policy-schema.d.ts +847 -0
  43. package/dist/slack-task-policy.d.ts +65 -0
  44. package/dist/task-notes-schema.d.ts +924 -0
  45. package/dist/task-notes.d.ts +85 -0
  46. package/dist/video-generation.d.ts +9 -0
  47. package/dist/video-generation.js +5 -3
  48. package/dist/xai-subscription.d.ts +326 -0
  49. package/drizzle/0212_browser_state_transfer_hardening.sql +560 -0
  50. package/drizzle/0213_browser_interaction_authority.sql +67 -0
  51. package/drizzle/0214_browser_download_saves.sql +34 -0
  52. package/drizzle/0215_browser_controller_host.sql +385 -0
  53. package/drizzle/0216_browser_auth_health_evidence.sql +137 -0
  54. package/drizzle/0217_external_browser_auth_operations.sql +22 -0
  55. package/drizzle/0218_organization_tenancy_foundation.sql +299 -0
  56. package/drizzle/0219_organization_tenancy_managed_human_provisioning.sql +317 -0
  57. package/drizzle/0219_site_auth_maintenance_sessions.sql +234 -0
  58. package/drizzle/0220_memory_slack_append_only_cascade.sql +38 -0
  59. package/drizzle/0220_session_channels.sql +54 -0
  60. package/drizzle/0221_sessions_channel_index.sql +5 -0
  61. package/drizzle/0222_session_visibility_authority_epochs.sql +158 -0
  62. package/drizzle/0222_sessions_channel_fk.sql +89 -0
  63. package/drizzle/0223_pending_tool_event_output.sql +55 -0
  64. package/drizzle/0223_sessions_channel_fk_validate.sql +118 -0
  65. package/drizzle/0224_slack_post_outcome_reconciliation.sql +123 -0
  66. package/drizzle/0225_session_visibility_fork_activation.sql +1363 -0
  67. package/drizzle/0226_personal_codex_authority_foundation.sql +365 -0
  68. package/drizzle/0227_slack_native_actions.sql +145 -0
  69. package/drizzle/0228_interaction_controller_data_plane.sql +14 -0
  70. package/drizzle/0228_slack_task_policy.sql +400 -0
  71. package/drizzle/0229_slack_inbox_file_fact.sql +10 -0
  72. package/drizzle/0230_user_scoped_variable_sets_rigs.sql +84 -0
  73. package/drizzle/0231_integration_definition_identity_cutover.sql +271 -0
  74. package/drizzle/0232_integration_facet_authority_cutover.sql +610 -0
  75. package/drizzle/0233_skill_and_integration_authority_cutover.sql +1162 -0
  76. package/drizzle/0234_xai_subscription_authority.sql +1139 -0
  77. package/drizzle/0235_canonical_human_login_bindings.sql +920 -0
  78. package/drizzle/0236_browser_identity_lifecycle.sql +39 -0
  79. package/drizzle/0236_session_visibility_slack_policy.sql +72 -0
  80. package/drizzle/0237_interaction_transition_reaper.sql +263 -0
  81. package/drizzle/0238_goal_persistence_policy.sql +471 -0
  82. package/drizzle/0238_supergrok_realtime_model.sql +29 -0
  83. package/drizzle/0239_supergrok_video_funding.sql +50 -0
  84. package/drizzle/0239_task_tree_notes.sql +990 -0
  85. package/package.json +13 -5
  86. package/src/browser-auth.ts +2600 -123
  87. package/src/browser-downloads.ts +517 -0
  88. package/src/browser-identities.ts +220 -3
  89. package/src/browser-sessions.ts +334 -45
  90. package/src/browser-state-artifacts.ts +402 -12
  91. package/src/canonical-human-identities.ts +317 -0
  92. package/src/capability-integrations.ts +121 -213
  93. package/src/database.ts +41 -1
  94. package/src/index.ts +4157 -394
  95. package/src/insights.ts +109 -28
  96. package/src/integration-bindings.ts +101 -101
  97. package/src/{integration-features.ts → integration-facets.ts} +187 -184
  98. package/src/interaction-revisions.ts +33 -3
  99. package/src/interaction-schema.ts +311 -8
  100. package/src/pack-components.ts +43 -43
  101. package/src/pack-installations.ts +0 -43
  102. package/src/plugin-packages.ts +9 -9
  103. package/src/provision-roles.ts +232 -0
  104. package/src/runtime-posture.ts +227 -14
  105. package/src/schema.ts +1188 -75
  106. package/src/session-control.ts +122 -37
  107. package/src/session-queue-commands.ts +78 -1
  108. package/src/session-tenancy.ts +188 -0
  109. package/src/session-tool-call-settlement.ts +9 -3
  110. package/src/slack-task-policy-schema.ts +130 -0
  111. package/src/slack-task-policy.ts +341 -0
  112. package/src/task-notes-schema.ts +135 -0
  113. package/src/task-notes.ts +178 -0
  114. package/src/video-generation.ts +90 -12
  115. package/src/workspace-artifacts.ts +1 -1
  116. package/src/xai-subscription.ts +1887 -0
  117. package/dist/chunk-ISI2TIUG.js.map +0 -1
  118. package/dist/chunk-MWDVXQOL.js.map +0 -1
  119. package/dist/chunk-NBE6CHRI.js.map +0 -1
  120. package/dist/chunk-YHZXQ7HP.js.map +0 -1
  121. package/dist/integration-features.d.ts +0 -99
  122. /package/dist/{chunk-F5FT7BEZ.js.map → chunk-P3RE4D2B.js.map} +0 -0
@@ -0,0 +1,178 @@
1
+ import {
2
+ TASK_NOTE_LIST_DEFAULT_LIMIT,
3
+ TASK_NOTE_LIST_MAX_LIMIT,
4
+ TaskNote,
5
+ TaskNoteKind,
6
+ TaskNoteListResponse,
7
+ TaskNoteMutationResult,
8
+ TaskNoteReason,
9
+ TaskNoteText,
10
+ type TaskNoteKind as TaskNoteKindType,
11
+ } from "@opengeni/contracts";
12
+ import { sql } from "drizzle-orm";
13
+ import type { Database } from "./database";
14
+ import { rawRows, withRlsContext } from "./database";
15
+
16
+ export type TaskNoteAttemptClaims = {
17
+ accountId: string;
18
+ workspaceId: string;
19
+ sessionId: string;
20
+ turnId: string;
21
+ attemptId: string;
22
+ executionGeneration: number;
23
+ };
24
+
25
+ export type CreateTaskNoteInput = TaskNoteAttemptClaims & {
26
+ operationId: string;
27
+ kind: TaskNoteKindType;
28
+ text: string;
29
+ expiresInDays: number;
30
+ };
31
+
32
+ export type ArchiveTaskNoteInput = TaskNoteAttemptClaims & {
33
+ operationId: string;
34
+ noteId: string;
35
+ expectedVersion: number;
36
+ reason: string;
37
+ };
38
+
39
+ export type ListTaskNotesInput = TaskNoteAttemptClaims & {
40
+ includeArchived?: boolean;
41
+ limit?: number;
42
+ };
43
+
44
+ type TaskNoteRow = {
45
+ note_id: string;
46
+ root_session_id: string;
47
+ kind: string;
48
+ note_text: string;
49
+ status: string;
50
+ version: number;
51
+ expires_at: Date | string;
52
+ created_at: Date | string;
53
+ updated_at: Date | string;
54
+ archived_at: Date | string | null;
55
+ actor_kind: string;
56
+ source_session_id: string;
57
+ source_turn_id: string;
58
+ replayed?: boolean;
59
+ };
60
+
61
+ function iso(value: Date | string): string {
62
+ return (value instanceof Date ? value : new Date(value)).toISOString();
63
+ }
64
+
65
+ function noteFromRow(row: TaskNoteRow) {
66
+ return TaskNote.parse({
67
+ id: row.note_id,
68
+ rootSessionId: row.root_session_id,
69
+ kind: row.kind,
70
+ text: row.note_text,
71
+ status: row.status,
72
+ version: row.version,
73
+ expiresAt: iso(row.expires_at),
74
+ createdAt: iso(row.created_at),
75
+ updatedAt: iso(row.updated_at),
76
+ archivedAt: row.archived_at === null ? null : iso(row.archived_at),
77
+ provenance: {
78
+ actorKind: row.actor_kind,
79
+ sourceSessionId: row.source_session_id,
80
+ sourceTurnId: row.source_turn_id,
81
+ },
82
+ });
83
+ }
84
+
85
+ function assertAttemptClaims(claims: TaskNoteAttemptClaims): void {
86
+ if (!Number.isSafeInteger(claims.executionGeneration) || claims.executionGeneration < 1) {
87
+ throw new Error("Task notes require a positive exact execution generation");
88
+ }
89
+ }
90
+
91
+ export async function createTaskNote(db: Database, input: CreateTaskNoteInput) {
92
+ assertAttemptClaims(input);
93
+ TaskNoteKind.parse(input.kind);
94
+ TaskNoteText.parse(input.text);
95
+ if (
96
+ !Number.isSafeInteger(input.expiresInDays) ||
97
+ input.expiresInDays < 1 ||
98
+ input.expiresInDays > 30
99
+ ) {
100
+ throw new Error("Task note expiry must be 1-30 whole days");
101
+ }
102
+ const rows = await withRlsContext(
103
+ db,
104
+ { accountId: input.accountId, workspaceId: input.workspaceId },
105
+ async (tx) =>
106
+ await rawRows<TaskNoteRow>(
107
+ tx,
108
+ sql`SELECT * FROM create_task_note_for_attempt(
109
+ ${input.accountId}::uuid,
110
+ ${input.workspaceId}::uuid,
111
+ ${input.sessionId}::uuid,
112
+ ${input.turnId}::uuid,
113
+ ${input.attemptId}::uuid,
114
+ ${input.executionGeneration}::integer,
115
+ ${input.operationId}::uuid,
116
+ ${input.kind}::text,
117
+ ${input.text}::text,
118
+ ${input.expiresInDays}::integer
119
+ )`,
120
+ ),
121
+ );
122
+ if (rows.length !== 1) throw new Error("Task note create returned no durable receipt");
123
+ return TaskNoteMutationResult.parse({ note: noteFromRow(rows[0]!), replayed: rows[0]!.replayed });
124
+ }
125
+
126
+ export async function archiveTaskNote(db: Database, input: ArchiveTaskNoteInput) {
127
+ assertAttemptClaims(input);
128
+ TaskNoteReason.parse(input.reason);
129
+ const rows = await withRlsContext(
130
+ db,
131
+ { accountId: input.accountId, workspaceId: input.workspaceId },
132
+ async (tx) =>
133
+ await rawRows<TaskNoteRow>(
134
+ tx,
135
+ sql`SELECT * FROM archive_task_note_for_attempt(
136
+ ${input.accountId}::uuid,
137
+ ${input.workspaceId}::uuid,
138
+ ${input.sessionId}::uuid,
139
+ ${input.turnId}::uuid,
140
+ ${input.attemptId}::uuid,
141
+ ${input.executionGeneration}::integer,
142
+ ${input.operationId}::uuid,
143
+ ${input.noteId}::uuid,
144
+ ${input.expectedVersion}::integer,
145
+ ${input.reason}::text
146
+ )`,
147
+ ),
148
+ );
149
+ if (rows.length !== 1) throw new Error("Task note archive returned no durable receipt");
150
+ return TaskNoteMutationResult.parse({ note: noteFromRow(rows[0]!), replayed: rows[0]!.replayed });
151
+ }
152
+
153
+ export async function listTaskNotes(db: Database, input: ListTaskNotesInput) {
154
+ assertAttemptClaims(input);
155
+ const limit = input.limit ?? TASK_NOTE_LIST_DEFAULT_LIMIT;
156
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > TASK_NOTE_LIST_MAX_LIMIT) {
157
+ throw new Error(`Task note list limit must be 1-${TASK_NOTE_LIST_MAX_LIMIT}`);
158
+ }
159
+ const rows = await withRlsContext(
160
+ db,
161
+ { accountId: input.accountId, workspaceId: input.workspaceId },
162
+ async (tx) =>
163
+ await rawRows<TaskNoteRow>(
164
+ tx,
165
+ sql`SELECT * FROM list_task_notes_for_attempt(
166
+ ${input.accountId}::uuid,
167
+ ${input.workspaceId}::uuid,
168
+ ${input.sessionId}::uuid,
169
+ ${input.turnId}::uuid,
170
+ ${input.attemptId}::uuid,
171
+ ${input.executionGeneration}::integer,
172
+ ${input.includeArchived ?? false}::boolean,
173
+ ${limit}::integer
174
+ )`,
175
+ ),
176
+ );
177
+ return TaskNoteListResponse.parse({ notes: rows.map(noteFromRow) });
178
+ }
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  GENERATED_VIDEO_MAX_BYTES,
3
3
  GeneratedVideoReceipt,
4
+ GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
4
5
  SEEDANCE_2_5_MODEL_ID,
5
6
  VideoGenerationPolicy,
6
7
  VideoGenerationOperationSummary,
@@ -236,8 +237,13 @@ export async function admitVideoGenerationOperation(
236
237
  }
237
238
  await tx
238
239
  .insert(schema.workspaceVideoGenerationQuotas)
239
- .values({ workspaceId: input.workspaceId, accountId: input.accountId })
240
- .onConflictDoNothing({ target: schema.workspaceVideoGenerationQuotas.workspaceId });
240
+ .values({
241
+ workspaceId: input.workspaceId,
242
+ accountId: input.accountId,
243
+ })
244
+ .onConflictDoNothing({
245
+ target: schema.workspaceVideoGenerationQuotas.workspaceId,
246
+ });
241
247
  const [quota] = await tx
242
248
  .select()
243
249
  .from(schema.workspaceVideoGenerationQuotas)
@@ -311,6 +317,44 @@ export async function admitVideoGenerationOperation(
311
317
  );
312
318
  }
313
319
 
320
+ /** Persist a rotated provider token before it is used for another remote call. */
321
+ export async function rotateVideoGenerationCredential(
322
+ db: Database,
323
+ input: {
324
+ accountId: string;
325
+ workspaceId: string;
326
+ operationId: string;
327
+ requestDigest: string;
328
+ expectedCredentialEncrypted: string;
329
+ credentialEncrypted: string;
330
+ },
331
+ ): Promise<VideoGenerationOperation> {
332
+ const [row] = await db
333
+ .update(schema.videoGenerationOperations)
334
+ .set({
335
+ credentialEncrypted: input.credentialEncrypted,
336
+ updatedAt: new Date(),
337
+ })
338
+ .where(
339
+ and(
340
+ eq(schema.videoGenerationOperations.accountId, input.accountId),
341
+ eq(schema.videoGenerationOperations.workspaceId, input.workspaceId),
342
+ eq(schema.videoGenerationOperations.id, input.operationId),
343
+ eq(schema.videoGenerationOperations.requestDigest, input.requestDigest),
344
+ eq(schema.videoGenerationOperations.credentialEncrypted, input.expectedCredentialEncrypted),
345
+ inArray(schema.videoGenerationOperations.status, [
346
+ "accepted",
347
+ "submission_uncertain",
348
+ "provider_started",
349
+ "retaining",
350
+ ]),
351
+ ),
352
+ )
353
+ .returning();
354
+ if (!row) throw new VideoGenerationConflictError("Video provider credential changed");
355
+ return row;
356
+ }
357
+
314
358
  /**
315
359
  * Seal a reference-bearing operation only after every immutable staging object
316
360
  * has been uploaded and independently verified by the worker. The source
@@ -422,11 +466,17 @@ export async function markVideoGenerationAcceptedInTransaction(
422
466
  operation.connectionId === null &&
423
467
  (operation.creditState === "debited" ||
424
468
  (operation.creditState === "not_applicable" && operation.pricedCostMicros === 0));
469
+ const supergrokAuthorized =
470
+ operation.fundingSource === "supergrok_subscription" &&
471
+ operation.connectionId === null &&
472
+ operation.credentialEncrypted !== null &&
473
+ operation.pricedCostMicros === 0 &&
474
+ operation.creditState === "not_applicable";
425
475
  if (
426
476
  !policy ||
427
477
  policy.fundingSource !== operation.fundingSource ||
428
478
  !policy.enabledModelIds.includes(operation.modelId) ||
429
- (!workspaceGatewayAuthorized && !managedAuthorized)
479
+ (!workspaceGatewayAuthorized && !managedAuthorized && !supergrokAuthorized)
430
480
  ) {
431
481
  throw new VideoGenerationConflictError("Video generation authorization changed");
432
482
  }
@@ -684,7 +734,10 @@ export async function markVideoGenerationSubmissionIntent(
684
734
 
685
735
  export async function markVideoGenerationProviderStarted(
686
736
  db: Database,
687
- input: OperationMutationInput & { providerJobId: string; nextReconcileAt: Date },
737
+ input: OperationMutationInput & {
738
+ providerJobId: string;
739
+ nextReconcileAt: Date;
740
+ },
688
741
  ): Promise<VideoGenerationOperation> {
689
742
  return await mutateOperation(db, input, async (tx, operation) => {
690
743
  if (
@@ -720,7 +773,11 @@ export async function markVideoGenerationProviderStarted(
720
773
 
721
774
  export async function rescheduleVideoGenerationOperation(
722
775
  db: Database,
723
- input: OperationMutationInput & { owner?: string; nextReconcileAt: Date; error?: string },
776
+ input: OperationMutationInput & {
777
+ owner?: string;
778
+ nextReconcileAt: Date;
779
+ error?: string;
780
+ },
724
781
  ): Promise<boolean> {
725
782
  return await withRlsContext(
726
783
  db,
@@ -832,7 +889,10 @@ export type SettleVideoGenerationReadyInput = OperationMutationInput & {
832
889
  export async function settleVideoGenerationReady(
833
890
  db: Database,
834
891
  input: SettleVideoGenerationReadyInput,
835
- ): Promise<{ operation: VideoGenerationOperation; artifact: GeneratedVideoArtifact }> {
892
+ ): Promise<{
893
+ operation: VideoGenerationOperation;
894
+ artifact: GeneratedVideoArtifact;
895
+ }> {
836
896
  return await withRlsContext(
837
897
  db,
838
898
  { accountId: input.accountId, workspaceId: input.workspaceId },
@@ -931,6 +991,8 @@ export async function settleVideoGenerationReady(
931
991
  providerRequestExpiresAt: null,
932
992
  credentialEncrypted: null,
933
993
  requestEncrypted: null,
994
+ boundedPublicReason: null,
995
+ lastError: null,
934
996
  privateDataEraseAfter: now,
935
997
  reconcileLeaseOwner: null,
936
998
  reconcileLeaseExpiresAt: null,
@@ -950,7 +1012,10 @@ export async function getGeneratedVideoArtifact(
950
1012
  db: Database,
951
1013
  workspaceId: string,
952
1014
  artifactId: string,
953
- ): Promise<{ artifact: GeneratedVideoArtifact; file: typeof schema.files.$inferSelect } | null> {
1015
+ ): Promise<{
1016
+ artifact: GeneratedVideoArtifact;
1017
+ file: typeof schema.files.$inferSelect;
1018
+ } | null> {
954
1019
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
955
1020
  const [row] = await scopedDb
956
1021
  .select({ artifact: schema.generatedVideoArtifacts, file: schema.files })
@@ -971,13 +1036,21 @@ export async function listSessionGeneratedVideoArtifacts(
971
1036
  db: Database,
972
1037
  workspaceId: string,
973
1038
  sessionId: string,
974
- ): Promise<Array<{ artifact: GeneratedVideoArtifact; file: typeof schema.files.$inferSelect }>> {
1039
+ ): Promise<
1040
+ Array<{
1041
+ artifact: GeneratedVideoArtifact;
1042
+ file: typeof schema.files.$inferSelect;
1043
+ }>
1044
+ > {
975
1045
  return await withWorkspaceRls(
976
1046
  db,
977
1047
  workspaceId,
978
1048
  async (scopedDb) =>
979
1049
  await scopedDb
980
- .select({ artifact: schema.generatedVideoArtifacts, file: schema.files })
1050
+ .select({
1051
+ artifact: schema.generatedVideoArtifacts,
1052
+ file: schema.files,
1053
+ })
981
1054
  .from(schema.generatedVideoArtifacts)
982
1055
  .innerJoin(schema.files, eq(schema.files.id, schema.generatedVideoArtifacts.primaryFileId))
983
1056
  .where(
@@ -1026,7 +1099,9 @@ export async function markVideoGenerationReferenceCleaned(
1026
1099
  eq(schema.videoGenerationReferences.stagingObjectKey, input.stagingObjectKey),
1027
1100
  ),
1028
1101
  )
1029
- .returning({ operationId: schema.videoGenerationReferences.operationId });
1102
+ .returning({
1103
+ operationId: schema.videoGenerationReferences.operationId,
1104
+ });
1030
1105
  if (updated) return true;
1031
1106
  const [existing] = await scopedDb
1032
1107
  .select({ cleanedAt: schema.videoGenerationReferences.cleanedAt })
@@ -1177,7 +1252,10 @@ export async function mediaGenerationResultForStoredOperation(
1177
1252
  },
1178
1253
  sandboxPath: `/workspace/generated-videos/${retained.artifact.sandboxFilename}`,
1179
1254
  });
1180
- return mediaGenerationResultForOperation({ operation, readyReceipt: receipt });
1255
+ return mediaGenerationResultForOperation({
1256
+ operation,
1257
+ readyReceipt: receipt,
1258
+ });
1181
1259
  }
1182
1260
 
1183
1261
  export async function getVideoGenerationOperationSummary(
@@ -1399,7 +1477,7 @@ function policyFromRow(
1399
1477
 
1400
1478
  function assertSupportedModels(modelIds: readonly string[]): void {
1401
1479
  for (const modelId of modelIds) {
1402
- if (modelId !== SEEDANCE_2_5_MODEL_ID) {
1480
+ if (modelId !== SEEDANCE_2_5_MODEL_ID && modelId !== GROK_IMAGINE_VIDEO_1_5_MODEL_ID) {
1403
1481
  throw new Error(`Unknown video generation model: ${modelId}`);
1404
1482
  }
1405
1483
  }
@@ -320,7 +320,7 @@ async function assertAttemptAuthority(
320
320
  session.first_party_mcp_permissions IS NULL
321
321
  OR session.first_party_mcp_permissions @> '["artifacts:publish"]'::jsonb
322
322
  )
323
- FOR UPDATE OF session
323
+ FOR NO KEY UPDATE OF session
324
324
  ), locked_turn AS MATERIALIZED (
325
325
  SELECT turn.id, turn.account_id, turn.workspace_id, turn.session_id,
326
326
  turn.active_attempt_id, turn.execution_generation, turn.initiator_subject_id