@opengeni/db 0.21.0 → 0.22.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/index.js CHANGED
@@ -12,6 +12,8 @@ import {
12
12
  codexSubscriptionCredentials,
13
13
  composerDrafts,
14
14
  connections,
15
+ connectorActionPolicies,
16
+ connectorActionRequests,
15
17
  creditLedgerEntries,
16
18
  deviceEnrollmentRequests,
17
19
  enrollments,
@@ -105,7 +107,7 @@ import {
105
107
  workspaceVariableSetVariables,
106
108
  workspaceVariableSets,
107
109
  workspaces
108
- } from "./chunk-NK2A36KP.js";
110
+ } from "./chunk-CYGFLLMN.js";
109
111
  import {
110
112
  migrate,
111
113
  runMigrations
@@ -125,7 +127,7 @@ import {
125
127
  inspectRuntimeDatabasePosture,
126
128
  provisionRoles,
127
129
  runtimeDatabaseReadyCheck
128
- } from "./chunk-OTJHD33E.js";
130
+ } from "./chunk-BNGEN5QZ.js";
129
131
  import "./chunk-PZ5AY32C.js";
130
132
 
131
133
  // src/index.ts
@@ -172,7 +174,11 @@ import {
172
174
  SubmitHumanInputResponseRequest,
173
175
  TurnExecutionPolicyV1
174
176
  } from "@opengeni/contracts";
175
- import { environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3 } from "@opengeni/config";
177
+ import {
178
+ environmentsEncryptionKeyBytes as environmentsEncryptionKeyBytes3,
179
+ VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
180
+ VERCEL_AI_GATEWAY_CONNECTION_ROLE
181
+ } from "@opengeni/config";
176
182
  import { boundModelToolOutputItem as boundModelToolOutputItem2, isCodexBilledModel } from "@opengeni/codex";
177
183
  import { isCodexBilledModel as isCodexBilledModel2 } from "@opengeni/codex";
178
184
  import {
@@ -1397,7 +1403,7 @@ async function registerSessionTurnAttemptClaim(db, input) {
1397
1403
  eq4(sessionTurnAttempts.id, input.id)
1398
1404
  )
1399
1405
  ).for("update").limit(1);
1400
- if (!existing || existing.accountId !== input.accountId || existing.sessionId !== input.sessionId || existing.turnId !== input.turnId || existing.executionGeneration !== input.executionGeneration || existing.temporalWorkflowId !== input.temporalWorkflowId || existing.temporalWorkflowRunId !== input.temporalWorkflowRunId || existing.temporalActivityId !== input.temporalActivityId || existing.state === "closed") {
1406
+ if (!existing || existing.accountId !== input.accountId || existing.sessionId !== input.sessionId || existing.turnId !== input.turnId || existing.executionGeneration !== input.executionGeneration || existing.temporalWorkflowId !== input.temporalWorkflowId || existing.temporalWorkflowRunId !== input.temporalWorkflowRunId || existing.temporalActivityId !== input.temporalActivityId || JSON.stringify(existing.connectorActionPolicies) !== JSON.stringify(input.connectorActionPolicies) || existing.state === "closed") {
1401
1407
  throw new SessionControlInvariantError(
1402
1408
  `Attempt ${input.id} conflicts with a different or closed ownership chain`
1403
1409
  );
@@ -14014,6 +14020,32 @@ async function loadConnectionCredentialForBroker(db, settings, input) {
14014
14020
  }
14015
14021
  );
14016
14022
  }
14023
+ async function workspaceVercelAiGatewayConnectionActive(db, workspaceId) {
14024
+ const connections2 = await listConnectionsMetadata(db, workspaceId, null);
14025
+ return connections2.some(
14026
+ (connection) => connection.subjectId === null && connection.providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN && connection.kind === "api_key" && connection.status === "active" && connection.metadata.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE
14027
+ );
14028
+ }
14029
+ async function loadWorkspaceVercelAiGatewayApiKey(db, settings, workspaceId) {
14030
+ const metadata = (await listConnectionsMetadata(db, workspaceId, null)).find(
14031
+ (connection2) => connection2.subjectId === null && connection2.providerDomain === VERCEL_AI_GATEWAY_CONNECTION_DOMAIN && connection2.kind === "api_key" && connection2.status === "active" && connection2.metadata.credentialRole === VERCEL_AI_GATEWAY_CONNECTION_ROLE
14032
+ );
14033
+ if (!metadata) {
14034
+ return null;
14035
+ }
14036
+ const connection = await loadConnectionCredentialForBroker(db, settings, {
14037
+ workspaceId,
14038
+ connectionId: metadata.id,
14039
+ providerDomain: VERCEL_AI_GATEWAY_CONNECTION_DOMAIN,
14040
+ kind: "api_key",
14041
+ allowSubjectOwned: false
14042
+ });
14043
+ if (!connection || connection.status !== "active" || connection.metadata.credentialRole !== VERCEL_AI_GATEWAY_CONNECTION_ROLE) {
14044
+ return null;
14045
+ }
14046
+ const apiKey = connection.credential.apiKey;
14047
+ return typeof apiKey === "string" && apiKey.trim().length > 0 ? apiKey : null;
14048
+ }
14017
14049
  async function recordConnectionTokenRefresh(db, input) {
14018
14050
  return await withConnectionSubjectRls(
14019
14051
  db,
@@ -19117,6 +19149,510 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, attemptId
19117
19149
  });
