@opengeni/db 1.3.0 → 1.4.2

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 (91) hide show
  1. package/dist/canonical-human-identities.js +3 -3
  2. package/dist/capability-reconciliation.js +2 -2
  3. package/dist/{chunk-7QWWTVYH.js → chunk-5ROHLEN6.js} +29 -4
  4. package/dist/chunk-5ROHLEN6.js.map +1 -0
  5. package/dist/{chunk-AQMH2ZEH.js → chunk-A4J7EECR.js} +2 -2
  6. package/dist/{chunk-VQX54TK3.js → chunk-B5C5SBSI.js} +3 -3
  7. package/dist/{chunk-OI6YZXTU.js → chunk-DKFEUGM4.js} +2 -2
  8. package/dist/{chunk-HYBQABBJ.js → chunk-HUKBIZGH.js} +2 -2
  9. package/dist/{chunk-LRSIODCQ.js → chunk-IT3EYY2C.js} +6 -4
  10. package/dist/{chunk-LRSIODCQ.js.map → chunk-IT3EYY2C.js.map} +1 -1
  11. package/dist/{chunk-DSNEIW5E.js → chunk-JL6TBB76.js} +2 -1
  12. package/dist/chunk-JL6TBB76.js.map +1 -0
  13. package/dist/{chunk-OEFNTGR3.js → chunk-JWBEE4CL.js} +4 -4
  14. package/dist/{chunk-BRVBLQAL.js → chunk-K5MTNKJU.js} +4 -4
  15. package/dist/{chunk-6UKNJ43T.js → chunk-NZ2SP55O.js} +2 -2
  16. package/dist/{chunk-3XHC5KC5.js → chunk-OKCBARX6.js} +2 -2
  17. package/dist/{chunk-PNWWNEDM.js → chunk-OV5AUIBA.js} +2 -2
  18. package/dist/{chunk-BID3CNN3.js → chunk-TODVDGMO.js} +36 -18
  19. package/dist/{chunk-BID3CNN3.js.map → chunk-TODVDGMO.js.map} +1 -1
  20. package/dist/{chunk-6S5T6FSP.js → chunk-W75DRYG3.js} +5 -5
  21. package/dist/{chunk-7S3O33NM.js → chunk-Y72DP3JF.js} +2 -2
  22. package/dist/codemode-approvals.d.ts +7 -1
  23. package/dist/connect-attempts.js +3 -3
  24. package/dist/connection-metadata.d.ts +416 -0
  25. package/dist/credit-balances.d.ts +25 -4
  26. package/dist/database.d.ts +7 -1
  27. package/dist/editable-artifact-durable-export.js +2 -2
  28. package/dist/editable-artifacts.js +3 -3
  29. package/dist/index.d.ts +51 -8
  30. package/dist/index.js +10826 -10129
  31. package/dist/index.js.map +1 -1
  32. package/dist/legacy-tool-action-reviews.d.ts +75 -0
  33. package/dist/managed-auth-session-sets.js +3 -3
  34. package/dist/mcp-operations.js +2 -2
  35. package/dist/modal-native-live-origin.d.ts +75 -0
  36. package/dist/organization-integration-policy.js +3 -3
  37. package/dist/organization-slack-bots.d.ts +1 -1
  38. package/dist/provision-roles.js +1 -1
  39. package/dist/retained-provider-commands.d.ts +21 -21
  40. package/dist/retained-provider-commands.js +6 -6
  41. package/dist/runtime-posture.d.ts +1 -1
  42. package/dist/sandbox-interaction-idle.d.ts +22 -0
  43. package/dist/schema.js +1 -1
  44. package/dist/session-background-commands.js +4 -4
  45. package/dist/session-command-output.js +5 -5
  46. package/dist/session-event-slices.js +3 -3
  47. package/dist/session-mcp-credential-rotation.js +5 -5
  48. package/dist/session-realtime-billing.d.ts +48 -0
  49. package/dist/session-realtime.d.ts +8 -2
  50. package/dist/session-tenancy.js +3 -3
  51. package/dist/video-generation.js +3 -3
  52. package/dist/voice-transcription-settlement.d.ts +36 -0
  53. package/dist/workspace-tool-defaults.js +4 -4
  54. package/drizzle/0623_voice_transcription_attribution.sql +128 -0
  55. package/drizzle/0624_azure_live_realtime_model.sql +30 -0
  56. package/drizzle/0625_transcription_billing_refusal_codes.sql +29 -0
  57. package/drizzle/0632_modal_native_live_origin.sql +239 -0
  58. package/package.json +7 -7
  59. package/src/codemode-approvals.ts +52 -22
  60. package/src/codemode-operations.ts +198 -181
  61. package/src/connection-metadata.ts +166 -0
  62. package/src/credit-balances.ts +58 -17
  63. package/src/database.ts +9 -0
  64. package/src/index.ts +540 -412
  65. package/src/insights-unified.ts +2 -1
  66. package/src/legacy-tool-action-reviews.ts +242 -0
  67. package/src/modal-native-live-origin.ts +148 -0
  68. package/src/organization-slack-bots.ts +1 -1
  69. package/src/provision-roles.ts +12 -0
  70. package/src/runtime-posture.ts +32 -3
  71. package/src/sandbox-interaction-idle.ts +91 -0
  72. package/src/schema.ts +1 -0
  73. package/src/session-realtime-billing.ts +135 -0
  74. package/src/session-realtime-ledger.ts +9 -4
  75. package/src/session-realtime.ts +20 -2
  76. package/src/tool-action-reviews.ts +129 -18
  77. package/src/transcription-recordings.ts +3 -1
  78. package/src/voice-transcription-settlement.ts +94 -0
  79. package/dist/chunk-7QWWTVYH.js.map +0 -1
  80. package/dist/chunk-DSNEIW5E.js.map +0 -1
  81. /package/dist/{chunk-AQMH2ZEH.js.map → chunk-A4J7EECR.js.map} +0 -0
  82. /package/dist/{chunk-VQX54TK3.js.map → chunk-B5C5SBSI.js.map} +0 -0
  83. /package/dist/{chunk-OI6YZXTU.js.map → chunk-DKFEUGM4.js.map} +0 -0
  84. /package/dist/{chunk-HYBQABBJ.js.map → chunk-HUKBIZGH.js.map} +0 -0
  85. /package/dist/{chunk-OEFNTGR3.js.map → chunk-JWBEE4CL.js.map} +0 -0
  86. /package/dist/{chunk-BRVBLQAL.js.map → chunk-K5MTNKJU.js.map} +0 -0
  87. /package/dist/{chunk-6UKNJ43T.js.map → chunk-NZ2SP55O.js.map} +0 -0
  88. /package/dist/{chunk-3XHC5KC5.js.map → chunk-OKCBARX6.js.map} +0 -0
  89. /package/dist/{chunk-PNWWNEDM.js.map → chunk-OV5AUIBA.js.map} +0 -0
  90. /package/dist/{chunk-6S5T6FSP.js.map → chunk-W75DRYG3.js.map} +0 -0
  91. /package/dist/{chunk-7S3O33NM.js.map → chunk-Y72DP3JF.js.map} +0 -0
