@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.
- package/dist/canonical-human-identities.js +3 -3
- package/dist/capability-reconciliation.js +2 -2
- package/dist/{chunk-7QWWTVYH.js → chunk-5ROHLEN6.js} +29 -4
- package/dist/chunk-5ROHLEN6.js.map +1 -0
- package/dist/{chunk-AQMH2ZEH.js → chunk-A4J7EECR.js} +2 -2
- package/dist/{chunk-VQX54TK3.js → chunk-B5C5SBSI.js} +3 -3
- package/dist/{chunk-OI6YZXTU.js → chunk-DKFEUGM4.js} +2 -2
- package/dist/{chunk-HYBQABBJ.js → chunk-HUKBIZGH.js} +2 -2
- package/dist/{chunk-LRSIODCQ.js → chunk-IT3EYY2C.js} +6 -4
- package/dist/{chunk-LRSIODCQ.js.map → chunk-IT3EYY2C.js.map} +1 -1
- package/dist/{chunk-DSNEIW5E.js → chunk-JL6TBB76.js} +2 -1
- package/dist/chunk-JL6TBB76.js.map +1 -0
- package/dist/{chunk-OEFNTGR3.js → chunk-JWBEE4CL.js} +4 -4
- package/dist/{chunk-BRVBLQAL.js → chunk-K5MTNKJU.js} +4 -4
- package/dist/{chunk-6UKNJ43T.js → chunk-NZ2SP55O.js} +2 -2
- package/dist/{chunk-3XHC5KC5.js → chunk-OKCBARX6.js} +2 -2
- package/dist/{chunk-PNWWNEDM.js → chunk-OV5AUIBA.js} +2 -2
- package/dist/{chunk-BID3CNN3.js → chunk-TODVDGMO.js} +36 -18
- package/dist/{chunk-BID3CNN3.js.map → chunk-TODVDGMO.js.map} +1 -1
- package/dist/{chunk-6S5T6FSP.js → chunk-W75DRYG3.js} +5 -5
- package/dist/{chunk-7S3O33NM.js → chunk-Y72DP3JF.js} +2 -2
- package/dist/codemode-approvals.d.ts +7 -1
- package/dist/connect-attempts.js +3 -3
- package/dist/connection-metadata.d.ts +416 -0
- package/dist/credit-balances.d.ts +25 -4
- package/dist/database.d.ts +7 -1
- package/dist/editable-artifact-durable-export.js +2 -2
- package/dist/editable-artifacts.js +3 -3
- package/dist/index.d.ts +51 -8
- package/dist/index.js +10826 -10129
- package/dist/index.js.map +1 -1
- package/dist/legacy-tool-action-reviews.d.ts +75 -0
- package/dist/managed-auth-session-sets.js +3 -3
- package/dist/mcp-operations.js +2 -2
- package/dist/modal-native-live-origin.d.ts +75 -0
- package/dist/organization-integration-policy.js +3 -3
- package/dist/organization-slack-bots.d.ts +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/retained-provider-commands.d.ts +21 -21
- package/dist/retained-provider-commands.js +6 -6
- package/dist/runtime-posture.d.ts +1 -1
- package/dist/sandbox-interaction-idle.d.ts +22 -0
- package/dist/schema.js +1 -1
- package/dist/session-background-commands.js +4 -4
- package/dist/session-command-output.js +5 -5
- package/dist/session-event-slices.js +3 -3
- package/dist/session-mcp-credential-rotation.js +5 -5
- package/dist/session-realtime-billing.d.ts +48 -0
- package/dist/session-realtime.d.ts +8 -2
- package/dist/session-tenancy.js +3 -3
- package/dist/video-generation.js +3 -3
- package/dist/voice-transcription-settlement.d.ts +36 -0
- package/dist/workspace-tool-defaults.js +4 -4
- package/drizzle/0623_voice_transcription_attribution.sql +128 -0
- package/drizzle/0624_azure_live_realtime_model.sql +30 -0
- package/drizzle/0625_transcription_billing_refusal_codes.sql +29 -0
- package/drizzle/0632_modal_native_live_origin.sql +239 -0
- package/package.json +7 -7
- package/src/codemode-approvals.ts +52 -22
- package/src/codemode-operations.ts +198 -181
- package/src/connection-metadata.ts +166 -0
- package/src/credit-balances.ts +58 -17
- package/src/database.ts +9 -0
- package/src/index.ts +540 -412
- package/src/insights-unified.ts +2 -1
- package/src/legacy-tool-action-reviews.ts +242 -0
- package/src/modal-native-live-origin.ts +148 -0
- package/src/organization-slack-bots.ts +1 -1
- package/src/provision-roles.ts +12 -0
- package/src/runtime-posture.ts +32 -3
- package/src/sandbox-interaction-idle.ts +91 -0
- package/src/schema.ts +1 -0
- package/src/session-realtime-billing.ts +135 -0
- package/src/session-realtime-ledger.ts +9 -4
- package/src/session-realtime.ts +20 -2
- package/src/tool-action-reviews.ts +129 -18
- package/src/transcription-recordings.ts +3 -1
- package/src/voice-transcription-settlement.ts +94 -0
- package/dist/chunk-7QWWTVYH.js.map +0 -1
- package/dist/chunk-DSNEIW5E.js.map +0 -1
- /package/dist/{chunk-AQMH2ZEH.js.map → chunk-A4J7EECR.js.map} +0 -0
- /package/dist/{chunk-VQX54TK3.js.map → chunk-B5C5SBSI.js.map} +0 -0
- /package/dist/{chunk-OI6YZXTU.js.map → chunk-DKFEUGM4.js.map} +0 -0
- /package/dist/{chunk-HYBQABBJ.js.map → chunk-HUKBIZGH.js.map} +0 -0
- /package/dist/{chunk-OEFNTGR3.js.map → chunk-JWBEE4CL.js.map} +0 -0
- /package/dist/{chunk-BRVBLQAL.js.map → chunk-K5MTNKJU.js.map} +0 -0
- /package/dist/{chunk-6UKNJ43T.js.map → chunk-NZ2SP55O.js.map} +0 -0
- /package/dist/{chunk-3XHC5KC5.js.map → chunk-OKCBARX6.js.map} +0 -0
- /package/dist/{chunk-PNWWNEDM.js.map → chunk-OV5AUIBA.js.map} +0 -0
- /package/dist/{chunk-6S5T6FSP.js.map → chunk-W75DRYG3.js.map} +0 -0
- /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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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: "
|
|
292
|
-
operation: mapCodemodeOperation(
|
|
293
|
-
claimId:
|
|
356
|
+
status: "claimed",
|
|
357
|
+
operation: mapCodemodeOperation(claimed),
|
|
358
|
+
claimId: input.claimId,
|
|
359
|
+
reclaimed,
|
|
294
360
|
};
|
|
295
|
-
}
|
|
296
|
-
|
|
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
|
+
}
|