19118
19150
  });
19119
19151
  }
19152
+ var CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX = 2048;
19153
+ var CONNECTOR_ACTION_APPROVAL_ID_MAX = 1024;
19154
+ var CONNECTOR_ACTION_CONNECTION_ID_MAX = 512;
19155
+ var CONNECTOR_ACTION_SERVER_ID_MAX = 256;
19156
+ var CONNECTOR_ACTION_NAME_MAX = 512;
19157
+ function boundedConnectorActionText(value, label, max) {
19158
+ const trimmed = value.trim();
19159
+ if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > max) {
19160
+ throw new Error(`${label} must be between 1 and ${max} UTF-8 bytes`);
19161
+ }
19162
+ return trimmed;
19163
+ }
19164
+ function connectorActionPolicySelector(toolName, args) {
19165
+ if (args && typeof args === "object" && !Array.isArray(args)) {
19166
+ const action = args.action;
19167
+ if (typeof action === "string" && action.trim().length > 0) {
19168
+ return boundedConnectorActionText(action, "connector action name", CONNECTOR_ACTION_NAME_MAX);
19169
+ }
19170
+ }
19171
+ return toolName;
19172
+ }
19173
+ function connectorActionEvidenceName(resolved) {
19174
+ return resolved.entry?.actionName ?? "*";
19175
+ }
19176
+ function connectorActionFingerprint(input) {
19177
+ return createHash6("sha256").update(
19178
+ stableJson3({
19179
+ workspaceId: input.workspaceId,
19180
+ connectionId: input.connectionId,
19181
+ serverId: input.serverId,
19182
+ toolName: input.toolName,
19183
+ actionName: input.actionName,
19184
+ arguments: input.arguments ?? null
19185
+ }),
19186
+ "utf8"
19187
+ ).digest("hex");
19188
+ }
19189
+ function resolveConnectorActionPolicy(snapshot, input) {
19190
+ const candidates = snapshot.filter(
19191
+ (entry) => entry.connectionId === input.connectionId && (entry.serverId === input.serverId || entry.serverId === "*") && (entry.toolName === input.toolName || entry.toolName === "*") && (entry.actionName === input.actionName || entry.actionName === "*")
19192
+ ).map((entry) => ({
19193
+ entry,
19194
+ specificity: Number(entry.serverId !== "*") + Number(entry.toolName !== "*") + Number(entry.actionName !== "*")
19195
+ })).sort(
19196
+ (left, right) => right.specificity - left.specificity || left.entry.id.localeCompare(right.entry.id)
19197
+ );
19198
+ const selected = candidates[0];
19199
+ if (!selected) return { managed: false };
19200
+ if (candidates[1]?.specificity === selected.specificity) {
19201
+ return { managed: true, source: "ambiguous", entry: null, decision: "block" };
19202
+ }
19203
+ return { managed: true, source: "explicit", entry: selected.entry };
19204
+ }
19205
+ function connectorActionAuditMetadata(row, extra = {}) {
19206
+ return {
19207
+ requestId: row.id,
19208
+ sessionId: row.sessionId,
19209
+ turnId: row.turnId,
19210
+ attemptId: row.creationAttemptId,
19211
+ creationExecutionGeneration: row.creationExecutionGeneration,
19212
+ executionAttemptId: row.executionAttemptId,
19213
+ executionAttemptGeneration: row.executionAttemptGeneration,
19214
+ approvalId: row.approvalId,
19215
+ initiatorKind: row.initiatorKind,
19216
+ initiatorSubjectId: row.initiatorSubjectId,
19217
+ connectionId: row.connectionId,
19218
+ connectionVersion: row.connectionVersion,
19219
+ serverId: row.serverId,
19220
+ toolName: row.toolName,
19221
+ actionName: row.actionName,
19222
+ policyId: row.policyId,
19223
+ policyVersion: row.policyVersion,
19224
+ policySource: row.policySource,
19225
+ policyDecision: row.policyDecision,
19226
+ actionFingerprint: row.actionFingerprint,
19227
+ ...extra
19228
+ };
19229
+ }
19230
+ async function insertConnectorActionAudit(db, input) {
19231
+ await db.insert(auditEvents).values({
19232
+ accountId: input.row.accountId,
19233
+ workspaceId: input.row.workspaceId,
19234
+ subjectId: input.subjectId,
19235
+ action: input.action,
19236
+ targetType: "connector_action_request",
19237
+ targetId: input.row.id,
19238
+ metadata: connectorActionAuditMetadata(input.row, input.extra)
19239
+ });
19240
+ }
19241
+ function normalizedConnectorActionInvocation(identity, invocation) {
19242
+ const approvalId = boundedConnectorActionText(
19243
+ invocation.approvalId,
19244
+ "connector approval id",
19245
+ CONNECTOR_ACTION_APPROVAL_ID_MAX
19246
+ );
19247
+ const serverId = boundedConnectorActionText(
19248
+ invocation.serverId,
19249
+ "connector server id",
19250
+ CONNECTOR_ACTION_SERVER_ID_MAX
19251
+ );
19252
+ const toolName = boundedConnectorActionText(
19253
+ invocation.toolName,
19254
+ "connector tool name",
19255
+ CONNECTOR_ACTION_NAME_MAX
19256
+ );
19257
+ const policyActionSelector = connectorActionPolicySelector(toolName, invocation.arguments);
19258
+ const connectionId = invocation.connectionId?.trim() ? boundedConnectorActionText(
19259
+ invocation.connectionId,
19260
+ "connector connection id",
19261
+ CONNECTOR_ACTION_CONNECTION_ID_MAX
19262
+ ) : null;
19263
+ return {
19264
+ approvalId,
19265
+ connectionId,
19266
+ serverId,
19267
+ toolName,
19268
+ policyActionSelector,
19269
+ arguments: invocation.arguments
19270
+ };
19271
+ }
19272
+ function durableConnectorActionInvocation(identity, invocation, resolved) {
19273
+ const actionName = connectorActionEvidenceName(resolved);
19274
+ return {
19275
+ approvalId: invocation.approvalId,
19276
+ connectionId: invocation.connectionId,
19277
+ serverId: invocation.serverId,
19278
+ toolName: invocation.toolName,
19279
+ actionName,
19280
+ actionFingerprint: connectorActionFingerprint({
19281
+ workspaceId: identity.workspaceId,
19282
+ connectionId: invocation.connectionId,
19283
+ serverId: invocation.serverId,
19284
+ toolName: invocation.toolName,
19285
+ actionName,
19286
+ arguments: invocation.arguments
19287
+ })
19288
+ };
19289
+ }
19290
+ async function connectorActionAttemptSnapshot(db, identity) {
19291
+ const [attempt] = await db.select({
19292
+ accountId: sessionTurnAttempts.accountId,
19293
+ sessionId: sessionTurnAttempts.sessionId,
19294
+ turnId: sessionTurnAttempts.turnId,
19295
+ executionGeneration: sessionTurnAttempts.executionGeneration,
19296
+ state: sessionTurnAttempts.state,
19297
+ connectorActionPolicies: sessionTurnAttempts.connectorActionPolicies
19298
+ }).from(sessionTurnAttempts).where(
19299
+ and11(
19300
+ eq11(sessionTurnAttempts.workspaceId, identity.workspaceId),
19301
+ eq11(sessionTurnAttempts.id, identity.attemptId)
19302
+ )
19303
+ ).limit(1);
19304
+ if (!attempt || attempt.accountId !== identity.accountId || attempt.sessionId !== identity.sessionId || attempt.turnId !== identity.turnId || attempt.executionGeneration !== identity.executionGeneration || !["claimed", "running"].includes(attempt.state)) {
19305
+ throw new Error(`connector action attempt ownership is unavailable: ${identity.attemptId}`);
19306
+ }
19307
+ if (attempt.connectorActionPolicies.length > CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX) {
19308
+ throw new Error("connector action policy snapshot exceeds the runtime bound");
19309
+ }
19310
+ return attempt.connectorActionPolicies;
19311
+ }
19312
+ function connectorActionRequestMatches(row, input) {
19313
+ const entry = input.resolved.entry;
19314
+ return row.accountId === input.identity.accountId && row.workspaceId === input.identity.workspaceId && row.sessionId === input.identity.sessionId && row.turnId === input.identity.turnId && row.creationAttemptId === input.identity.attemptId && row.creationExecutionGeneration === input.identity.executionGeneration && row.approvalId === input.invocation.approvalId && row.initiatorKind === input.identity.initiator.kind && row.initiatorSubjectId === input.identity.initiator.subjectId && row.connectionId === input.invocation.connectionId && row.serverId === input.invocation.serverId && row.toolName === input.invocation.toolName && row.actionName === input.invocation.actionName && row.policyId === (entry?.id ?? null) && row.policyVersion === (entry?.version ?? null) && row.policySource === input.resolved.source && row.policyDecision === (entry?.policy ?? "block") && row.actionFingerprint === input.invocation.actionFingerprint;
19315
+ }
19316
+ function connectorActionRequestMatchesLogicalCall(row, identity, invocation) {
19317
+ if (!invocation.connectionId) return false;
19318
+ return row.accountId === identity.accountId && row.workspaceId === identity.workspaceId && row.sessionId === identity.sessionId && row.turnId === identity.turnId && row.approvalId === invocation.approvalId && row.initiatorKind === identity.initiator.kind && row.initiatorSubjectId === identity.initiator.subjectId && row.connectionId === invocation.connectionId && row.serverId === invocation.serverId && row.toolName === invocation.toolName && row.actionFingerprint === connectorActionFingerprint({
19319
+ workspaceId: identity.workspaceId,
19320
+ connectionId: invocation.connectionId,
19321
+ serverId: invocation.serverId,
19322
+ toolName: invocation.toolName,
19323
+ actionName: row.actionName,
19324
+ arguments: invocation.arguments
19325
+ });
19326
+ }
19327
+ async function insertConnectorActionRequest(db, input) {
19328
+ const entry = input.resolved.entry;
19329
+ const [inserted] = await db.insert(connectorActionRequests).values({
19330
+ accountId: input.identity.accountId,
19331
+ workspaceId: input.identity.workspaceId,
19332
+ sessionId: input.identity.sessionId,
19333
+ turnId: input.identity.turnId,
19334
+ creationAttemptId: input.identity.attemptId,
19335
+ creationExecutionGeneration: input.identity.executionGeneration,
19336
+ approvalId: input.invocation.approvalId,
19337
+ initiatorKind: input.identity.initiator.kind,
19338
+ initiatorSubjectId: input.identity.initiator.subjectId,
19339
+ connectionId: input.invocation.connectionId,
19340
+ serverId: input.invocation.serverId,
19341
+ toolName: input.invocation.toolName,
19342
+ actionName: input.invocation.actionName,
19343
+ policyId: entry?.id ?? null,
19344
+ policyVersion: entry?.version ?? null,
19345
+ policySource: input.resolved.source,
19346
+ policyDecision: entry?.policy ?? "block",
19347
+ actionFingerprint: input.invocation.actionFingerprint,
19348
+ status: input.status,
19349
+ ...input.status === "executing" ? {
19350
+ executionAttemptId: input.identity.attemptId,
19351
+ executionAttemptGeneration: input.identity.executionGeneration,
19352
+ executionStartedAt: /* @__PURE__ */ new Date()
19353
+ } : {}
19354
+ }).onConflictDoNothing({
19355
+ target: [
19356
+ connectorActionRequests.workspaceId,
19357
+ connectorActionRequests.sessionId,
19358
+ connectorActionRequests.turnId,
19359
+ connectorActionRequests.approvalId
19360
+ ]
19361
+ }).returning();
19362
+ if (inserted) return { row: inserted, inserted: true };
19363
+ const [existing] = await db.select().from(connectorActionRequests).where(
19364
+ and11(
19365
+ eq11(connectorActionRequests.workspaceId, input.identity.workspaceId),
19366
+ eq11(connectorActionRequests.sessionId, input.identity.sessionId),
19367
+ eq11(connectorActionRequests.turnId, input.identity.turnId),
19368
+ eq11(connectorActionRequests.approvalId, input.invocation.approvalId)
19369
+ )
19370
+ ).for("update").limit(1);
19371
+ if (!existing || !connectorActionRequestMatches(existing, input)) {
19372
+ throw new Error("connector action approval id conflicts with different immutable inputs");
19373
+ }
19374
+ return { row: existing, inserted: false };
19375
+ }
19376
+ async function upsertConnectorActionPolicy(db, input) {
19377
+ const scope = {
19378
+ connectionId: boundedConnectorActionText(
19379
+ input.connectionId,
19380
+ "connector connection id",
19381
+ CONNECTOR_ACTION_CONNECTION_ID_MAX
19382
+ ),
19383
+ serverId: boundedConnectorActionText(
19384
+ input.serverId,
19385
+ "connector server id",
19386
+ CONNECTOR_ACTION_SERVER_ID_MAX
19387
+ ),
19388
+ toolName: boundedConnectorActionText(
19389
+ input.toolName,
19390
+ "connector tool name",
19391
+ CONNECTOR_ACTION_NAME_MAX
19392
+ ),
19393
+ actionName: boundedConnectorActionText(
19394
+ input.actionName,
19395
+ "connector action name",
19396
+ CONNECTOR_ACTION_NAME_MAX
19397
+ )
19398
+ };
19399
+ const subjectId = boundedConnectorActionText(input.subjectId, "policy actor", 1024);
19400
+ return await withRlsContext(
19401
+ db,
19402
+ { accountId: input.accountId, workspaceId: input.workspaceId },
19403
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
19404
+ await assertWorkspaceAccountPairInScope(tx, input.accountId, input.workspaceId);
19405
+ const [existing] = await tx.select().from(connectorActionPolicies).where(
19406
+ and11(
19407
+ eq11(connectorActionPolicies.workspaceId, input.workspaceId),
19408
+ eq11(connectorActionPolicies.connectionId, scope.connectionId),
19409
+ eq11(connectorActionPolicies.serverId, scope.serverId),
19410
+ eq11(connectorActionPolicies.toolName, scope.toolName),
19411
+ eq11(connectorActionPolicies.actionName, scope.actionName)
19412
+ )
19413
+ ).for("update").limit(1);
19414
+ if (existing?.policy === input.policy) return { policy: existing, changed: false };
19415
+ const now = /* @__PURE__ */ new Date();
19416
+ const [row] = existing ? await tx.update(connectorActionPolicies).set({
19417
+ policy: input.policy,
19418
+ version: existing.version + 1,
19419
+ updatedBySubjectId: subjectId,
19420
+ updatedAt: now
19421
+ }).where(eq11(connectorActionPolicies.id, existing.id)).returning() : await tx.insert(connectorActionPolicies).values({
19422
+ accountId: input.accountId,
19423
+ workspaceId: input.workspaceId,
19424
+ ...scope,
19425
+ policy: input.policy,
19426
+ createdBySubjectId: subjectId,
19427
+ updatedBySubjectId: subjectId
19428
+ }).returning();
19429
+ if (!row) throw new Error("Failed to persist connector action policy");
19430
+ await tx.insert(auditEvents).values({
19431
+ accountId: input.accountId,
19432
+ workspaceId: input.workspaceId,
19433
+ subjectId,
19434
+ action: "connector.action.policy_changed",
19435
+ targetType: "connector_action_policy",
19436
+ targetId: row.id,
19437
+ metadata: {
19438
+ connectionId: row.connectionId,
19439
+ serverId: row.serverId,
19440
+ toolName: row.toolName,
19441
+ actionName: row.actionName,
19442
+ policy: row.policy,
19443
+ version: row.version,
19444
+ previousPolicy: existing?.policy ?? null,
19445
+ previousVersion: existing?.version ?? null
19446
+ }
19447
+ });
19448
+ return { policy: row, changed: true };
19449
+ })
19450
+ );
19451
+ }
19452
+ async function prepareConnectorActionApproval(db, identity, invocation) {
19453
+ const normalized = normalizedConnectorActionInvocation(identity, invocation);
19454
+ if (!normalized.connectionId) {
19455
+ return { managed: false, decision: "unmanaged" };
19456
+ }
19457
+ return await withRlsContext(
19458
+ db,
19459
+ { accountId: identity.accountId, workspaceId: identity.workspaceId },
19460
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
19461
+ const snapshot = await connectorActionAttemptSnapshot(tx, identity);
19462
+ const resolved = resolveConnectorActionPolicy(snapshot, {
19463
+ connectionId: normalized.connectionId,
19464
+ serverId: normalized.serverId,
19465
+ toolName: normalized.toolName,
19466
+ actionName: normalized.policyActionSelector
19467
+ });
19468
+ if (!resolved.managed) return { managed: false, decision: "unmanaged" };
19469
+ const durable = durableConnectorActionInvocation(
19470
+ identity,
19471
+ { ...normalized, connectionId: normalized.connectionId },
19472
+ resolved
19473
+ );
19474
+ const decision = resolved.entry?.policy ?? "block";
19475
+ if (decision === "allow") {
19476
+ return {
19477
+ managed: true,
19478
+ decision,
19479
+ actionFingerprint: durable.actionFingerprint
19480
+ };
19481
+ }
19482
+ const { row, inserted } = await insertConnectorActionRequest(tx, {
19483
+ identity,
19484
+ invocation: durable,
19485
+ resolved,
19486
+ status: decision === "ask" ? "pending" : "blocked"
19487
+ });
19488
+ if (inserted) {
19489
+ await insertConnectorActionAudit(tx, {
19490
+ row,
19491
+ action: decision === "ask" ? "connector.action.approval_requested" : "connector.action.blocked",
19492
+ subjectId: identity.initiator.subjectId,
19493
+ extra: { outcome: decision }
19494
+ });
19495
+ }
19496
+ return {
19497
+ managed: true,
19498
+ decision,
19499
+ requestId: row.id,
19500
+ actionFingerprint: row.actionFingerprint
19501
+ };
19502
+ })
19503
+ );
19504
+ }
19505
+ async function beginConnectorActionExecution(db, identity, invocation) {
19506
+ const normalized = normalizedConnectorActionInvocation(identity, invocation);
19507
+ if (!normalized.connectionId) {
19508
+ return { allowed: true, managed: false };
19509
+ }
19510
+ return await withRlsContext(
19511
+ db,
19512
+ { accountId: identity.accountId, workspaceId: identity.workspaceId },
19513
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
19514
+ const snapshot = await connectorActionAttemptSnapshot(tx, identity);
19515
+ const [existing] = await tx.select().from(connectorActionRequests).where(
19516
+ and11(
19517
+ eq11(connectorActionRequests.workspaceId, identity.workspaceId),
19518
+ eq11(connectorActionRequests.sessionId, identity.sessionId),
19519
+ eq11(connectorActionRequests.turnId, identity.turnId),
19520
+ eq11(connectorActionRequests.approvalId, normalized.approvalId)
19521
+ )
19522
+ ).for("update").limit(1);
19523
+ if (existing && !connectorActionRequestMatchesLogicalCall(existing, identity, normalized)) {
19524
+ throw new Error("connector action approval id conflicts with different immutable inputs");
19525
+ }
19526
+ let row = existing;
19527
+ let inserted = false;
19528
+ if (!row) {
19529
+ const resolved = resolveConnectorActionPolicy(snapshot, {
19530
+ connectionId: normalized.connectionId,
19531
+ serverId: normalized.serverId,
19532
+ toolName: normalized.toolName,
19533
+ actionName: normalized.policyActionSelector
19534
+ });
19535
+ if (!resolved.managed) return { allowed: true, managed: false };
19536
+ const durable = durableConnectorActionInvocation(
19537
+ identity,
19538
+ { ...normalized, connectionId: normalized.connectionId },
19539
+ resolved
19540
+ );
19541
+ const decision = resolved.entry?.policy ?? "block";
19542
+ const created = await insertConnectorActionRequest(tx, {
19543
+ identity,
19544
+ invocation: durable,
19545
+ resolved,
19546
+ status: decision === "ask" ? "pending" : decision === "block" ? "blocked" : "executing"
19547
+ });
19548
+ row = created.row;
19549
+ inserted = created.inserted;
19550
+ if (inserted) {
19551
+ await insertConnectorActionAudit(tx, {
19552
+ row,
19553
+ action: decision === "ask" ? "connector.action.approval_requested" : decision === "block" ? "connector.action.blocked" : "connector.action.execution_started",
19554
+ subjectId: identity.initiator.subjectId,
19555
+ extra: { outcome: decision === "allow" ? "started" : decision }
19556
+ });
19557
+ }
19558
+ }
19559
+ if (row.status === "approved") {
19560
+ const [executing] = await tx.update(connectorActionRequests).set({
19561
+ status: "executing",
19562
+ executionAttemptId: identity.attemptId,
19563
+ executionAttemptGeneration: identity.executionGeneration,
19564
+ executionStartedAt: /* @__PURE__ */ new Date(),
19565
+ updatedAt: /* @__PURE__ */ new Date()
19566
+ }).where(eq11(connectorActionRequests.id, row.id)).returning();
19567
+ if (!executing) throw new Error("Approved connector action request disappeared");
19568
+ row = executing;
19569
+ await insertConnectorActionAudit(tx, {
19570
+ row,
19571
+ action: "connector.action.execution_started",
19572
+ subjectId: identity.initiator.subjectId,
19573
+ extra: { outcome: "started" }
19574
+ });
19575
+ return {
19576
+ allowed: true,
19577
+ managed: true,
19578
+ requestId: row.id,
19579
+ actionFingerprint: row.actionFingerprint
19580
+ };
19581
+ }
19582
+ if (row.status === "executing") {
19583
+ if (inserted) {
19584
+ return {
19585
+ allowed: true,
19586
+ managed: true,
19587
+ requestId: row.id,
19588
+ actionFingerprint: row.actionFingerprint
19589
+ };
19590
+ }
19591
+ const [uncertain] = await tx.update(connectorActionRequests).set({
19592
+ status: "uncertain",
19593
+ outcome: "retry_after_execution_started",
19594
+ executionFinishedAt: /* @__PURE__ */ new Date(),
19595
+ updatedAt: /* @__PURE__ */ new Date()
19596
+ }).where(eq11(connectorActionRequests.id, row.id)).returning();
19597
+ if (!uncertain) throw new Error("Executing connector action request disappeared");
19598
+ await insertConnectorActionAudit(tx, {
19599
+ row: uncertain,
19600
+ action: "connector.action.execution_uncertain",
19601
+ subjectId: identity.initiator.subjectId,
19602
+ extra: { outcome: "retry_denied" }
19603
+ });
19604
+ return {
19605
+ allowed: false,
19606
+ managed: true,
19607
+ reason: "uncertain_retry",
19608
+ requestId: uncertain.id,
19609
+ actionFingerprint: uncertain.actionFingerprint
19610
+ };
19611
+ }
19612
+ const reason = row.status === "pending" ? "approval_required" : row.status === "rejected" ? "rejected" : row.status === "blocked" ? "blocked" : "already_executed";
19613
+ return {
19614
+ allowed: false,
19615
+ managed: true,
19616
+ reason,
19617
+ requestId: row.id,
19618
+ actionFingerprint: row.actionFingerprint
19619
+ };
19620
+ })
19621
+ );
19622
+ }
19623
+ async function completeConnectorActionExecution(db, input) {
19624
+ await withRlsContext(
19625
+ db,
19626
+ { accountId: input.accountId, workspaceId: input.workspaceId },
19627
+ async (scopedDb) => await scopedDb.transaction(async (tx) => {
19628
+ const [existing] = await tx.select().from(connectorActionRequests).where(
19629
+ and11(
19630
+ eq11(connectorActionRequests.workspaceId, input.workspaceId),
19631
+ eq11(connectorActionRequests.id, input.requestId),
19632
+ eq11(connectorActionRequests.executionAttemptId, input.attemptId)
19633
+ )
19634
+ ).for("update").limit(1);
19635
+ if (!existing) throw new Error("Connector action request not found for completion");
19636
+ if (existing.status === input.outcome) return;
19637
+ if (existing.status !== "executing") {
19638
+ throw new Error(`Connector action request cannot complete from ${existing.status}`);
19639
+ }
19640
+ const [row] = await tx.update(connectorActionRequests).set({
19641
+ status: input.outcome,
19642
+ outcome: input.outcome,
19643
+ executionFinishedAt: /* @__PURE__ */ new Date(),
19644
+ updatedAt: /* @__PURE__ */ new Date()
19645
+ }).where(eq11(connectorActionRequests.id, existing.id)).returning();
19646
+ if (!row) throw new Error("Connector action request disappeared during completion");
19647
+ await insertConnectorActionAudit(tx, {
19648
+ row,
19649
+ action: input.outcome === "completed" ? "connector.action.execution_completed" : "connector.action.execution_uncertain",
19650
+ subjectId: row.initiatorSubjectId,
19651
+ extra: { outcome: input.outcome }
19652
+ });
19653
+ })
19654
+ );
19655
+ }
19120
19656
  async function getNestedAgentDepthDeploymentPolicy(db) {
19121
19657
  const [row] = await db.select({
19122
19658
  maxNestedAgentDepth: nestedAgentDepthConfiguration.maxNestedAgentDepth,
@@ -31178,6 +31714,24 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
31178
31714
  const mcpApprovalPolicies = Object.fromEntries(
31179
31715
  policyRows.map((row2) => [row2.serverId, row2.requireApproval ?? false])
31180
31716
  );
31717
+ const connectorPolicyRows = await tx.select({
31718
+ id: connectorActionPolicies.id,
31719
+ connectionId: connectorActionPolicies.connectionId,
31720
+ serverId: connectorActionPolicies.serverId,
31721
+ toolName: connectorActionPolicies.toolName,
31722
+ actionName: connectorActionPolicies.actionName,
31723
+ policy: connectorActionPolicies.policy,
31724
+ version: connectorActionPolicies.version
31725
+ }).from(connectorActionPolicies).where(eq11(connectorActionPolicies.workspaceId, workspaceId)).orderBy(
31726
+ asc5(connectorActionPolicies.connectionId),
31727
+ asc5(connectorActionPolicies.serverId),
31728
+ asc5(connectorActionPolicies.toolName),
31729
+ asc5(connectorActionPolicies.actionName),
31730
+ asc5(connectorActionPolicies.id)
31731
+ ).limit(2049);
31732
+ if (connectorPolicyRows.length > 2048) {
31733
+ throw new Error("Connector action policy snapshot exceeds the 2048-row bound");
31734
+ }
31181
31735
  return await registerSessionTurnAttemptClaim(tx, {
31182
31736
  id: input.attemptId,
31183
31737
  accountId: session.accountId,
@@ -31189,7 +31743,8 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
31189
31743
  temporalWorkflowRunId: input.workflowRunId,
31190
31744
  temporalActivityId: input.dispatchId,
31191
31745
  verifiedControlRevision: Number(workspaceControl.revision),
31192
- mcpApprovalPolicies
31746
+ mcpApprovalPolicies,
31747
+ connectorActionPolicies: connectorPolicyRows
31193
31748
  });
31194
31749
  };
31195
31750
  if (session.activeTurnId !== null) {
@@ -34918,6 +35473,37 @@ async function acceptSessionApprovalDecision(db, input) {
34918
35473
  clientEventId: input.clientEventId ?? null
34919
35474
  }).returning();
34920
35475
  if (!event) throw new Error("Failed to append approval decision");
35476
+ const approvalDecision = input.payload.decision;
35477
+ if (approvalDecision === "approve" || approvalDecision === "reject") {
35478
+ const [connectorRequest] = await tx.update(connectorActionRequests).set({
35479
+ status: approvalDecision === "approve" ? "approved" : "rejected",
35480
+ decision: approvalDecision,
35481
+ decisionBySubjectId: boundedConnectorActionText(
35482
+ input.subjectId,
35483
+ "approval actor",
35484
+ 1024
35485
+ ),
35486
+ decisionEventId: event.id,
35487
+ decidedAt: event.occurredAt,
35488
+ updatedAt: /* @__PURE__ */ new Date()
35489
+ }).where(
35490
+ and11(
35491
+ eq11(connectorActionRequests.workspaceId, input.workspaceId),
35492
+ eq11(connectorActionRequests.sessionId, input.sessionId),
35493
+ eq11(connectorActionRequests.turnId, turn.id),
35494
+ eq11(connectorActionRequests.approvalId, approvalId),
35495
+ eq11(connectorActionRequests.status, "pending")
35496
+ )
35497
+ ).returning();
35498
+ if (connectorRequest) {
35499
+ await insertConnectorActionAudit(tx, {
35500
+ row: connectorRequest,
35501
+ action: "connector.action.approval_decided",
35502
+ subjectId: input.subjectId,
35503
+ extra: { decision: approvalDecision }
35504
+ });
35505
+ }
35506
+ }
34921
35507
  await tx.update(sessions).set({
34922
35508
  lastSequence: session.lastSequence + 1,
34923
35509
  updatedAt: /* @__PURE__ */ new Date()
@@ -36214,6 +36800,7 @@ export {
36214
36800
  attachKnowledgeEntityAlias,
36215
36801
  autoResumeSessionBranchInTransaction,
36216
36802
  backfillModelCallFactsFromSessionEvents,
36803
+ beginConnectorActionExecution,
36217
36804
  beginKnowledgeSyncRun,
36218
36805
  beginRigChangeVerificationAttempt,
36219
36806
  beginSandboxRematerialization,
@@ -36257,6 +36844,7 @@ export {
36257
36844
  codexCapacityRefreshBackoffMs,
36258
36845
  commitWarmingToWarm,
36259
36846
  completeCodexResetRedemption,
36847
+ completeConnectorActionExecution,
36260
36848
  completeExpiredFileUploadCleanup,
36261
36849
  completeFileUpload,
36262
36850
  completeFileUploadCleanup,
@@ -36586,6 +37174,7 @@ export {
36586
37174
  loadSocialConnectionCredential,
36587
37175
  loadVariableSetForRun,
36588
37176
  loadWorkspaceEnvironmentForRun,
37177
+ loadWorkspaceVercelAiGatewayApiKey,
36589
37178
  lockSessionEventWriteRows,
36590
37179
  lockWorkspaceInferenceControl,
36591
37180
  markFileUploadFailed,
@@ -36627,6 +37216,7 @@ export {
36627
37216
  persistDrainSnapshot,
36628
37217
  persistWarmSnapshot,
36629
37218
  planWorkspaceCaptureGc,
37219
+ prepareConnectorActionApproval,
36630
37220
  previewColdLostLeaseInstanceBlockers,
36631
37221
  projectEffectiveControlForRelatedAccess,
36632
37222
  projectSessionForRelatedAccess,
@@ -36708,6 +37298,7 @@ export {
36708
37298
  requireWorkspace,
36709
37299
  reserveSessionCommandReceipt,
36710
37300
  reserveToolspaceCallForAttempt,
37301
+ resolveConnectorActionPolicy,
36711
37302
  resolveSlackInstallationRoute,
36712
37303
  resolveWorkspaceMemoryBlock,
36713
37304
  restoreKnowledgeSourceObject,
@@ -36823,6 +37414,7 @@ export {
36823
37414
  upsertBillingCustomer,
36824
37415
  upsertCapabilityCatalogItem,
36825
37416
  upsertCodexSubscriptionCredential,
37417
+ upsertConnectorActionPolicy,
36826
37418
  upsertGitHubInstallation,
36827
37419
  upsertKnowledgeEntity,
36828
37420
  upsertKnowledgeFact,
@@ -36849,6 +37441,7 @@ export {
36849
37441
  withWorkspaceSubjectRls,
36850
37442
  withWorkspaceUsageLock,
36851
37443
  workspaceCaptureAtRevision,
36852
- workspaceCodexSubscriptionActive
37444
+ workspaceCodexSubscriptionActive,
37445
+ workspaceVercelAiGatewayConnectionActive
36853
37446
  };
36854
37447
  //# sourceMappingURL=index.js.map