@opengeni/db 0.21.0 → 0.22.1
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/{chunk-OTJHD33E.js → chunk-BNGEN5QZ.js} +5 -1
- package/dist/chunk-BNGEN5QZ.js.map +1 -0
- package/dist/{chunk-NK2A36KP.js → chunk-CYGFLLMN.js} +150 -1
- package/dist/chunk-CYGFLLMN.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +565 -4
- package/dist/index.js.map +1 -1
- package/dist/provision-roles.js +1 -1
- package/dist/runtime-posture.d.ts +2 -2
- package/dist/schema.d.ts +820 -0
- package/dist/schema.js +5 -1
- package/dist/session-control.d.ts +1 -0
- package/drizzle/0155_connector_action_policies.sql +218 -0
- package/package.json +3 -3
- package/src/index.ts +870 -0
- package/src/runtime-posture.ts +4 -0
- package/src/schema.ts +181 -0
- package/src/session-control.ts +3 -0
- package/dist/chunk-NK2A36KP.js.map +0 -1
- package/dist/chunk-OTJHD33E.js.map +0 -1
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-
|
|
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-
|
|
130
|
+
} from "./chunk-BNGEN5QZ.js";
|
|
129
131
|
import "./chunk-PZ5AY32C.js";
|
|
130
132
|
|
|
131
133
|
// src/index.ts
|
|
@@ -1397,7 +1399,7 @@ async function registerSessionTurnAttemptClaim(db, input) {
|
|
|
1397
1399
|
eq4(sessionTurnAttempts.id, input.id)
|
|
1398
1400
|
)
|
|
1399
1401
|
).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") {
|
|
1402
|
+
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
1403
|
throw new SessionControlInvariantError(
|
|
1402
1404
|
`Attempt ${input.id} conflicts with a different or closed ownership chain`
|
|
1403
1405
|
);
|
|
@@ -19117,6 +19119,510 @@ async function listSessionMcpServersForRun(db, workspaceId, sessionId, attemptId
|
|
|
19117
19119
|
});
|
|
19118
19120
|
});
|
|
19119
19121
|
}
|
|
19122
|
+
var CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX = 2048;
|
|
19123
|
+
var CONNECTOR_ACTION_APPROVAL_ID_MAX = 1024;
|
|
19124
|
+
var CONNECTOR_ACTION_CONNECTION_ID_MAX = 512;
|
|
19125
|
+
var CONNECTOR_ACTION_SERVER_ID_MAX = 256;
|
|
19126
|
+
var CONNECTOR_ACTION_NAME_MAX = 512;
|
|
19127
|
+
function boundedConnectorActionText(value, label, max) {
|
|
19128
|
+
const trimmed = value.trim();
|
|
19129
|
+
if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > max) {
|
|
19130
|
+
throw new Error(`${label} must be between 1 and ${max} UTF-8 bytes`);
|
|
19131
|
+
}
|
|
19132
|
+
return trimmed;
|
|
19133
|
+
}
|
|
19134
|
+
function connectorActionPolicySelector(toolName, args) {
|
|
19135
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
19136
|
+
const action = args.action;
|
|
19137
|
+
if (typeof action === "string" && action.trim().length > 0) {
|
|
19138
|
+
return boundedConnectorActionText(action, "connector action name", CONNECTOR_ACTION_NAME_MAX);
|
|
19139
|
+
}
|
|
19140
|
+
}
|
|
19141
|
+
return toolName;
|
|
19142
|
+
}
|
|
19143
|
+
function connectorActionEvidenceName(resolved) {
|
|
19144
|
+
return resolved.entry?.actionName ?? "*";
|
|
19145
|
+
}
|
|
19146
|
+
function connectorActionFingerprint(input) {
|
|
19147
|
+
return createHash6("sha256").update(
|
|
19148
|
+
stableJson3({
|
|
19149
|
+
workspaceId: input.workspaceId,
|
|
19150
|
+
connectionId: input.connectionId,
|
|
19151
|
+
serverId: input.serverId,
|
|
19152
|
+
toolName: input.toolName,
|
|
19153
|
+
actionName: input.actionName,
|
|
19154
|
+
arguments: input.arguments ?? null
|
|
19155
|
+
}),
|
|
19156
|
+
"utf8"
|
|
19157
|
+
).digest("hex");
|
|
19158
|
+
}
|
|
19159
|
+
function resolveConnectorActionPolicy(snapshot, input) {
|
|
19160
|
+
const candidates = snapshot.filter(
|
|
19161
|
+
(entry) => entry.connectionId === input.connectionId && (entry.serverId === input.serverId || entry.serverId === "*") && (entry.toolName === input.toolName || entry.toolName === "*") && (entry.actionName === input.actionName || entry.actionName === "*")
|
|
19162
|
+
).map((entry) => ({
|
|
19163
|
+
entry,
|
|
19164
|
+
specificity: Number(entry.serverId !== "*") + Number(entry.toolName !== "*") + Number(entry.actionName !== "*")
|
|
19165
|
+
})).sort(
|
|
19166
|
+
(left, right) => right.specificity - left.specificity || left.entry.id.localeCompare(right.entry.id)
|
|
19167
|
+
);
|
|
19168
|
+
const selected = candidates[0];
|
|
19169
|
+
if (!selected) return { managed: false };
|
|
19170
|
+
if (candidates[1]?.specificity === selected.specificity) {
|
|
19171
|
+
return { managed: true, source: "ambiguous", entry: null, decision: "block" };
|
|
19172
|
+
}
|
|
19173
|
+
return { managed: true, source: "explicit", entry: selected.entry };
|
|
19174
|
+
}
|
|
19175
|
+
function connectorActionAuditMetadata(row, extra = {}) {
|
|
19176
|
+
return {
|
|
19177
|
+
requestId: row.id,
|
|
19178
|
+
sessionId: row.sessionId,
|
|
19179
|
+
turnId: row.turnId,
|
|
19180
|
+
attemptId: row.creationAttemptId,
|
|
19181
|
+
creationExecutionGeneration: row.creationExecutionGeneration,
|
|
19182
|
+
executionAttemptId: row.executionAttemptId,
|
|
19183
|
+
executionAttemptGeneration: row.executionAttemptGeneration,
|
|
19184
|
+
approvalId: row.approvalId,
|
|
19185
|
+
initiatorKind: row.initiatorKind,
|
|
19186
|
+
initiatorSubjectId: row.initiatorSubjectId,
|
|
19187
|
+
connectionId: row.connectionId,
|
|
19188
|
+
connectionVersion: row.connectionVersion,
|
|
19189
|
+
serverId: row.serverId,
|
|
19190
|
+
toolName: row.toolName,
|
|
19191
|
+
actionName: row.actionName,
|
|
19192
|
+
policyId: row.policyId,
|
|
19193
|
+
policyVersion: row.policyVersion,
|
|
19194
|
+
policySource: row.policySource,
|
|
19195
|
+
policyDecision: row.policyDecision,
|
|
19196
|
+
actionFingerprint: row.actionFingerprint,
|
|
19197
|
+
...extra
|
|
19198
|
+
};
|
|
19199
|
+
}
|
|
19200
|
+
async function insertConnectorActionAudit(db, input) {
|
|
19201
|
+
await db.insert(auditEvents).values({
|
|
19202
|
+
accountId: input.row.accountId,
|
|
19203
|
+
workspaceId: input.row.workspaceId,
|
|
19204
|
+
subjectId: input.subjectId,
|
|
19205
|
+
action: input.action,
|
|
19206
|
+
targetType: "connector_action_request",
|
|
19207
|
+
targetId: input.row.id,
|
|
19208
|
+
metadata: connectorActionAuditMetadata(input.row, input.extra)
|
|
19209
|
+
});
|
|
19210
|
+
}
|
|
19211
|
+
function normalizedConnectorActionInvocation(identity, invocation) {
|
|
19212
|
+
const approvalId = boundedConnectorActionText(
|
|
19213
|
+
invocation.approvalId,
|
|
19214
|
+
"connector approval id",
|
|
19215
|
+
CONNECTOR_ACTION_APPROVAL_ID_MAX
|
|
19216
|
+
);
|
|
19217
|
+
const serverId = boundedConnectorActionText(
|
|
19218
|
+
invocation.serverId,
|
|
19219
|
+
"connector server id",
|
|
19220
|
+
CONNECTOR_ACTION_SERVER_ID_MAX
|
|
19221
|
+
);
|
|
19222
|
+
const toolName = boundedConnectorActionText(
|
|
19223
|
+
invocation.toolName,
|
|
19224
|
+
"connector tool name",
|
|
19225
|
+
CONNECTOR_ACTION_NAME_MAX
|
|
19226
|
+
);
|
|
19227
|
+
const policyActionSelector = connectorActionPolicySelector(toolName, invocation.arguments);
|
|
19228
|
+
const connectionId = invocation.connectionId?.trim() ? boundedConnectorActionText(
|
|
19229
|
+
invocation.connectionId,
|
|
19230
|
+
"connector connection id",
|
|
19231
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX
|
|
19232
|
+
) : null;
|
|
19233
|
+
return {
|
|
19234
|
+
approvalId,
|
|
19235
|
+
connectionId,
|
|
19236
|
+
serverId,
|
|
19237
|
+
toolName,
|
|
19238
|
+
policyActionSelector,
|
|
19239
|
+
arguments: invocation.arguments
|
|
19240
|
+
};
|
|
19241
|
+
}
|
|
19242
|
+
function durableConnectorActionInvocation(identity, invocation, resolved) {
|
|
19243
|
+
const actionName = connectorActionEvidenceName(resolved);
|
|
19244
|
+
return {
|
|
19245
|
+
approvalId: invocation.approvalId,
|
|
19246
|
+
connectionId: invocation.connectionId,
|
|
19247
|
+
serverId: invocation.serverId,
|
|
19248
|
+
toolName: invocation.toolName,
|
|
19249
|
+
actionName,
|
|
19250
|
+
actionFingerprint: connectorActionFingerprint({
|
|
19251
|
+
workspaceId: identity.workspaceId,
|
|
19252
|
+
connectionId: invocation.connectionId,
|
|
19253
|
+
serverId: invocation.serverId,
|
|
19254
|
+
toolName: invocation.toolName,
|
|
19255
|
+
actionName,
|
|
19256
|
+
arguments: invocation.arguments
|
|
19257
|
+
})
|
|
19258
|
+
};
|
|
19259
|
+
}
|
|
19260
|
+
async function connectorActionAttemptSnapshot(db, identity) {
|
|
19261
|
+
const [attempt] = await db.select({
|
|
19262
|
+
accountId: sessionTurnAttempts.accountId,
|
|
19263
|
+
sessionId: sessionTurnAttempts.sessionId,
|
|
19264
|
+
turnId: sessionTurnAttempts.turnId,
|
|
19265
|
+
executionGeneration: sessionTurnAttempts.executionGeneration,
|
|
19266
|
+
state: sessionTurnAttempts.state,
|
|
19267
|
+
connectorActionPolicies: sessionTurnAttempts.connectorActionPolicies
|
|
19268
|
+
}).from(sessionTurnAttempts).where(
|
|
19269
|
+
and11(
|
|
19270
|
+
eq11(sessionTurnAttempts.workspaceId, identity.workspaceId),
|
|
19271
|
+
eq11(sessionTurnAttempts.id, identity.attemptId)
|
|
19272
|
+
)
|
|
19273
|
+
).limit(1);
|
|
19274
|
+
if (!attempt || attempt.accountId !== identity.accountId || attempt.sessionId !== identity.sessionId || attempt.turnId !== identity.turnId || attempt.executionGeneration !== identity.executionGeneration || !["claimed", "running"].includes(attempt.state)) {
|
|
19275
|
+
throw new Error(`connector action attempt ownership is unavailable: ${identity.attemptId}`);
|
|
19276
|
+
}
|
|
19277
|
+
if (attempt.connectorActionPolicies.length > CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX) {
|
|
19278
|
+
throw new Error("connector action policy snapshot exceeds the runtime bound");
|
|
19279
|
+
}
|
|
19280
|
+
return attempt.connectorActionPolicies;
|
|
19281
|
+
}
|
|
19282
|
+
function connectorActionRequestMatches(row, input) {
|
|
19283
|
+
const entry = input.resolved.entry;
|
|
19284
|
+
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;
|
|
19285
|
+
}
|
|
19286
|
+
function connectorActionRequestMatchesLogicalCall(row, identity, invocation) {
|
|
19287
|
+
if (!invocation.connectionId) return false;
|
|
19288
|
+
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({
|
|
19289
|
+
workspaceId: identity.workspaceId,
|
|
19290
|
+
connectionId: invocation.connectionId,
|
|
19291
|
+
serverId: invocation.serverId,
|
|
19292
|
+
toolName: invocation.toolName,
|
|
19293
|
+
actionName: row.actionName,
|
|
19294
|
+
arguments: invocation.arguments
|
|
19295
|
+
});
|
|
19296
|
+
}
|
|
19297
|
+
async function insertConnectorActionRequest(db, input) {
|
|
19298
|
+
const entry = input.resolved.entry;
|
|
19299
|
+
const [inserted] = await db.insert(connectorActionRequests).values({
|
|
19300
|
+
accountId: input.identity.accountId,
|
|
19301
|
+
workspaceId: input.identity.workspaceId,
|
|
19302
|
+
sessionId: input.identity.sessionId,
|
|
19303
|
+
turnId: input.identity.turnId,
|
|
19304
|
+
creationAttemptId: input.identity.attemptId,
|
|
19305
|
+
creationExecutionGeneration: input.identity.executionGeneration,
|
|
19306
|
+
approvalId: input.invocation.approvalId,
|
|
19307
|
+
initiatorKind: input.identity.initiator.kind,
|
|
19308
|
+
initiatorSubjectId: input.identity.initiator.subjectId,
|
|
19309
|
+
connectionId: input.invocation.connectionId,
|
|
19310
|
+
serverId: input.invocation.serverId,
|
|
19311
|
+
toolName: input.invocation.toolName,
|
|
19312
|
+
actionName: input.invocation.actionName,
|
|
19313
|
+
policyId: entry?.id ?? null,
|
|
19314
|
+
policyVersion: entry?.version ?? null,
|
|
19315
|
+
policySource: input.resolved.source,
|
|
19316
|
+
policyDecision: entry?.policy ?? "block",
|
|
19317
|
+
actionFingerprint: input.invocation.actionFingerprint,
|
|
19318
|
+
status: input.status,
|
|
19319
|
+
...input.status === "executing" ? {
|
|
19320
|
+
executionAttemptId: input.identity.attemptId,
|
|
19321
|
+
executionAttemptGeneration: input.identity.executionGeneration,
|
|
19322
|
+
executionStartedAt: /* @__PURE__ */ new Date()
|
|
19323
|
+
} : {}
|
|
19324
|
+
}).onConflictDoNothing({
|
|
19325
|
+
target: [
|
|
19326
|
+
connectorActionRequests.workspaceId,
|
|
19327
|
+
connectorActionRequests.sessionId,
|
|
19328
|
+
connectorActionRequests.turnId,
|
|
19329
|
+
connectorActionRequests.approvalId
|
|
19330
|
+
]
|
|
19331
|
+
}).returning();
|
|
19332
|
+
if (inserted) return { row: inserted, inserted: true };
|
|
19333
|
+
const [existing] = await db.select().from(connectorActionRequests).where(
|
|
19334
|
+
and11(
|
|
19335
|
+
eq11(connectorActionRequests.workspaceId, input.identity.workspaceId),
|
|
19336
|
+
eq11(connectorActionRequests.sessionId, input.identity.sessionId),
|
|
19337
|
+
eq11(connectorActionRequests.turnId, input.identity.turnId),
|
|
19338
|
+
eq11(connectorActionRequests.approvalId, input.invocation.approvalId)
|
|
19339
|
+
)
|
|
19340
|
+
).for("update").limit(1);
|
|
19341
|
+
if (!existing || !connectorActionRequestMatches(existing, input)) {
|
|
19342
|
+
throw new Error("connector action approval id conflicts with different immutable inputs");
|
|
19343
|
+
}
|
|
19344
|
+
return { row: existing, inserted: false };
|
|
19345
|
+
}
|
|
19346
|
+
async function upsertConnectorActionPolicy(db, input) {
|
|
19347
|
+
const scope = {
|
|
19348
|
+
connectionId: boundedConnectorActionText(
|
|
19349
|
+
input.connectionId,
|
|
19350
|
+
"connector connection id",
|
|
19351
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX
|
|
19352
|
+
),
|
|
19353
|
+
serverId: boundedConnectorActionText(
|
|
19354
|
+
input.serverId,
|
|
19355
|
+
"connector server id",
|
|
19356
|
+
CONNECTOR_ACTION_SERVER_ID_MAX
|
|
19357
|
+
),
|
|
19358
|
+
toolName: boundedConnectorActionText(
|
|
19359
|
+
input.toolName,
|
|
19360
|
+
"connector tool name",
|
|
19361
|
+
CONNECTOR_ACTION_NAME_MAX
|
|
19362
|
+
),
|
|
19363
|
+
actionName: boundedConnectorActionText(
|
|
19364
|
+
input.actionName,
|
|
19365
|
+
"connector action name",
|
|
19366
|
+
CONNECTOR_ACTION_NAME_MAX
|
|
19367
|
+
)
|
|
19368
|
+
};
|
|
19369
|
+
const subjectId = boundedConnectorActionText(input.subjectId, "policy actor", 1024);
|
|
19370
|
+
return await withRlsContext(
|
|
19371
|
+
db,
|
|
19372
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
19373
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
19374
|
+
await assertWorkspaceAccountPairInScope(tx, input.accountId, input.workspaceId);
|
|
19375
|
+
const [existing] = await tx.select().from(connectorActionPolicies).where(
|
|
19376
|
+
and11(
|
|
19377
|
+
eq11(connectorActionPolicies.workspaceId, input.workspaceId),
|
|
19378
|
+
eq11(connectorActionPolicies.connectionId, scope.connectionId),
|
|
19379
|
+
eq11(connectorActionPolicies.serverId, scope.serverId),
|
|
19380
|
+
eq11(connectorActionPolicies.toolName, scope.toolName),
|
|
19381
|
+
eq11(connectorActionPolicies.actionName, scope.actionName)
|
|
19382
|
+
)
|
|
19383
|
+
).for("update").limit(1);
|
|
19384
|
+
if (existing?.policy === input.policy) return { policy: existing, changed: false };
|
|
19385
|
+
const now = /* @__PURE__ */ new Date();
|
|
19386
|
+
const [row] = existing ? await tx.update(connectorActionPolicies).set({
|
|
19387
|
+
policy: input.policy,
|
|
19388
|
+
version: existing.version + 1,
|
|
19389
|
+
updatedBySubjectId: subjectId,
|
|
19390
|
+
updatedAt: now
|
|
19391
|
+
}).where(eq11(connectorActionPolicies.id, existing.id)).returning() : await tx.insert(connectorActionPolicies).values({
|
|
19392
|
+
accountId: input.accountId,
|
|
19393
|
+
workspaceId: input.workspaceId,
|
|
19394
|
+
...scope,
|
|
19395
|
+
policy: input.policy,
|
|
19396
|
+
createdBySubjectId: subjectId,
|
|
19397
|
+
updatedBySubjectId: subjectId
|
|
19398
|
+
}).returning();
|
|
19399
|
+
if (!row) throw new Error("Failed to persist connector action policy");
|
|
19400
|
+
await tx.insert(auditEvents).values({
|
|
19401
|
+
accountId: input.accountId,
|
|
19402
|
+
workspaceId: input.workspaceId,
|
|
19403
|
+
subjectId,
|
|
19404
|
+
action: "connector.action.policy_changed",
|
|
19405
|
+
targetType: "connector_action_policy",
|
|
19406
|
+
targetId: row.id,
|
|
19407
|
+
metadata: {
|
|
19408
|
+
connectionId: row.connectionId,
|
|
19409
|
+
serverId: row.serverId,
|
|
19410
|
+
toolName: row.toolName,
|
|
19411
|
+
actionName: row.actionName,
|
|
19412
|
+
policy: row.policy,
|
|
19413
|
+
version: row.version,
|
|
19414
|
+
previousPolicy: existing?.policy ?? null,
|
|
19415
|
+
previousVersion: existing?.version ?? null
|
|
19416
|
+
}
|
|
19417
|
+
});
|
|
19418
|
+
return { policy: row, changed: true };
|
|
19419
|
+
})
|
|
19420
|
+
);
|
|
19421
|
+
}
|
|
19422
|
+
async function prepareConnectorActionApproval(db, identity, invocation) {
|
|
19423
|
+
const normalized = normalizedConnectorActionInvocation(identity, invocation);
|
|
19424
|
+
if (!normalized.connectionId) {
|
|
19425
|
+
return { managed: false, decision: "unmanaged" };
|
|
19426
|
+
}
|
|
19427
|
+
return await withRlsContext(
|
|
19428
|
+
db,
|
|
19429
|
+
{ accountId: identity.accountId, workspaceId: identity.workspaceId },
|
|
19430
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
19431
|
+
const snapshot = await connectorActionAttemptSnapshot(tx, identity);
|
|
19432
|
+
const resolved = resolveConnectorActionPolicy(snapshot, {
|
|
19433
|
+
connectionId: normalized.connectionId,
|
|
19434
|
+
serverId: normalized.serverId,
|
|
19435
|
+
toolName: normalized.toolName,
|
|
19436
|
+
actionName: normalized.policyActionSelector
|
|
19437
|
+
});
|
|
19438
|
+
if (!resolved.managed) return { managed: false, decision: "unmanaged" };
|
|
19439
|
+
const durable = durableConnectorActionInvocation(
|
|
19440
|
+
identity,
|
|
19441
|
+
{ ...normalized, connectionId: normalized.connectionId },
|
|
19442
|
+
resolved
|
|
19443
|
+
);
|
|
19444
|
+
const decision = resolved.entry?.policy ?? "block";
|
|
19445
|
+
if (decision === "allow") {
|
|
19446
|
+
return {
|
|
19447
|
+
managed: true,
|
|
19448
|
+
decision,
|
|
19449
|
+
actionFingerprint: durable.actionFingerprint
|
|
19450
|
+
};
|
|
19451
|
+
}
|
|
19452
|
+
const { row, inserted } = await insertConnectorActionRequest(tx, {
|
|
19453
|
+
identity,
|
|
19454
|
+
invocation: durable,
|
|
19455
|
+
resolved,
|
|
19456
|
+
status: decision === "ask" ? "pending" : "blocked"
|
|
19457
|
+
});
|
|
19458
|
+
if (inserted) {
|
|
19459
|
+
await insertConnectorActionAudit(tx, {
|
|
19460
|
+
row,
|
|
19461
|
+
action: decision === "ask" ? "connector.action.approval_requested" : "connector.action.blocked",
|
|
19462
|
+
subjectId: identity.initiator.subjectId,
|
|
19463
|
+
extra: { outcome: decision }
|
|
19464
|
+
});
|
|
19465
|
+
}
|
|
19466
|
+
return {
|
|
19467
|
+
managed: true,
|
|
19468
|
+
decision,
|
|
19469
|
+
requestId: row.id,
|
|
19470
|
+
actionFingerprint: row.actionFingerprint
|
|
19471
|
+
};
|
|
19472
|
+
})
|
|
19473
|
+
);
|
|
19474
|
+
}
|
|
19475
|
+
async function beginConnectorActionExecution(db, identity, invocation) {
|
|
19476
|
+
const normalized = normalizedConnectorActionInvocation(identity, invocation);
|
|
19477
|
+
if (!normalized.connectionId) {
|
|
19478
|
+
return { allowed: true, managed: false };
|
|
19479
|
+
}
|
|
19480
|
+
return await withRlsContext(
|
|
19481
|
+
db,
|
|
19482
|
+
{ accountId: identity.accountId, workspaceId: identity.workspaceId },
|
|
19483
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
19484
|
+
const snapshot = await connectorActionAttemptSnapshot(tx, identity);
|
|
19485
|
+
const [existing] = await tx.select().from(connectorActionRequests).where(
|
|
19486
|
+
and11(
|
|
19487
|
+
eq11(connectorActionRequests.workspaceId, identity.workspaceId),
|
|
19488
|
+
eq11(connectorActionRequests.sessionId, identity.sessionId),
|
|
19489
|
+
eq11(connectorActionRequests.turnId, identity.turnId),
|
|
19490
|
+
eq11(connectorActionRequests.approvalId, normalized.approvalId)
|
|
19491
|
+
)
|
|
19492
|
+
).for("update").limit(1);
|
|
19493
|
+
if (existing && !connectorActionRequestMatchesLogicalCall(existing, identity, normalized)) {
|
|
19494
|
+
throw new Error("connector action approval id conflicts with different immutable inputs");
|
|
19495
|
+
}
|
|
19496
|
+
let row = existing;
|
|
19497
|
+
let inserted = false;
|
|
19498
|
+
if (!row) {
|
|
19499
|
+
const resolved = resolveConnectorActionPolicy(snapshot, {
|
|
19500
|
+
connectionId: normalized.connectionId,
|
|
19501
|
+
serverId: normalized.serverId,
|
|
19502
|
+
toolName: normalized.toolName,
|
|
19503
|
+
actionName: normalized.policyActionSelector
|
|
19504
|
+
});
|
|
19505
|
+
if (!resolved.managed) return { allowed: true, managed: false };
|
|
19506
|
+
const durable = durableConnectorActionInvocation(
|
|
19507
|
+
identity,
|
|
19508
|
+
{ ...normalized, connectionId: normalized.connectionId },
|
|
19509
|
+
resolved
|
|
19510
|
+
);
|
|
19511
|
+
const decision = resolved.entry?.policy ?? "block";
|
|
19512
|
+
const created = await insertConnectorActionRequest(tx, {
|
|
19513
|
+
identity,
|
|
19514
|
+
invocation: durable,
|
|
19515
|
+
resolved,
|
|
19516
|
+
status: decision === "ask" ? "pending" : decision === "block" ? "blocked" : "executing"
|
|
19517
|
+
});
|
|
19518
|
+
row = created.row;
|
|
19519
|
+
inserted = created.inserted;
|
|
19520
|
+
if (inserted) {
|
|
19521
|
+
await insertConnectorActionAudit(tx, {
|
|
19522
|
+
row,
|
|
19523
|
+
action: decision === "ask" ? "connector.action.approval_requested" : decision === "block" ? "connector.action.blocked" : "connector.action.execution_started",
|
|
19524
|
+
subjectId: identity.initiator.subjectId,
|
|
19525
|
+
extra: { outcome: decision === "allow" ? "started" : decision }
|
|
19526
|
+
});
|
|
19527
|
+
}
|
|
19528
|
+
}
|
|
19529
|
+
if (row.status === "approved") {
|
|
19530
|
+
const [executing] = await tx.update(connectorActionRequests).set({
|
|
19531
|
+
status: "executing",
|
|
19532
|
+
executionAttemptId: identity.attemptId,
|
|
19533
|
+
executionAttemptGeneration: identity.executionGeneration,
|
|
19534
|
+
executionStartedAt: /* @__PURE__ */ new Date(),
|
|
19535
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19536
|
+
}).where(eq11(connectorActionRequests.id, row.id)).returning();
|
|
19537
|
+
if (!executing) throw new Error("Approved connector action request disappeared");
|
|
19538
|
+
row = executing;
|
|
19539
|
+
await insertConnectorActionAudit(tx, {
|
|
19540
|
+
row,
|
|
19541
|
+
action: "connector.action.execution_started",
|
|
19542
|
+
subjectId: identity.initiator.subjectId,
|
|
19543
|
+
extra: { outcome: "started" }
|
|
19544
|
+
});
|
|
19545
|
+
return {
|
|
19546
|
+
allowed: true,
|
|
19547
|
+
managed: true,
|
|
19548
|
+
requestId: row.id,
|
|
19549
|
+
actionFingerprint: row.actionFingerprint
|
|
19550
|
+
};
|
|
19551
|
+
}
|
|
19552
|
+
if (row.status === "executing") {
|
|
19553
|
+
if (inserted) {
|
|
19554
|
+
return {
|
|
19555
|
+
allowed: true,
|
|
19556
|
+
managed: true,
|
|
19557
|
+
requestId: row.id,
|
|
19558
|
+
actionFingerprint: row.actionFingerprint
|
|
19559
|
+
};
|
|
19560
|
+
}
|
|
19561
|
+
const [uncertain] = await tx.update(connectorActionRequests).set({
|
|
19562
|
+
status: "uncertain",
|
|
19563
|
+
outcome: "retry_after_execution_started",
|
|
19564
|
+
executionFinishedAt: /* @__PURE__ */ new Date(),
|
|
19565
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19566
|
+
}).where(eq11(connectorActionRequests.id, row.id)).returning();
|
|
19567
|
+
if (!uncertain) throw new Error("Executing connector action request disappeared");
|
|
19568
|
+
await insertConnectorActionAudit(tx, {
|
|
19569
|
+
row: uncertain,
|
|
19570
|
+
action: "connector.action.execution_uncertain",
|
|
19571
|
+
subjectId: identity.initiator.subjectId,
|
|
19572
|
+
extra: { outcome: "retry_denied" }
|
|
19573
|
+
});
|
|
19574
|
+
return {
|
|
19575
|
+
allowed: false,
|
|
19576
|
+
managed: true,
|
|
19577
|
+
reason: "uncertain_retry",
|
|
19578
|
+
requestId: uncertain.id,
|
|
19579
|
+
actionFingerprint: uncertain.actionFingerprint
|
|
19580
|
+
};
|
|
19581
|
+
}
|
|
19582
|
+
const reason = row.status === "pending" ? "approval_required" : row.status === "rejected" ? "rejected" : row.status === "blocked" ? "blocked" : "already_executed";
|
|
19583
|
+
return {
|
|
19584
|
+
allowed: false,
|
|
19585
|
+
managed: true,
|
|
19586
|
+
reason,
|
|
19587
|
+
requestId: row.id,
|
|
19588
|
+
actionFingerprint: row.actionFingerprint
|
|
19589
|
+
};
|
|
19590
|
+
})
|
|
19591
|
+
);
|
|
19592
|
+
}
|
|
19593
|
+
async function completeConnectorActionExecution(db, input) {
|
|
19594
|
+
await withRlsContext(
|
|
19595
|
+
db,
|
|
19596
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
19597
|
+
async (scopedDb) => await scopedDb.transaction(async (tx) => {
|
|
19598
|
+
const [existing] = await tx.select().from(connectorActionRequests).where(
|
|
19599
|
+
and11(
|
|
19600
|
+
eq11(connectorActionRequests.workspaceId, input.workspaceId),
|
|
19601
|
+
eq11(connectorActionRequests.id, input.requestId),
|
|
19602
|
+
eq11(connectorActionRequests.executionAttemptId, input.attemptId)
|
|
19603
|
+
)
|
|
19604
|
+
).for("update").limit(1);
|
|
19605
|
+
if (!existing) throw new Error("Connector action request not found for completion");
|
|
19606
|
+
if (existing.status === input.outcome) return;
|
|
19607
|
+
if (existing.status !== "executing") {
|
|
19608
|
+
throw new Error(`Connector action request cannot complete from ${existing.status}`);
|
|
19609
|
+
}
|
|
19610
|
+
const [row] = await tx.update(connectorActionRequests).set({
|
|
19611
|
+
status: input.outcome,
|
|
19612
|
+
outcome: input.outcome,
|
|
19613
|
+
executionFinishedAt: /* @__PURE__ */ new Date(),
|
|
19614
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
19615
|
+
}).where(eq11(connectorActionRequests.id, existing.id)).returning();
|
|
19616
|
+
if (!row) throw new Error("Connector action request disappeared during completion");
|
|
19617
|
+
await insertConnectorActionAudit(tx, {
|
|
19618
|
+
row,
|
|
19619
|
+
action: input.outcome === "completed" ? "connector.action.execution_completed" : "connector.action.execution_uncertain",
|
|
19620
|
+
subjectId: row.initiatorSubjectId,
|
|
19621
|
+
extra: { outcome: input.outcome }
|
|
19622
|
+
});
|
|
19623
|
+
})
|
|
19624
|
+
);
|
|
19625
|
+
}
|
|
19120
19626
|
async function getNestedAgentDepthDeploymentPolicy(db) {
|
|
19121
19627
|
const [row] = await db.select({
|
|
19122
19628
|
maxNestedAgentDepth: nestedAgentDepthConfiguration.maxNestedAgentDepth,
|
|
@@ -31178,6 +31684,24 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
|
|
|
31178
31684
|
const mcpApprovalPolicies = Object.fromEntries(
|
|
31179
31685
|
policyRows.map((row2) => [row2.serverId, row2.requireApproval ?? false])
|
|
31180
31686
|
);
|
|
31687
|
+
const connectorPolicyRows = await tx.select({
|
|
31688
|
+
id: connectorActionPolicies.id,
|
|
31689
|
+
connectionId: connectorActionPolicies.connectionId,
|
|
31690
|
+
serverId: connectorActionPolicies.serverId,
|
|
31691
|
+
toolName: connectorActionPolicies.toolName,
|
|
31692
|
+
actionName: connectorActionPolicies.actionName,
|
|
31693
|
+
policy: connectorActionPolicies.policy,
|
|
31694
|
+
version: connectorActionPolicies.version
|
|
31695
|
+
}).from(connectorActionPolicies).where(eq11(connectorActionPolicies.workspaceId, workspaceId)).orderBy(
|
|
31696
|
+
asc5(connectorActionPolicies.connectionId),
|
|
31697
|
+
asc5(connectorActionPolicies.serverId),
|
|
31698
|
+
asc5(connectorActionPolicies.toolName),
|
|
31699
|
+
asc5(connectorActionPolicies.actionName),
|
|
31700
|
+
asc5(connectorActionPolicies.id)
|
|
31701
|
+
).limit(2049);
|
|
31702
|
+
if (connectorPolicyRows.length > 2048) {
|
|
31703
|
+
throw new Error("Connector action policy snapshot exceeds the 2048-row bound");
|
|
31704
|
+
}
|
|
31181
31705
|
return await registerSessionTurnAttemptClaim(tx, {
|
|
31182
31706
|
id: input.attemptId,
|
|
31183
31707
|
accountId: session.accountId,
|
|
@@ -31189,7 +31713,8 @@ async function claimSessionWorkForAttempt(db, workspaceId, input) {
|
|
|
31189
31713
|
temporalWorkflowRunId: input.workflowRunId,
|
|
31190
31714
|
temporalActivityId: input.dispatchId,
|
|
31191
31715
|
verifiedControlRevision: Number(workspaceControl.revision),
|
|
31192
|
-
mcpApprovalPolicies
|
|
31716
|
+
mcpApprovalPolicies,
|
|
31717
|
+
connectorActionPolicies: connectorPolicyRows
|
|
31193
31718
|
});
|
|
31194
31719
|
};
|
|
31195
31720
|
if (session.activeTurnId !== null) {
|
|
@@ -34918,6 +35443,37 @@ async function acceptSessionApprovalDecision(db, input) {
|
|
|
34918
35443
|
clientEventId: input.clientEventId ?? null
|
|
34919
35444
|
}).returning();
|
|
34920
35445
|
if (!event) throw new Error("Failed to append approval decision");
|
|
35446
|
+
const approvalDecision = input.payload.decision;
|
|
35447
|
+
if (approvalDecision === "approve" || approvalDecision === "reject") {
|
|
35448
|
+
const [connectorRequest] = await tx.update(connectorActionRequests).set({
|
|
35449
|
+
status: approvalDecision === "approve" ? "approved" : "rejected",
|
|
35450
|
+
decision: approvalDecision,
|
|
35451
|
+
decisionBySubjectId: boundedConnectorActionText(
|
|
35452
|
+
input.subjectId,
|
|
35453
|
+
"approval actor",
|
|
35454
|
+
1024
|
|
35455
|
+
),
|
|
35456
|
+
decisionEventId: event.id,
|
|
35457
|
+
decidedAt: event.occurredAt,
|
|
35458
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
35459
|
+
}).where(
|
|
35460
|
+
and11(
|
|
35461
|
+
eq11(connectorActionRequests.workspaceId, input.workspaceId),
|
|
35462
|
+
eq11(connectorActionRequests.sessionId, input.sessionId),
|
|
35463
|
+
eq11(connectorActionRequests.turnId, turn.id),
|
|
35464
|
+
eq11(connectorActionRequests.approvalId, approvalId),
|
|
35465
|
+
eq11(connectorActionRequests.status, "pending")
|
|
35466
|
+
)
|
|
35467
|
+
).returning();
|
|
35468
|
+
if (connectorRequest) {
|
|
35469
|
+
await insertConnectorActionAudit(tx, {
|
|
35470
|
+
row: connectorRequest,
|
|
35471
|
+
action: "connector.action.approval_decided",
|
|
35472
|
+
subjectId: input.subjectId,
|
|
35473
|
+
extra: { decision: approvalDecision }
|
|
35474
|
+
});
|
|
35475
|
+
}
|
|
35476
|
+
}
|
|
34921
35477
|
await tx.update(sessions).set({
|
|
34922
35478
|
lastSequence: session.lastSequence + 1,
|
|
34923
35479
|
updatedAt: /* @__PURE__ */ new Date()
|
|
@@ -36214,6 +36770,7 @@ export {
|
|
|
36214
36770
|
attachKnowledgeEntityAlias,
|
|
36215
36771
|
autoResumeSessionBranchInTransaction,
|
|
36216
36772
|
backfillModelCallFactsFromSessionEvents,
|
|
36773
|
+
beginConnectorActionExecution,
|
|
36217
36774
|
beginKnowledgeSyncRun,
|
|
36218
36775
|
beginRigChangeVerificationAttempt,
|
|
36219
36776
|
beginSandboxRematerialization,
|
|
@@ -36257,6 +36814,7 @@ export {
|
|
|
36257
36814
|
codexCapacityRefreshBackoffMs,
|
|
36258
36815
|
commitWarmingToWarm,
|
|
36259
36816
|
completeCodexResetRedemption,
|
|
36817
|
+
completeConnectorActionExecution,
|
|
36260
36818
|
completeExpiredFileUploadCleanup,
|
|
36261
36819
|
completeFileUpload,
|
|
36262
36820
|
completeFileUploadCleanup,
|
|
@@ -36627,6 +37185,7 @@ export {
|
|
|
36627
37185
|
persistDrainSnapshot,
|
|
36628
37186
|
persistWarmSnapshot,
|
|
36629
37187
|
planWorkspaceCaptureGc,
|
|
37188
|
+
prepareConnectorActionApproval,
|
|
36630
37189
|
previewColdLostLeaseInstanceBlockers,
|
|
36631
37190
|
projectEffectiveControlForRelatedAccess,
|
|
36632
37191
|
projectSessionForRelatedAccess,
|
|
@@ -36708,6 +37267,7 @@ export {
|
|
|
36708
37267
|
requireWorkspace,
|
|
36709
37268
|
reserveSessionCommandReceipt,
|
|
36710
37269
|
reserveToolspaceCallForAttempt,
|
|
37270
|
+
resolveConnectorActionPolicy,
|
|
36711
37271
|
resolveSlackInstallationRoute,
|
|
36712
37272
|
resolveWorkspaceMemoryBlock,
|
|
36713
37273
|
restoreKnowledgeSourceObject,
|
|
@@ -36823,6 +37383,7 @@ export {
|
|
|
36823
37383
|
upsertBillingCustomer,
|
|
36824
37384
|
upsertCapabilityCatalogItem,
|
|
36825
37385
|
upsertCodexSubscriptionCredential,
|
|
37386
|
+
upsertConnectorActionPolicy,
|
|
36826
37387
|
upsertGitHubInstallation,
|
|
36827
37388
|
upsertKnowledgeEntity,
|
|
36828
37389
|
upsertKnowledgeFact,
|