@@ -18,6 +18,7 @@ import {
18
18
  import { and, eq, sql } from "drizzle-orm";
19
19
  import type { Database } from "./database";
20
20
  import { withRlsContext } from "./database";
21
+ import { runIdempotentPersistenceTransaction } from "./persistence-errors";
21
22
  import * as schema from "./schema";
22
23
 
23
24
  export class CodemodeOperationConflictError extends Error {
@@ -101,106 +102,114 @@ export async function submitCodemodeOperation(
101
102
  if (call.caller.kind !== "codemode") throw new CodemodeOperationNotExecutableError();
102
103
  assertJsonBytes(call.arguments, CODEMODE_ARGUMENTS_MAX_BYTES, "arguments");
103
104
  const requestDigest = digestCodemodeOperationRequest(call);
104
- return await withRlsContext(
105
- db,
106
- { accountId: input.accountId, workspaceId: input.workspaceId },
107
- async (scopedDb) =>
108
- await scopedDb.transaction(async (tx) => {
109
- // Serialize the caller-owned id before testing for an existing row.
110
- // Without this fence, two first submissions can both observe absence
111
- // and the later insert fails its unique constraint instead of replaying.
112
- await tx.execute(sql`
105
+ // The whole admission is one idempotent transaction keyed by the
106
+ // caller-owned operation id (identical bytes replay the same row), so a
107
+ // deadlock/serialization victim rolls back completely and is retried here
108
+ // instead of surfacing as an HTTP failure. No external effect runs inside.
109
+ return await runIdempotentPersistenceTransaction(
110
+ { stage: "codemode.operation.submit", maxAttempts: 3 },
111
+ async () =>
112
+ await withRlsContext(
113
+ db,
114
+ { accountId: input.accountId, workspaceId: input.workspaceId },
115
+ async (scopedDb) =>
116
+ await scopedDb.transaction(async (tx) => {
117
+ // Serialize the caller-owned id before testing for an existing row.
118
+ // Without this fence, two first submissions can both observe absence
119
+ // and the later insert fails its unique constraint instead of replaying.
120
+ await tx.execute(sql`
113
121
  select pg_advisory_xact_lock(
114
122
  hashtextextended(${`codemode-operation:${input.workspaceId}:${call.operationId}`}, 0)
115
123
  )
116
124
  `);
117
- const fence = await lockTurnAttemptWriteFenceTx(tx as unknown as Database, input);
118
- if (
119
- !fence.allowed ||
120
- fence.turn.accountId !== input.accountId ||
121
- fence.turn.status !== "running"
122
- )
123
- throw new CodemodeOperationNotExecutableError();
124
- const [existing] = await tx
125
- .select()
126
- .from(schema.sessionAttemptCodemodeCalls)
127
- .where(eq(schema.sessionAttemptCodemodeCalls.operationId, call.operationId))
128
- .for("update")
129
- .limit(1);
130
- if (existing) {
131
- assertExistingRequest(existing, input, requestDigest);
132
- return { operation: mapCodemodeOperation(existing), created: false };
133
- }
134
-
135
- const [catalogRow] = await tx
136
- .select({ catalog: schema.sessionAttemptToolCatalogs.catalog })
137
- .from(schema.sessionAttemptToolCatalogs)
138
- .where(
139
- and(
140
- eq(schema.sessionAttemptToolCatalogs.attemptId, input.attemptId),
141
- eq(schema.sessionAttemptToolCatalogs.digest, call.catalogDigest),
142
- ),
143
- )
144
- .limit(1);
145
- if (!catalogRow) throw new CodemodeOperationNotExecutableError();
146
- const catalog = parseVerifiedAttemptToolCatalog(catalogRow.catalog);
147
- if (
148
- catalog.accountId !== input.accountId ||
149
- catalog.workspaceId !== input.workspaceId ||
150
- catalog.sessionId !== input.sessionId ||
151
- catalog.turnId !== input.turnId ||
152
- catalog.attemptId !== input.attemptId ||
153
- catalog.executionGeneration !== input.executionGeneration
154
- ) {
155
- throw new CodemodeOperationNotExecutableError();
156
- }
157
- const catalogEntry = catalog.entries.find(
158
- (entry) =>
159
- entry.identity.serverId === call.identity.serverId &&
160
- entry.identity.toolName === call.identity.toolName,
161
- );
162
- if (!catalogEntry) {
163
- throw new CodemodeToolNotInCatalogError();
164
- }
165
- if (catalogEntry.approval === "human" && input.durableApproval !== true) {
166
- throw new CodemodeToolApprovalRequiredError();
167
- }
168
-
169
- // The turn lock also serializes admission; completed receipts never consume this budget.
170
- const active = await tx
171
- .select({ operationId: schema.sessionAttemptCodemodeCalls.operationId })
172
- .from(schema.sessionAttemptCodemodeCalls)
173
- .where(
174
- and(
175
- eq(schema.sessionAttemptCodemodeCalls.turnId, input.turnId),
176
- sql`${schema.sessionAttemptCodemodeCalls.state} in ('queued', 'running', 'waiting_for_approval')`,
177
- ),
178
- )
179
- .limit(128);
180
- if (active.length >= 128) throw new CodemodeOperationLimitError();
181
-
182
- const [created] = await tx
183
- .insert(schema.sessionAttemptCodemodeCalls)
184
- .values({
185
- operationId: call.operationId,
186
- accountId: input.accountId,
187
- workspaceId: input.workspaceId,
188
- sessionId: input.sessionId,
189
- turnId: input.turnId,
190
- attemptId: input.attemptId,
191
- executionGeneration: input.executionGeneration,
192
- catalogDigest: call.catalogDigest,
193
- requestDigest,
194
- durableApproval: input.durableApproval === true,
195
- serverId: call.identity.serverId,
196
- toolName: call.identity.toolName,
197
- arguments: call.arguments,
198
- callerSubjectId: call.caller.subjectId,
199
- })
200
- .returning();
201
- if (!created) throw new Error("Failed to create Codemode operation");
202
- return { operation: mapCodemodeOperation(created), created: true };
203
- }),
125
+ const fence = await lockTurnAttemptWriteFenceTx(tx as unknown as Database, input);
126
+ if (
127
+ !fence.allowed ||
128
+ fence.turn.accountId !== input.accountId ||
129
+ fence.turn.status !== "running"
130
+ )
131
+ throw new CodemodeOperationNotExecutableError();
132
+ const [existing] = await tx
133
+ .select()
134
+ .from(schema.sessionAttemptCodemodeCalls)
135
+ .where(eq(schema.sessionAttemptCodemodeCalls.operationId, call.operationId))
136
+ .for("update")
137
+ .limit(1);
138
+ if (existing) {
139
+ assertExistingRequest(existing, input, requestDigest);
140
+ return { operation: mapCodemodeOperation(existing), created: false };
141
+ }
142
+
143
+ const [catalogRow] = await tx
144
+ .select({ catalog: schema.sessionAttemptToolCatalogs.catalog })
145
+ .from(schema.sessionAttemptToolCatalogs)
146
+ .where(
147
+ and(
148
+ eq(schema.sessionAttemptToolCatalogs.attemptId, input.attemptId),
149
+ eq(schema.sessionAttemptToolCatalogs.digest, call.catalogDigest),
150
+ ),
151
+ )
152
+ .limit(1);
153
+ if (!catalogRow) throw new CodemodeOperationNotExecutableError();
154
+ const catalog = parseVerifiedAttemptToolCatalog(catalogRow.catalog);
155
+ if (
156
+ catalog.accountId !== input.accountId ||
157
+ catalog.workspaceId !== input.workspaceId ||
158
+ catalog.sessionId !== input.sessionId ||
159
+ catalog.turnId !== input.turnId ||
160
+ catalog.attemptId !== input.attemptId ||
161
+ catalog.executionGeneration !== input.executionGeneration
162
+ ) {
163
+ throw new CodemodeOperationNotExecutableError();
164
+ }
165
+ const catalogEntry = catalog.entries.find(
166
+ (entry) =>
167
+ entry.identity.serverId === call.identity.serverId &&
168
+ entry.identity.toolName === call.identity.toolName,
169
+ );
170
+ if (!catalogEntry) {
171
+ throw new CodemodeToolNotInCatalogError();
172
+ }
173
+ if (catalogEntry.approval === "human" && input.durableApproval !== true) {
174
+ throw new CodemodeToolApprovalRequiredError();
175
+ }
176
+
177
+ // The turn lock also serializes admission; completed receipts never consume this budget.
178
+ const active = await tx
179
+ .select({ operationId: schema.sessionAttemptCodemodeCalls.operationId })
180
+ .from(schema.sessionAttemptCodemodeCalls)
181
+ .where(
182
+ and(
183
+ eq(schema.sessionAttemptCodemodeCalls.turnId, input.turnId),
184
+ sql`${schema.sessionAttemptCodemodeCalls.state} in ('queued', 'running', 'waiting_for_approval')`,
185
+ ),
186
+ )
187
+ .limit(128);
188
+ if (active.length >= 128) throw new CodemodeOperationLimitError();
189
+
190
+ const [created] = await tx
191
+ .insert(schema.sessionAttemptCodemodeCalls)
192
+ .values({
193
+ operationId: call.operationId,
194
+ accountId: input.accountId,
195
+ workspaceId: input.workspaceId,
196
+ sessionId: input.sessionId,
197
+ turnId: input.turnId,
198
+ attemptId: input.attemptId,
199
+ executionGeneration: input.executionGeneration,
200
+ catalogDigest: call.catalogDigest,
201
+ requestDigest,
202
+ durableApproval: input.durableApproval === true,
203
+ serverId: call.identity.serverId,
204
+ toolName: call.identity.toolName,
205
+ arguments: call.arguments,
206
+ callerSubjectId: call.caller.subjectId,
207
+ })
208
+ .returning();
209
+ if (!created) throw new Error("Failed to create Codemode operation");
210
+ return { operation: mapCodemodeOperation(created), created: true };
211
+ }),
212
+ ),
204
213
  );
205
214
  }
206
215
 
@@ -255,94 +264,102 @@ export async function claimCodemodeOperation(
255
264
  claimLeaseMs?: number;
256
265
  },
257
266
  ): Promise<ClaimCodemodeOperationResult> {
258
- return await withRlsContext(
259
- db,
260
- { accountId: input.accountId, workspaceId: input.workspaceId },
261
- async (scopedDb) =>
262
- await scopedDb.transaction(async (tx) => {
263
- const fence = await lockTurnAttemptWriteFenceTx(tx as unknown as Database, input);
264
- if (
265
- !fence.allowed ||
266
- fence.turn.accountId !== input.accountId ||
267
- fence.turn.status !== "running"
268
- )
269
- return { status: "rejected", operation: null };
270
- let [row] = await tx
271
- .select()
272
- .from(schema.sessionAttemptCodemodeCalls)
273
- .where(eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId))
274
- .for("update")
275
- .limit(1);
276
- if (!row || !operationMatchesAuthority(row, input)) {
277
- return { status: "rejected", operation: row ? mapCodemodeOperation(row) : null };
278
- }
279
- const now = input.now ?? new Date();
280
- const claimExpiresAt = new Date(now.getTime() + boundedClaimLeaseMs(input.claimLeaseMs));
281
- let reclaimed = false;
282
- if (row.state === "running") {
283
- if (!row.claimExpiresAt || row.claimExpiresAt.getTime() > now.getTime()) {
284
- return { status: "already_running", operation: mapCodemodeOperation(row) };
285
- }
286
- if (row.executionStartedAt) {
287
- if (!row.claimId) {
288
- throw new Error("Codemode running operation is missing its claim id");
267
+ // Pure database transaction keyed by the caller's claim id; a rolled-back
268
+ // deadlock victim committed nothing and is safe to repeat.
269
+ return await runIdempotentPersistenceTransaction(
270
+ { stage: "codemode.operation.claim", maxAttempts: 3 },
271
+ async () =>
272
+ await withRlsContext(
273
+ db,
274
+ { accountId: input.accountId, workspaceId: input.workspaceId },
275
+ async (scopedDb) =>
276
+ await scopedDb.transaction(async (tx) => {
277
+ const fence = await lockTurnAttemptWriteFenceTx(tx as unknown as Database, input);
278
+ if (
279
+ !fence.allowed ||
280
+ fence.turn.accountId !== input.accountId ||
281
+ fence.turn.status !== "running"
282
+ )
283
+ return { status: "rejected", operation: null };
284
+ let [row] = await tx
285
+ .select()
286
+ .from(schema.sessionAttemptCodemodeCalls)
287
+ .where(eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId))
288
+ .for("update")
289
+ .limit(1);
290
+ if (!row || !operationMatchesAuthority(row, input)) {
291
+ return { status: "rejected", operation: row ? mapCodemodeOperation(row) : null };
289
292
  }
293
+ const now = input.now ?? new Date();
294
+ const claimExpiresAt = new Date(
295
+ now.getTime() + boundedClaimLeaseMs(input.claimLeaseMs),
296
+ );
297
+ let reclaimed = false;
298
+ if (row.state === "running") {
299
+ if (!row.claimExpiresAt || row.claimExpiresAt.getTime() > now.getTime()) {
300
+ return { status: "already_running", operation: mapCodemodeOperation(row) };
301
+ }
302
+ if (row.executionStartedAt) {
303
+ if (!row.claimId) {
304
+ throw new Error("Codemode running operation is missing its claim id");
305
+ }
306
+ return {
307
+ status: "execution_owner_lost",
308
+ operation: mapCodemodeOperation(row),
309
+ claimId: row.claimId,
310
+ };
311
+ }
312
+ const [requeued] = await tx
313
+ .update(schema.sessionAttemptCodemodeCalls)
314
+ .set({
315
+ state: "queued",
316
+ claimId: null,
317
+ claimedAt: null,
318
+ claimExpiresAt: null,
319
+ updatedAt: now,
320
+ })
321
+ .where(
322
+ and(
323
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
324
+ eq(schema.sessionAttemptCodemodeCalls.state, "running"),
325
+ eq(schema.sessionAttemptCodemodeCalls.claimId, row.claimId!),
326
+ ),
327
+ )
328
+ .returning();
329
+ if (!requeued) throw new Error("Codemode expired claim recovery lost its row lock");
330
+ row = requeued;
331
+ reclaimed = true;
332
+ }
333
+ if (row.state === "waiting_for_approval")
334
+ return { status: "terminal", operation: mapCodemodeOperation(row) };
335
+ if (isTerminalState(row.state))
336
+ return { status: "terminal", operation: mapCodemodeOperation(row) };
337
+
338
+ const [claimed] = await tx
339
+ .update(schema.sessionAttemptCodemodeCalls)
340
+ .set({
341
+ state: "running",
342
+ claimId: input.claimId,
343
+ claimedAt: now,
344
+ claimExpiresAt,
345
+ updatedAt: now,
346
+ })
347
+ .where(
348
+ and(
349
+ eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
350
+ eq(schema.sessionAttemptCodemodeCalls.state, "queued"),
351
+ ),
352
+ )
353
+ .returning();
354
+ if (!claimed) throw new Error("Codemode operation claim lost its row lock");
290
355
  return {
291
- status: "execution_owner_lost",
292
- operation: mapCodemodeOperation(row),
293
- claimId: row.claimId,
356
+ status: "claimed",
357
+ operation: mapCodemodeOperation(claimed),
358
+ claimId: input.claimId,
359
+ reclaimed,
294
360
  };
295
- }
296
- const [requeued] = await tx
297
- .update(schema.sessionAttemptCodemodeCalls)
298
- .set({
299
- state: "queued",
300
- claimId: null,
301
- claimedAt: null,
302
- claimExpiresAt: null,
303
- updatedAt: now,
304
- })
305
- .where(
306
- and(
307
- eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
308
- eq(schema.sessionAttemptCodemodeCalls.state, "running"),
309
- eq(schema.sessionAttemptCodemodeCalls.claimId, row.claimId!),
310
- ),
311
- )
312
- .returning();
313
- if (!requeued) throw new Error("Codemode expired claim recovery lost its row lock");
314
- row = requeued;
315
- reclaimed = true;
316
- }
317
- if (row.state === "waiting_for_approval")
318
- return { status: "terminal", operation: mapCodemodeOperation(row) };
319
- if (isTerminalState(row.state))
320
- return { status: "terminal", operation: mapCodemodeOperation(row) };
321
-
322
- const [claimed] = await tx
323
- .update(schema.sessionAttemptCodemodeCalls)
324
- .set({
325
- state: "running",
326
- claimId: input.claimId,
327
- claimedAt: now,
328
- claimExpiresAt,
329
- updatedAt: now,
330
- })
331
- .where(
332
- and(
333
- eq(schema.sessionAttemptCodemodeCalls.operationId, input.operationId),
334
- eq(schema.sessionAttemptCodemodeCalls.state, "queued"),
335
- ),
336
- )
337
- .returning();
338
- if (!claimed) throw new Error("Codemode operation claim lost its row lock");
339
- return {
340
- status: "claimed",
341
- operation: mapCodemodeOperation(claimed),
342
- claimId: input.claimId,
343
- reclaimed,
344
- };
345
- }),
361
+ }),
362
+ ),
346
363
  );
347
364
  }
348
365
 
@@ -0,0 +1,166 @@
1
+ import {
2
+ type ConnectionKind,
3
+ type ConnectionMetadata,
4
+ type ConnectionStatus,
5
+ OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
6
+ OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY,
7
+ VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY,
8
+ VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY,
9
+ } from "@opengeni/contracts";
10
+ import { and, desc, eq, isNull, or, type SQL } from "drizzle-orm";
11
+ import { withWorkspaceRls, withWorkspaceSubjectRls, type Database } from "./database";
12
+ import * as schema from "./schema";
13
+
14
+ /** Server-owned verification facts; public schemas expose them read-only and nullable. */
15
+ export type ConnectionMetadataWithVerification = ConnectionMetadata & {
16
+ verifiedInstallAt: string | null;
17
+ verifiedInstallVersion: number | null;
18
+ };
19
+
20
+ export const connectionMetadataColumns = {
21
+ id: schema.connections.id,
22
+ authorityId: schema.connections.authorityId,
23
+ authorityGeneration: schema.connections.authorityGeneration,
24
+ accountId: schema.connections.accountId,
25
+ workspaceId: schema.connections.workspaceId,
26
+ subjectId: schema.connections.subjectId,
27
+ providerDomain: schema.connections.providerDomain,
28
+ kind: schema.connections.kind,
29
+ status: schema.connections.status,
30
+ grantedScopes: schema.connections.grantedScopes,
31
+ expiresAt: schema.connections.expiresAt,
32
+ lastRefreshAt: schema.connections.lastRefreshAt,
33
+ lastUsedAt: schema.connections.lastUsedAt,
34
+ lastError: schema.connections.lastError,
35
+ version: schema.connections.version,
36
+ verifiedInstallAt: schema.connections.verifiedInstallAt,
37
+ verifiedInstallVersion: schema.connections.verifiedInstallVersion,
38
+ metadata: schema.connections.metadata,
39
+ createdBySubjectId: schema.connections.createdBySubjectId,
40
+ updatedBySubjectId: schema.connections.updatedBySubjectId,
41
+ createdAt: schema.connections.createdAt,
42
+ updatedAt: schema.connections.updatedAt,
43
+ };
44
+
45
+ export function connectionSubjectVisibility(subjectId?: string | null): SQL {
46
+ return subjectId
47
+ ? or(isNull(schema.connections.subjectId), eq(schema.connections.subjectId, subjectId))!
48
+ : isNull(schema.connections.subjectId);
49
+ }
50
+
51
+ export async function withConnectionSubjectRls<T>(
52
+ db: Database,
53
+ workspaceId: string,
54
+ subjectId: string | null | undefined,
55
+ fn: (db: Database) => Promise<T>,
56
+ ): Promise<T> {
57
+ return subjectId
58
+ ? await withWorkspaceSubjectRls(db, workspaceId, subjectId, fn)
59
+ : await withWorkspaceRls(db, workspaceId, fn);
60
+ }
61
+
62
+ export async function listConnectionsMetadata(
63
+ db: Database,
64
+ workspaceId: string,
65
+ subjectId?: string | null,
66
+ ): Promise<ConnectionMetadataWithVerification[]> {
67
+ return await withConnectionSubjectRls(db, workspaceId, subjectId, async (scopedDb) => {
68
+ const rows = await scopedDb
69
+ .select(connectionMetadataColumns)
70
+ .from(schema.connections)
71
+ .where(
72
+ and(
73
+ eq(schema.connections.workspaceId, workspaceId),
74
+ connectionSubjectVisibility(subjectId),
75
+ ),
76
+ )
77
+ // Legacy rows can share created_at. UUID DESC is the immutable stable
78
+ // tie-breaker, so every caller that intentionally selects the first row
79
+ // collapses duplicates in the same documented direction.
80
+ .orderBy(desc(schema.connections.createdAt), desc(schema.connections.id));
81
+ return rows.map(mapConnectionMetadata);
82
+ });
83
+ }
84
+
85
+ export async function getConnectionMetadata(
86
+ db: Database,
87
+ workspaceId: string,
88
+ connectionId: string,
89
+ subjectId?: string | null,
90
+ ): Promise<ConnectionMetadataWithVerification | null> {
91
+ return await withConnectionSubjectRls(db, workspaceId, subjectId, async (scopedDb) => {
92
+ const [row] = await scopedDb
93
+ .select(connectionMetadataColumns)
94
+ .from(schema.connections)
95
+ .where(
96
+ and(
97
+ eq(schema.connections.workspaceId, workspaceId),
98
+ eq(schema.connections.id, connectionId),
99
+ connectionSubjectVisibility(subjectId),
100
+ ),
101
+ )
102
+ .limit(1);
103
+ return row ? mapConnectionMetadata(row) : null;
104
+ });
105
+ }
106
+
107
+ export function mapConnectionMetadata(row: {
108
+ id: string;
109
+ authorityId?: string | null;
110
+ authorityGeneration: number;
111
+ accountId: string;
112
+ workspaceId: string;
113
+ subjectId: string | null;
114
+ providerDomain: string;
115
+ kind: string;
116
+ status: string;
117
+ grantedScopes: string[];
118
+ expiresAt: Date | null;
119
+ lastRefreshAt: Date | null;
120
+ lastUsedAt: Date | null;
121
+ lastError: string | null;
122
+ version: number;
123
+ verifiedInstallAt: Date | null;
124
+ verifiedInstallVersion: number | null;
125
+ metadata: Record<string, unknown>;
126
+ createdBySubjectId: string | null;
127
+ updatedBySubjectId: string | null;
128
+ createdAt: Date;
129
+ updatedAt: Date;
130
+ }): ConnectionMetadataWithVerification {
131
+ const {
132
+ [OPENROUTER_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _openRouterOperationId,
133
+ [OPENROUTER_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _openRouterOperationDigest,
134
+ [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_ID_METADATA_KEY]: _operationId,
135
+ [VERCEL_AI_GATEWAY_CREDENTIAL_OPERATION_DIGEST_METADATA_KEY]: _operationDigest,
136
+ anthropicCredentialOperationId: _anthropicOperationId,
137
+ anthropicCredentialOperationDigest: _anthropicOperationDigest,
138
+ claude_subscriptionCredentialOperationId: _claudeOperationId,
139
+ claude_subscriptionCredentialOperationDigest: _claudeOperationDigest,
140
+ ...publicMetadata
141
+ } = row.metadata;
142
+ return {
143
+ id: row.id,
144
+ ...(row.subjectId !== null && row.authorityId ? { authorityId: row.authorityId } : {}),
145
+ connectionAuthorityGeneration: row.authorityGeneration,
146
+ accountId: row.accountId,
147
+ workspaceId: row.workspaceId,
148
+ subjectId: row.subjectId,
149
+ providerDomain: row.providerDomain,
150
+ kind: row.kind as ConnectionKind,
151
+ status: row.status as ConnectionStatus,
152
+ grantedScopes: row.grantedScopes,
153
+ expiresAt: row.expiresAt?.toISOString() ?? null,
154
+ lastRefreshAt: row.lastRefreshAt?.toISOString() ?? null,
155
+ lastUsedAt: row.lastUsedAt?.toISOString() ?? null,
156
+ lastError: row.lastError,
157
+ version: row.version,
158
+ verifiedInstallAt: row.verifiedInstallAt?.toISOString() ?? null,
159
+ verifiedInstallVersion: row.verifiedInstallVersion,
160
+ metadata: publicMetadata,
161
+ createdBySubjectId: row.createdBySubjectId,
162
+ updatedBySubjectId: row.updatedBySubjectId,
163
+ createdAt: row.createdAt.toISOString(),
164
+ updatedAt: row.updatedAt.toISOString(),
165
+ };
166
+ }