@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/src/index.ts
CHANGED
|
@@ -15746,6 +15746,816 @@ export async function listSessionMcpServersForRun(
|
|
|
15746
15746
|
});
|
|
15747
15747
|
}
|
|
15748
15748
|
|
|
15749
|
+
export type ConnectorActionPolicyDecision = schema.ConnectorActionPolicyDecision;
|
|
15750
|
+
export type ConnectorActionPolicySnapshotEntry = schema.ConnectorActionPolicySnapshotEntry;
|
|
15751
|
+
|
|
15752
|
+
export type ConnectorActionAttemptIdentity = {
|
|
15753
|
+
accountId: string;
|
|
15754
|
+
workspaceId: string;
|
|
15755
|
+
sessionId: string;
|
|
15756
|
+
turnId: string;
|
|
15757
|
+
attemptId: string;
|
|
15758
|
+
executionGeneration: number;
|
|
15759
|
+
initiator: Pick<TurnInitiator, "kind" | "subjectId">;
|
|
15760
|
+
};
|
|
15761
|
+
|
|
15762
|
+
export type ConnectorActionInvocation = {
|
|
15763
|
+
approvalId: string;
|
|
15764
|
+
connectionId?: string | null;
|
|
15765
|
+
serverId: string;
|
|
15766
|
+
toolName: string;
|
|
15767
|
+
arguments: unknown;
|
|
15768
|
+
};
|
|
15769
|
+
|
|
15770
|
+
export type PrepareConnectorActionApprovalResult =
|
|
15771
|
+
| { managed: false; decision: "unmanaged" }
|
|
15772
|
+
| {
|
|
15773
|
+
managed: true;
|
|
15774
|
+
decision: ConnectorActionPolicyDecision;
|
|
15775
|
+
requestId?: string;
|
|
15776
|
+
actionFingerprint: string;
|
|
15777
|
+
};
|
|
15778
|
+
|
|
15779
|
+
export type BeginConnectorActionExecutionResult =
|
|
15780
|
+
| { allowed: true; managed: false }
|
|
15781
|
+
| {
|
|
15782
|
+
allowed: true;
|
|
15783
|
+
managed: true;
|
|
15784
|
+
requestId: string;
|
|
15785
|
+
actionFingerprint: string;
|
|
15786
|
+
}
|
|
15787
|
+
| {
|
|
15788
|
+
allowed: false;
|
|
15789
|
+
managed: true;
|
|
15790
|
+
reason: "approval_required" | "blocked" | "rejected" | "already_executed" | "uncertain_retry";
|
|
15791
|
+
requestId: string;
|
|
15792
|
+
actionFingerprint: string;
|
|
15793
|
+
};
|
|
15794
|
+
|
|
15795
|
+
const CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX = 2048;
|
|
15796
|
+
const CONNECTOR_ACTION_APPROVAL_ID_MAX = 1024;
|
|
15797
|
+
const CONNECTOR_ACTION_CONNECTION_ID_MAX = 512;
|
|
15798
|
+
const CONNECTOR_ACTION_SERVER_ID_MAX = 256;
|
|
15799
|
+
const CONNECTOR_ACTION_NAME_MAX = 512;
|
|
15800
|
+
|
|
15801
|
+
function boundedConnectorActionText(value: string, label: string, max: number): string {
|
|
15802
|
+
const trimmed = value.trim();
|
|
15803
|
+
if (trimmed.length === 0 || Buffer.byteLength(trimmed, "utf8") > max) {
|
|
15804
|
+
throw new Error(`${label} must be between 1 and ${max} UTF-8 bytes`);
|
|
15805
|
+
}
|
|
15806
|
+
return trimmed;
|
|
15807
|
+
}
|
|
15808
|
+
|
|
15809
|
+
/**
|
|
15810
|
+
* Resolve the request's policy selector. This caller-controlled value is used
|
|
15811
|
+
* only transiently to match the attempt-frozen policy snapshot; it must never
|
|
15812
|
+
* be copied into a request row or audit event.
|
|
15813
|
+
*/
|
|
15814
|
+
function connectorActionPolicySelector(toolName: string, args: unknown): string {
|
|
15815
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
15816
|
+
const action = (args as Record<string, unknown>).action;
|
|
15817
|
+
if (typeof action === "string" && action.trim().length > 0) {
|
|
15818
|
+
return boundedConnectorActionText(action, "connector action name", CONNECTOR_ACTION_NAME_MAX);
|
|
15819
|
+
}
|
|
15820
|
+
}
|
|
15821
|
+
return toolName;
|
|
15822
|
+
}
|
|
15823
|
+
|
|
15824
|
+
function connectorActionEvidenceName(
|
|
15825
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>,
|
|
15826
|
+
): string {
|
|
15827
|
+
return resolved.entry?.actionName ?? "*";
|
|
15828
|
+
}
|
|
15829
|
+
|
|
15830
|
+
function connectorActionFingerprint(input: {
|
|
15831
|
+
workspaceId: string;
|
|
15832
|
+
connectionId: string;
|
|
15833
|
+
serverId: string;
|
|
15834
|
+
toolName: string;
|
|
15835
|
+
actionName: string;
|
|
15836
|
+
arguments: unknown;
|
|
15837
|
+
}): string {
|
|
15838
|
+
return createHash("sha256")
|
|
15839
|
+
.update(
|
|
15840
|
+
stableJson({
|
|
15841
|
+
workspaceId: input.workspaceId,
|
|
15842
|
+
connectionId: input.connectionId,
|
|
15843
|
+
serverId: input.serverId,
|
|
15844
|
+
toolName: input.toolName,
|
|
15845
|
+
actionName: input.actionName,
|
|
15846
|
+
arguments: input.arguments ?? null,
|
|
15847
|
+
}),
|
|
15848
|
+
"utf8",
|
|
15849
|
+
)
|
|
15850
|
+
.digest("hex");
|
|
15851
|
+
}
|
|
15852
|
+
|
|
15853
|
+
type ResolvedConnectorActionPolicy =
|
|
15854
|
+
| { managed: false }
|
|
15855
|
+
| {
|
|
15856
|
+
managed: true;
|
|
15857
|
+
source: "explicit";
|
|
15858
|
+
entry: ConnectorActionPolicySnapshotEntry;
|
|
15859
|
+
}
|
|
15860
|
+
| {
|
|
15861
|
+
managed: true;
|
|
15862
|
+
source: "ambiguous";
|
|
15863
|
+
entry: null;
|
|
15864
|
+
decision: "block";
|
|
15865
|
+
};
|
|
15866
|
+
|
|
15867
|
+
/** Resolve one immutable attempt snapshot with exact-over-wildcard precedence. */
|
|
15868
|
+
export function resolveConnectorActionPolicy(
|
|
15869
|
+
snapshot: readonly ConnectorActionPolicySnapshotEntry[],
|
|
15870
|
+
input: { connectionId: string; serverId: string; toolName: string; actionName: string },
|
|
15871
|
+
): ResolvedConnectorActionPolicy {
|
|
15872
|
+
const candidates = snapshot
|
|
15873
|
+
.filter(
|
|
15874
|
+
(entry) =>
|
|
15875
|
+
entry.connectionId === input.connectionId &&
|
|
15876
|
+
(entry.serverId === input.serverId || entry.serverId === "*") &&
|
|
15877
|
+
(entry.toolName === input.toolName || entry.toolName === "*") &&
|
|
15878
|
+
(entry.actionName === input.actionName || entry.actionName === "*"),
|
|
15879
|
+
)
|
|
15880
|
+
.map((entry) => ({
|
|
15881
|
+
entry,
|
|
15882
|
+
specificity:
|
|
15883
|
+
Number(entry.serverId !== "*") +
|
|
15884
|
+
Number(entry.toolName !== "*") +
|
|
15885
|
+
Number(entry.actionName !== "*"),
|
|
15886
|
+
}))
|
|
15887
|
+
.sort(
|
|
15888
|
+
(left, right) =>
|
|
15889
|
+
right.specificity - left.specificity || left.entry.id.localeCompare(right.entry.id),
|
|
15890
|
+
);
|
|
15891
|
+
const selected = candidates[0];
|
|
15892
|
+
if (!selected) return { managed: false };
|
|
15893
|
+
if (candidates[1]?.specificity === selected.specificity) {
|
|
15894
|
+
return { managed: true, source: "ambiguous", entry: null, decision: "block" };
|
|
15895
|
+
}
|
|
15896
|
+
return { managed: true, source: "explicit", entry: selected.entry };
|
|
15897
|
+
}
|
|
15898
|
+
|
|
15899
|
+
function connectorActionAuditMetadata(
|
|
15900
|
+
row: typeof schema.connectorActionRequests.$inferSelect,
|
|
15901
|
+
extra: Record<string, unknown> = {},
|
|
15902
|
+
): Record<string, unknown> {
|
|
15903
|
+
return {
|
|
15904
|
+
requestId: row.id,
|
|
15905
|
+
sessionId: row.sessionId,
|
|
15906
|
+
turnId: row.turnId,
|
|
15907
|
+
attemptId: row.creationAttemptId,
|
|
15908
|
+
creationExecutionGeneration: row.creationExecutionGeneration,
|
|
15909
|
+
executionAttemptId: row.executionAttemptId,
|
|
15910
|
+
executionAttemptGeneration: row.executionAttemptGeneration,
|
|
15911
|
+
approvalId: row.approvalId,
|
|
15912
|
+
initiatorKind: row.initiatorKind,
|
|
15913
|
+
initiatorSubjectId: row.initiatorSubjectId,
|
|
15914
|
+
connectionId: row.connectionId,
|
|
15915
|
+
connectionVersion: row.connectionVersion,
|
|
15916
|
+
serverId: row.serverId,
|
|
15917
|
+
toolName: row.toolName,
|
|
15918
|
+
actionName: row.actionName,
|
|
15919
|
+
policyId: row.policyId,
|
|
15920
|
+
policyVersion: row.policyVersion,
|
|
15921
|
+
policySource: row.policySource,
|
|
15922
|
+
policyDecision: row.policyDecision,
|
|
15923
|
+
actionFingerprint: row.actionFingerprint,
|
|
15924
|
+
...extra,
|
|
15925
|
+
};
|
|
15926
|
+
}
|
|
15927
|
+
|
|
15928
|
+
async function insertConnectorActionAudit(
|
|
15929
|
+
db: Database,
|
|
15930
|
+
input: {
|
|
15931
|
+
row: typeof schema.connectorActionRequests.$inferSelect;
|
|
15932
|
+
action: string;
|
|
15933
|
+
subjectId: string;
|
|
15934
|
+
extra?: Record<string, unknown>;
|
|
15935
|
+
},
|
|
15936
|
+
): Promise<void> {
|
|
15937
|
+
await db.insert(schema.auditEvents).values({
|
|
15938
|
+
accountId: input.row.accountId,
|
|
15939
|
+
workspaceId: input.row.workspaceId,
|
|
15940
|
+
subjectId: input.subjectId,
|
|
15941
|
+
action: input.action,
|
|
15942
|
+
targetType: "connector_action_request",
|
|
15943
|
+
targetId: input.row.id,
|
|
15944
|
+
metadata: connectorActionAuditMetadata(input.row, input.extra),
|
|
15945
|
+
});
|
|
15946
|
+
}
|
|
15947
|
+
|
|
15948
|
+
function normalizedConnectorActionInvocation(
|
|
15949
|
+
identity: ConnectorActionAttemptIdentity,
|
|
15950
|
+
invocation: ConnectorActionInvocation,
|
|
15951
|
+
): {
|
|
15952
|
+
approvalId: string;
|
|
15953
|
+
connectionId: string | null;
|
|
15954
|
+
serverId: string;
|
|
15955
|
+
toolName: string;
|
|
15956
|
+
policyActionSelector: string;
|
|
15957
|
+
arguments: unknown;
|
|
15958
|
+
} {
|
|
15959
|
+
const approvalId = boundedConnectorActionText(
|
|
15960
|
+
invocation.approvalId,
|
|
15961
|
+
"connector approval id",
|
|
15962
|
+
CONNECTOR_ACTION_APPROVAL_ID_MAX,
|
|
15963
|
+
);
|
|
15964
|
+
const serverId = boundedConnectorActionText(
|
|
15965
|
+
invocation.serverId,
|
|
15966
|
+
"connector server id",
|
|
15967
|
+
CONNECTOR_ACTION_SERVER_ID_MAX,
|
|
15968
|
+
);
|
|
15969
|
+
const toolName = boundedConnectorActionText(
|
|
15970
|
+
invocation.toolName,
|
|
15971
|
+
"connector tool name",
|
|
15972
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
15973
|
+
);
|
|
15974
|
+
const policyActionSelector = connectorActionPolicySelector(toolName, invocation.arguments);
|
|
15975
|
+
const connectionId = invocation.connectionId?.trim()
|
|
15976
|
+
? boundedConnectorActionText(
|
|
15977
|
+
invocation.connectionId,
|
|
15978
|
+
"connector connection id",
|
|
15979
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX,
|
|
15980
|
+
)
|
|
15981
|
+
: null;
|
|
15982
|
+
return {
|
|
15983
|
+
approvalId,
|
|
15984
|
+
connectionId,
|
|
15985
|
+
serverId,
|
|
15986
|
+
toolName,
|
|
15987
|
+
policyActionSelector,
|
|
15988
|
+
arguments: invocation.arguments,
|
|
15989
|
+
};
|
|
15990
|
+
}
|
|
15991
|
+
|
|
15992
|
+
function durableConnectorActionInvocation(
|
|
15993
|
+
identity: ConnectorActionAttemptIdentity,
|
|
15994
|
+
invocation: ReturnType<typeof normalizedConnectorActionInvocation> & { connectionId: string },
|
|
15995
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>,
|
|
15996
|
+
): {
|
|
15997
|
+
approvalId: string;
|
|
15998
|
+
connectionId: string;
|
|
15999
|
+
serverId: string;
|
|
16000
|
+
toolName: string;
|
|
16001
|
+
actionName: string;
|
|
16002
|
+
actionFingerprint: string;
|
|
16003
|
+
} {
|
|
16004
|
+
const actionName = connectorActionEvidenceName(resolved);
|
|
16005
|
+
return {
|
|
16006
|
+
approvalId: invocation.approvalId,
|
|
16007
|
+
connectionId: invocation.connectionId,
|
|
16008
|
+
serverId: invocation.serverId,
|
|
16009
|
+
toolName: invocation.toolName,
|
|
16010
|
+
actionName,
|
|
16011
|
+
actionFingerprint: connectorActionFingerprint({
|
|
16012
|
+
workspaceId: identity.workspaceId,
|
|
16013
|
+
connectionId: invocation.connectionId,
|
|
16014
|
+
serverId: invocation.serverId,
|
|
16015
|
+
toolName: invocation.toolName,
|
|
16016
|
+
actionName,
|
|
16017
|
+
arguments: invocation.arguments,
|
|
16018
|
+
}),
|
|
16019
|
+
};
|
|
16020
|
+
}
|
|
16021
|
+
|
|
16022
|
+
async function connectorActionAttemptSnapshot(
|
|
16023
|
+
db: Database,
|
|
16024
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16025
|
+
): Promise<ConnectorActionPolicySnapshotEntry[]> {
|
|
16026
|
+
const [attempt] = await db
|
|
16027
|
+
.select({
|
|
16028
|
+
accountId: schema.sessionTurnAttempts.accountId,
|
|
16029
|
+
sessionId: schema.sessionTurnAttempts.sessionId,
|
|
16030
|
+
turnId: schema.sessionTurnAttempts.turnId,
|
|
16031
|
+
executionGeneration: schema.sessionTurnAttempts.executionGeneration,
|
|
16032
|
+
state: schema.sessionTurnAttempts.state,
|
|
16033
|
+
connectorActionPolicies: schema.sessionTurnAttempts.connectorActionPolicies,
|
|
16034
|
+
})
|
|
16035
|
+
.from(schema.sessionTurnAttempts)
|
|
16036
|
+
.where(
|
|
16037
|
+
and(
|
|
16038
|
+
eq(schema.sessionTurnAttempts.workspaceId, identity.workspaceId),
|
|
16039
|
+
eq(schema.sessionTurnAttempts.id, identity.attemptId),
|
|
16040
|
+
),
|
|
16041
|
+
)
|
|
16042
|
+
.limit(1);
|
|
16043
|
+
if (
|
|
16044
|
+
!attempt ||
|
|
16045
|
+
attempt.accountId !== identity.accountId ||
|
|
16046
|
+
attempt.sessionId !== identity.sessionId ||
|
|
16047
|
+
attempt.turnId !== identity.turnId ||
|
|
16048
|
+
attempt.executionGeneration !== identity.executionGeneration ||
|
|
16049
|
+
!["claimed", "running"].includes(attempt.state)
|
|
16050
|
+
) {
|
|
16051
|
+
throw new Error(`connector action attempt ownership is unavailable: ${identity.attemptId}`);
|
|
16052
|
+
}
|
|
16053
|
+
if (attempt.connectorActionPolicies.length > CONNECTOR_ACTION_POLICY_SNAPSHOT_MAX) {
|
|
16054
|
+
throw new Error("connector action policy snapshot exceeds the runtime bound");
|
|
16055
|
+
}
|
|
16056
|
+
return attempt.connectorActionPolicies;
|
|
16057
|
+
}
|
|
16058
|
+
|
|
16059
|
+
function connectorActionRequestMatches(
|
|
16060
|
+
row: typeof schema.connectorActionRequests.$inferSelect,
|
|
16061
|
+
input: {
|
|
16062
|
+
identity: ConnectorActionAttemptIdentity;
|
|
16063
|
+
invocation: ReturnType<typeof durableConnectorActionInvocation>;
|
|
16064
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>;
|
|
16065
|
+
},
|
|
16066
|
+
): boolean {
|
|
16067
|
+
const entry = input.resolved.entry;
|
|
16068
|
+
return (
|
|
16069
|
+
row.accountId === input.identity.accountId &&
|
|
16070
|
+
row.workspaceId === input.identity.workspaceId &&
|
|
16071
|
+
row.sessionId === input.identity.sessionId &&
|
|
16072
|
+
row.turnId === input.identity.turnId &&
|
|
16073
|
+
row.creationAttemptId === input.identity.attemptId &&
|
|
16074
|
+
row.creationExecutionGeneration === input.identity.executionGeneration &&
|
|
16075
|
+
row.approvalId === input.invocation.approvalId &&
|
|
16076
|
+
row.initiatorKind === input.identity.initiator.kind &&
|
|
16077
|
+
row.initiatorSubjectId === input.identity.initiator.subjectId &&
|
|
16078
|
+
row.connectionId === input.invocation.connectionId &&
|
|
16079
|
+
row.serverId === input.invocation.serverId &&
|
|
16080
|
+
row.toolName === input.invocation.toolName &&
|
|
16081
|
+
row.actionName === input.invocation.actionName &&
|
|
16082
|
+
row.policyId === (entry?.id ?? null) &&
|
|
16083
|
+
row.policyVersion === (entry?.version ?? null) &&
|
|
16084
|
+
row.policySource === input.resolved.source &&
|
|
16085
|
+
row.policyDecision === (entry?.policy ?? "block") &&
|
|
16086
|
+
row.actionFingerprint === input.invocation.actionFingerprint
|
|
16087
|
+
);
|
|
16088
|
+
}
|
|
16089
|
+
|
|
16090
|
+
function connectorActionRequestMatchesLogicalCall(
|
|
16091
|
+
row: typeof schema.connectorActionRequests.$inferSelect,
|
|
16092
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16093
|
+
invocation: ReturnType<typeof normalizedConnectorActionInvocation>,
|
|
16094
|
+
): boolean {
|
|
16095
|
+
if (!invocation.connectionId) return false;
|
|
16096
|
+
return (
|
|
16097
|
+
row.accountId === identity.accountId &&
|
|
16098
|
+
row.workspaceId === identity.workspaceId &&
|
|
16099
|
+
row.sessionId === identity.sessionId &&
|
|
16100
|
+
row.turnId === identity.turnId &&
|
|
16101
|
+
row.approvalId === invocation.approvalId &&
|
|
16102
|
+
row.initiatorKind === identity.initiator.kind &&
|
|
16103
|
+
row.initiatorSubjectId === identity.initiator.subjectId &&
|
|
16104
|
+
row.connectionId === invocation.connectionId &&
|
|
16105
|
+
row.serverId === invocation.serverId &&
|
|
16106
|
+
row.toolName === invocation.toolName &&
|
|
16107
|
+
row.actionFingerprint ===
|
|
16108
|
+
connectorActionFingerprint({
|
|
16109
|
+
workspaceId: identity.workspaceId,
|
|
16110
|
+
connectionId: invocation.connectionId,
|
|
16111
|
+
serverId: invocation.serverId,
|
|
16112
|
+
toolName: invocation.toolName,
|
|
16113
|
+
actionName: row.actionName,
|
|
16114
|
+
arguments: invocation.arguments,
|
|
16115
|
+
})
|
|
16116
|
+
);
|
|
16117
|
+
}
|
|
16118
|
+
|
|
16119
|
+
async function insertConnectorActionRequest(
|
|
16120
|
+
db: Database,
|
|
16121
|
+
input: {
|
|
16122
|
+
identity: ConnectorActionAttemptIdentity;
|
|
16123
|
+
invocation: ReturnType<typeof durableConnectorActionInvocation>;
|
|
16124
|
+
resolved: Exclude<ResolvedConnectorActionPolicy, { managed: false }>;
|
|
16125
|
+
status: "pending" | "blocked" | "executing";
|
|
16126
|
+
},
|
|
16127
|
+
): Promise<{ row: typeof schema.connectorActionRequests.$inferSelect; inserted: boolean }> {
|
|
16128
|
+
const entry = input.resolved.entry;
|
|
16129
|
+
const [inserted] = await db
|
|
16130
|
+
.insert(schema.connectorActionRequests)
|
|
16131
|
+
.values({
|
|
16132
|
+
accountId: input.identity.accountId,
|
|
16133
|
+
workspaceId: input.identity.workspaceId,
|
|
16134
|
+
sessionId: input.identity.sessionId,
|
|
16135
|
+
turnId: input.identity.turnId,
|
|
16136
|
+
creationAttemptId: input.identity.attemptId,
|
|
16137
|
+
creationExecutionGeneration: input.identity.executionGeneration,
|
|
16138
|
+
approvalId: input.invocation.approvalId,
|
|
16139
|
+
initiatorKind: input.identity.initiator.kind,
|
|
16140
|
+
initiatorSubjectId: input.identity.initiator.subjectId,
|
|
16141
|
+
connectionId: input.invocation.connectionId!,
|
|
16142
|
+
serverId: input.invocation.serverId,
|
|
16143
|
+
toolName: input.invocation.toolName,
|
|
16144
|
+
actionName: input.invocation.actionName,
|
|
16145
|
+
policyId: entry?.id ?? null,
|
|
16146
|
+
policyVersion: entry?.version ?? null,
|
|
16147
|
+
policySource: input.resolved.source,
|
|
16148
|
+
policyDecision: entry?.policy ?? "block",
|
|
16149
|
+
actionFingerprint: input.invocation.actionFingerprint!,
|
|
16150
|
+
status: input.status,
|
|
16151
|
+
...(input.status === "executing"
|
|
16152
|
+
? {
|
|
16153
|
+
executionAttemptId: input.identity.attemptId,
|
|
16154
|
+
executionAttemptGeneration: input.identity.executionGeneration,
|
|
16155
|
+
executionStartedAt: new Date(),
|
|
16156
|
+
}
|
|
16157
|
+
: {}),
|
|
16158
|
+
})
|
|
16159
|
+
.onConflictDoNothing({
|
|
16160
|
+
target: [
|
|
16161
|
+
schema.connectorActionRequests.workspaceId,
|
|
16162
|
+
schema.connectorActionRequests.sessionId,
|
|
16163
|
+
schema.connectorActionRequests.turnId,
|
|
16164
|
+
schema.connectorActionRequests.approvalId,
|
|
16165
|
+
],
|
|
16166
|
+
})
|
|
16167
|
+
.returning();
|
|
16168
|
+
if (inserted) return { row: inserted, inserted: true };
|
|
16169
|
+
const [existing] = await db
|
|
16170
|
+
.select()
|
|
16171
|
+
.from(schema.connectorActionRequests)
|
|
16172
|
+
.where(
|
|
16173
|
+
and(
|
|
16174
|
+
eq(schema.connectorActionRequests.workspaceId, input.identity.workspaceId),
|
|
16175
|
+
eq(schema.connectorActionRequests.sessionId, input.identity.sessionId),
|
|
16176
|
+
eq(schema.connectorActionRequests.turnId, input.identity.turnId),
|
|
16177
|
+
eq(schema.connectorActionRequests.approvalId, input.invocation.approvalId),
|
|
16178
|
+
),
|
|
16179
|
+
)
|
|
16180
|
+
.for("update")
|
|
16181
|
+
.limit(1);
|
|
16182
|
+
if (!existing || !connectorActionRequestMatches(existing, input)) {
|
|
16183
|
+
throw new Error("connector action approval id conflicts with different immutable inputs");
|
|
16184
|
+
}
|
|
16185
|
+
return { row: existing, inserted: false };
|
|
16186
|
+
}
|
|
16187
|
+
|
|
16188
|
+
export async function upsertConnectorActionPolicy(
|
|
16189
|
+
db: Database,
|
|
16190
|
+
input: {
|
|
16191
|
+
accountId: string;
|
|
16192
|
+
workspaceId: string;
|
|
16193
|
+
subjectId: string;
|
|
16194
|
+
connectionId: string;
|
|
16195
|
+
serverId: string;
|
|
16196
|
+
toolName: string;
|
|
16197
|
+
actionName: string;
|
|
16198
|
+
policy: ConnectorActionPolicyDecision;
|
|
16199
|
+
},
|
|
16200
|
+
): Promise<{ policy: typeof schema.connectorActionPolicies.$inferSelect; changed: boolean }> {
|
|
16201
|
+
const scope = {
|
|
16202
|
+
connectionId: boundedConnectorActionText(
|
|
16203
|
+
input.connectionId,
|
|
16204
|
+
"connector connection id",
|
|
16205
|
+
CONNECTOR_ACTION_CONNECTION_ID_MAX,
|
|
16206
|
+
),
|
|
16207
|
+
serverId: boundedConnectorActionText(
|
|
16208
|
+
input.serverId,
|
|
16209
|
+
"connector server id",
|
|
16210
|
+
CONNECTOR_ACTION_SERVER_ID_MAX,
|
|
16211
|
+
),
|
|
16212
|
+
toolName: boundedConnectorActionText(
|
|
16213
|
+
input.toolName,
|
|
16214
|
+
"connector tool name",
|
|
16215
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
16216
|
+
),
|
|
16217
|
+
actionName: boundedConnectorActionText(
|
|
16218
|
+
input.actionName,
|
|
16219
|
+
"connector action name",
|
|
16220
|
+
CONNECTOR_ACTION_NAME_MAX,
|
|
16221
|
+
),
|
|
16222
|
+
};
|
|
16223
|
+
const subjectId = boundedConnectorActionText(input.subjectId, "policy actor", 1024);
|
|
16224
|
+
return await withRlsContext(
|
|
16225
|
+
db,
|
|
16226
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16227
|
+
async (scopedDb) =>
|
|
16228
|
+
await scopedDb.transaction(async (tx) => {
|
|
16229
|
+
await assertWorkspaceAccountPairInScope(tx, input.accountId, input.workspaceId);
|
|
16230
|
+
const [existing] = await tx
|
|
16231
|
+
.select()
|
|
16232
|
+
.from(schema.connectorActionPolicies)
|
|
16233
|
+
.where(
|
|
16234
|
+
and(
|
|
16235
|
+
eq(schema.connectorActionPolicies.workspaceId, input.workspaceId),
|
|
16236
|
+
eq(schema.connectorActionPolicies.connectionId, scope.connectionId),
|
|
16237
|
+
eq(schema.connectorActionPolicies.serverId, scope.serverId),
|
|
16238
|
+
eq(schema.connectorActionPolicies.toolName, scope.toolName),
|
|
16239
|
+
eq(schema.connectorActionPolicies.actionName, scope.actionName),
|
|
16240
|
+
),
|
|
16241
|
+
)
|
|
16242
|
+
.for("update")
|
|
16243
|
+
.limit(1);
|
|
16244
|
+
if (existing?.policy === input.policy) return { policy: existing, changed: false };
|
|
16245
|
+
const now = new Date();
|
|
16246
|
+
const [row] = existing
|
|
16247
|
+
? await tx
|
|
16248
|
+
.update(schema.connectorActionPolicies)
|
|
16249
|
+
.set({
|
|
16250
|
+
policy: input.policy,
|
|
16251
|
+
version: existing.version + 1,
|
|
16252
|
+
updatedBySubjectId: subjectId,
|
|
16253
|
+
updatedAt: now,
|
|
16254
|
+
})
|
|
16255
|
+
.where(eq(schema.connectorActionPolicies.id, existing.id))
|
|
16256
|
+
.returning()
|
|
16257
|
+
: await tx
|
|
16258
|
+
.insert(schema.connectorActionPolicies)
|
|
16259
|
+
.values({
|
|
16260
|
+
accountId: input.accountId,
|
|
16261
|
+
workspaceId: input.workspaceId,
|
|
16262
|
+
...scope,
|
|
16263
|
+
policy: input.policy,
|
|
16264
|
+
createdBySubjectId: subjectId,
|
|
16265
|
+
updatedBySubjectId: subjectId,
|
|
16266
|
+
})
|
|
16267
|
+
.returning();
|
|
16268
|
+
if (!row) throw new Error("Failed to persist connector action policy");
|
|
16269
|
+
await tx.insert(schema.auditEvents).values({
|
|
16270
|
+
accountId: input.accountId,
|
|
16271
|
+
workspaceId: input.workspaceId,
|
|
16272
|
+
subjectId,
|
|
16273
|
+
action: "connector.action.policy_changed",
|
|
16274
|
+
targetType: "connector_action_policy",
|
|
16275
|
+
targetId: row.id,
|
|
16276
|
+
metadata: {
|
|
16277
|
+
connectionId: row.connectionId,
|
|
16278
|
+
serverId: row.serverId,
|
|
16279
|
+
toolName: row.toolName,
|
|
16280
|
+
actionName: row.actionName,
|
|
16281
|
+
policy: row.policy,
|
|
16282
|
+
version: row.version,
|
|
16283
|
+
previousPolicy: existing?.policy ?? null,
|
|
16284
|
+
previousVersion: existing?.version ?? null,
|
|
16285
|
+
},
|
|
16286
|
+
});
|
|
16287
|
+
return { policy: row, changed: true };
|
|
16288
|
+
}),
|
|
16289
|
+
);
|
|
16290
|
+
}
|
|
16291
|
+
|
|
16292
|
+
export async function prepareConnectorActionApproval(
|
|
16293
|
+
db: Database,
|
|
16294
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16295
|
+
invocation: ConnectorActionInvocation,
|
|
16296
|
+
): Promise<PrepareConnectorActionApprovalResult> {
|
|
16297
|
+
const normalized = normalizedConnectorActionInvocation(identity, invocation);
|
|
16298
|
+
if (!normalized.connectionId) {
|
|
16299
|
+
return { managed: false, decision: "unmanaged" };
|
|
16300
|
+
}
|
|
16301
|
+
return await withRlsContext(
|
|
16302
|
+
db,
|
|
16303
|
+
{ accountId: identity.accountId, workspaceId: identity.workspaceId },
|
|
16304
|
+
async (scopedDb) =>
|
|
16305
|
+
await scopedDb.transaction(async (tx) => {
|
|
16306
|
+
const snapshot = await connectorActionAttemptSnapshot(tx as unknown as Database, identity);
|
|
16307
|
+
const resolved = resolveConnectorActionPolicy(snapshot, {
|
|
16308
|
+
connectionId: normalized.connectionId!,
|
|
16309
|
+
serverId: normalized.serverId,
|
|
16310
|
+
toolName: normalized.toolName,
|
|
16311
|
+
actionName: normalized.policyActionSelector,
|
|
16312
|
+
});
|
|
16313
|
+
if (!resolved.managed) return { managed: false, decision: "unmanaged" } as const;
|
|
16314
|
+
const durable = durableConnectorActionInvocation(
|
|
16315
|
+
identity,
|
|
16316
|
+
{ ...normalized, connectionId: normalized.connectionId! },
|
|
16317
|
+
resolved,
|
|
16318
|
+
);
|
|
16319
|
+
const decision = resolved.entry?.policy ?? "block";
|
|
16320
|
+
if (decision === "allow") {
|
|
16321
|
+
return {
|
|
16322
|
+
managed: true,
|
|
16323
|
+
decision,
|
|
16324
|
+
actionFingerprint: durable.actionFingerprint,
|
|
16325
|
+
} as const;
|
|
16326
|
+
}
|
|
16327
|
+
const { row, inserted } = await insertConnectorActionRequest(tx as unknown as Database, {
|
|
16328
|
+
identity,
|
|
16329
|
+
invocation: durable,
|
|
16330
|
+
resolved,
|
|
16331
|
+
status: decision === "ask" ? "pending" : "blocked",
|
|
16332
|
+
});
|
|
16333
|
+
if (inserted) {
|
|
16334
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16335
|
+
row,
|
|
16336
|
+
action:
|
|
16337
|
+
decision === "ask"
|
|
16338
|
+
? "connector.action.approval_requested"
|
|
16339
|
+
: "connector.action.blocked",
|
|
16340
|
+
subjectId: identity.initiator.subjectId,
|
|
16341
|
+
extra: { outcome: decision },
|
|
16342
|
+
});
|
|
16343
|
+
}
|
|
16344
|
+
return {
|
|
16345
|
+
managed: true,
|
|
16346
|
+
decision,
|
|
16347
|
+
requestId: row.id,
|
|
16348
|
+
actionFingerprint: row.actionFingerprint,
|
|
16349
|
+
} as const;
|
|
16350
|
+
}),
|
|
16351
|
+
);
|
|
16352
|
+
}
|
|
16353
|
+
|
|
16354
|
+
export async function beginConnectorActionExecution(
|
|
16355
|
+
db: Database,
|
|
16356
|
+
identity: ConnectorActionAttemptIdentity,
|
|
16357
|
+
invocation: ConnectorActionInvocation,
|
|
16358
|
+
): Promise<BeginConnectorActionExecutionResult> {
|
|
16359
|
+
const normalized = normalizedConnectorActionInvocation(identity, invocation);
|
|
16360
|
+
if (!normalized.connectionId) {
|
|
16361
|
+
return { allowed: true, managed: false };
|
|
16362
|
+
}
|
|
16363
|
+
return await withRlsContext(
|
|
16364
|
+
db,
|
|
16365
|
+
{ accountId: identity.accountId, workspaceId: identity.workspaceId },
|
|
16366
|
+
async (scopedDb) =>
|
|
16367
|
+
await scopedDb.transaction(async (tx) => {
|
|
16368
|
+
const snapshot = await connectorActionAttemptSnapshot(tx as unknown as Database, identity);
|
|
16369
|
+
const [existing] = await tx
|
|
16370
|
+
.select()
|
|
16371
|
+
.from(schema.connectorActionRequests)
|
|
16372
|
+
.where(
|
|
16373
|
+
and(
|
|
16374
|
+
eq(schema.connectorActionRequests.workspaceId, identity.workspaceId),
|
|
16375
|
+
eq(schema.connectorActionRequests.sessionId, identity.sessionId),
|
|
16376
|
+
eq(schema.connectorActionRequests.turnId, identity.turnId),
|
|
16377
|
+
eq(schema.connectorActionRequests.approvalId, normalized.approvalId),
|
|
16378
|
+
),
|
|
16379
|
+
)
|
|
16380
|
+
.for("update")
|
|
16381
|
+
.limit(1);
|
|
16382
|
+
if (existing && !connectorActionRequestMatchesLogicalCall(existing, identity, normalized)) {
|
|
16383
|
+
throw new Error("connector action approval id conflicts with different immutable inputs");
|
|
16384
|
+
}
|
|
16385
|
+
let row = existing;
|
|
16386
|
+
let inserted = false;
|
|
16387
|
+
if (!row) {
|
|
16388
|
+
const resolved = resolveConnectorActionPolicy(snapshot, {
|
|
16389
|
+
connectionId: normalized.connectionId!,
|
|
16390
|
+
serverId: normalized.serverId,
|
|
16391
|
+
toolName: normalized.toolName,
|
|
16392
|
+
actionName: normalized.policyActionSelector,
|
|
16393
|
+
});
|
|
16394
|
+
if (!resolved.managed) return { allowed: true, managed: false } as const;
|
|
16395
|
+
const durable = durableConnectorActionInvocation(
|
|
16396
|
+
identity,
|
|
16397
|
+
{ ...normalized, connectionId: normalized.connectionId! },
|
|
16398
|
+
resolved,
|
|
16399
|
+
);
|
|
16400
|
+
const decision = resolved.entry?.policy ?? "block";
|
|
16401
|
+
const created = await insertConnectorActionRequest(tx as unknown as Database, {
|
|
16402
|
+
identity,
|
|
16403
|
+
invocation: durable,
|
|
16404
|
+
resolved,
|
|
16405
|
+
status: decision === "ask" ? "pending" : decision === "block" ? "blocked" : "executing",
|
|
16406
|
+
});
|
|
16407
|
+
row = created.row;
|
|
16408
|
+
inserted = created.inserted;
|
|
16409
|
+
if (inserted) {
|
|
16410
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16411
|
+
row,
|
|
16412
|
+
action:
|
|
16413
|
+
decision === "ask"
|
|
16414
|
+
? "connector.action.approval_requested"
|
|
16415
|
+
: decision === "block"
|
|
16416
|
+
? "connector.action.blocked"
|
|
16417
|
+
: "connector.action.execution_started",
|
|
16418
|
+
subjectId: identity.initiator.subjectId,
|
|
16419
|
+
extra: { outcome: decision === "allow" ? "started" : decision },
|
|
16420
|
+
});
|
|
16421
|
+
}
|
|
16422
|
+
}
|
|
16423
|
+
if (row.status === "approved") {
|
|
16424
|
+
const [executing] = await tx
|
|
16425
|
+
.update(schema.connectorActionRequests)
|
|
16426
|
+
.set({
|
|
16427
|
+
status: "executing",
|
|
16428
|
+
executionAttemptId: identity.attemptId,
|
|
16429
|
+
executionAttemptGeneration: identity.executionGeneration,
|
|
16430
|
+
executionStartedAt: new Date(),
|
|
16431
|
+
updatedAt: new Date(),
|
|
16432
|
+
})
|
|
16433
|
+
.where(eq(schema.connectorActionRequests.id, row.id))
|
|
16434
|
+
.returning();
|
|
16435
|
+
if (!executing) throw new Error("Approved connector action request disappeared");
|
|
16436
|
+
row = executing;
|
|
16437
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16438
|
+
row,
|
|
16439
|
+
action: "connector.action.execution_started",
|
|
16440
|
+
subjectId: identity.initiator.subjectId,
|
|
16441
|
+
extra: { outcome: "started" },
|
|
16442
|
+
});
|
|
16443
|
+
return {
|
|
16444
|
+
allowed: true,
|
|
16445
|
+
managed: true,
|
|
16446
|
+
requestId: row.id,
|
|
16447
|
+
actionFingerprint: row.actionFingerprint,
|
|
16448
|
+
} as const;
|
|
16449
|
+
}
|
|
16450
|
+
if (row.status === "executing") {
|
|
16451
|
+
if (inserted) {
|
|
16452
|
+
return {
|
|
16453
|
+
allowed: true,
|
|
16454
|
+
managed: true,
|
|
16455
|
+
requestId: row.id,
|
|
16456
|
+
actionFingerprint: row.actionFingerprint,
|
|
16457
|
+
} as const;
|
|
16458
|
+
}
|
|
16459
|
+
const [uncertain] = await tx
|
|
16460
|
+
.update(schema.connectorActionRequests)
|
|
16461
|
+
.set({
|
|
16462
|
+
status: "uncertain",
|
|
16463
|
+
outcome: "retry_after_execution_started",
|
|
16464
|
+
executionFinishedAt: new Date(),
|
|
16465
|
+
updatedAt: new Date(),
|
|
16466
|
+
})
|
|
16467
|
+
.where(eq(schema.connectorActionRequests.id, row.id))
|
|
16468
|
+
.returning();
|
|
16469
|
+
if (!uncertain) throw new Error("Executing connector action request disappeared");
|
|
16470
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16471
|
+
row: uncertain,
|
|
16472
|
+
action: "connector.action.execution_uncertain",
|
|
16473
|
+
subjectId: identity.initiator.subjectId,
|
|
16474
|
+
extra: { outcome: "retry_denied" },
|
|
16475
|
+
});
|
|
16476
|
+
return {
|
|
16477
|
+
allowed: false,
|
|
16478
|
+
managed: true,
|
|
16479
|
+
reason: "uncertain_retry",
|
|
16480
|
+
requestId: uncertain.id,
|
|
16481
|
+
actionFingerprint: uncertain.actionFingerprint,
|
|
16482
|
+
} as const;
|
|
16483
|
+
}
|
|
16484
|
+
const reason =
|
|
16485
|
+
row.status === "pending"
|
|
16486
|
+
? "approval_required"
|
|
16487
|
+
: row.status === "rejected"
|
|
16488
|
+
? "rejected"
|
|
16489
|
+
: row.status === "blocked"
|
|
16490
|
+
? "blocked"
|
|
16491
|
+
: "already_executed";
|
|
16492
|
+
return {
|
|
16493
|
+
allowed: false,
|
|
16494
|
+
managed: true,
|
|
16495
|
+
reason,
|
|
16496
|
+
requestId: row.id,
|
|
16497
|
+
actionFingerprint: row.actionFingerprint,
|
|
16498
|
+
} as const;
|
|
16499
|
+
}),
|
|
16500
|
+
);
|
|
16501
|
+
}
|
|
16502
|
+
|
|
16503
|
+
export async function completeConnectorActionExecution(
|
|
16504
|
+
db: Database,
|
|
16505
|
+
input: {
|
|
16506
|
+
accountId: string;
|
|
16507
|
+
workspaceId: string;
|
|
16508
|
+
requestId: string;
|
|
16509
|
+
attemptId: string;
|
|
16510
|
+
outcome: "completed" | "uncertain";
|
|
16511
|
+
},
|
|
16512
|
+
): Promise<void> {
|
|
16513
|
+
await withRlsContext(
|
|
16514
|
+
db,
|
|
16515
|
+
{ accountId: input.accountId, workspaceId: input.workspaceId },
|
|
16516
|
+
async (scopedDb) =>
|
|
16517
|
+
await scopedDb.transaction(async (tx) => {
|
|
16518
|
+
const [existing] = await tx
|
|
16519
|
+
.select()
|
|
16520
|
+
.from(schema.connectorActionRequests)
|
|
16521
|
+
.where(
|
|
16522
|
+
and(
|
|
16523
|
+
eq(schema.connectorActionRequests.workspaceId, input.workspaceId),
|
|
16524
|
+
eq(schema.connectorActionRequests.id, input.requestId),
|
|
16525
|
+
eq(schema.connectorActionRequests.executionAttemptId, input.attemptId),
|
|
16526
|
+
),
|
|
16527
|
+
)
|
|
16528
|
+
.for("update")
|
|
16529
|
+
.limit(1);
|
|
16530
|
+
if (!existing) throw new Error("Connector action request not found for completion");
|
|
16531
|
+
if (existing.status === input.outcome) return;
|
|
16532
|
+
if (existing.status !== "executing") {
|
|
16533
|
+
throw new Error(`Connector action request cannot complete from ${existing.status}`);
|
|
16534
|
+
}
|
|
16535
|
+
const [row] = await tx
|
|
16536
|
+
.update(schema.connectorActionRequests)
|
|
16537
|
+
.set({
|
|
16538
|
+
status: input.outcome,
|
|
16539
|
+
outcome: input.outcome,
|
|
16540
|
+
executionFinishedAt: new Date(),
|
|
16541
|
+
updatedAt: new Date(),
|
|
16542
|
+
})
|
|
16543
|
+
.where(eq(schema.connectorActionRequests.id, existing.id))
|
|
16544
|
+
.returning();
|
|
16545
|
+
if (!row) throw new Error("Connector action request disappeared during completion");
|
|
16546
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
16547
|
+
row,
|
|
16548
|
+
action:
|
|
16549
|
+
input.outcome === "completed"
|
|
16550
|
+
? "connector.action.execution_completed"
|
|
16551
|
+
: "connector.action.execution_uncertain",
|
|
16552
|
+
subjectId: row.initiatorSubjectId,
|
|
16553
|
+
extra: { outcome: input.outcome },
|
|
16554
|
+
});
|
|
16555
|
+
}),
|
|
16556
|
+
);
|
|
16557
|
+
}
|
|
16558
|
+
|
|
15749
16559
|
type DeploymentDepthPolicy = NestedAgentDepthDeploymentPolicy;
|
|
15750
16560
|
|
|
15751
16561
|
/** Read the persisted deployment fallback; process configuration is not policy authority. */
|
|
@@ -36086,6 +36896,29 @@ export async function claimSessionWorkForAttempt(
|
|
|
36086
36896
|
const mcpApprovalPolicies: Record<string, SessionMcpApprovalPolicy> = Object.fromEntries(
|
|
36087
36897
|
policyRows.map((row) => [row.serverId, row.requireApproval ?? false]),
|
|
36088
36898
|
);
|
|
36899
|
+
const connectorPolicyRows = await tx
|
|
36900
|
+
.select({
|
|
36901
|
+
id: schema.connectorActionPolicies.id,
|
|
36902
|
+
connectionId: schema.connectorActionPolicies.connectionId,
|
|
36903
|
+
serverId: schema.connectorActionPolicies.serverId,
|
|
36904
|
+
toolName: schema.connectorActionPolicies.toolName,
|
|
36905
|
+
actionName: schema.connectorActionPolicies.actionName,
|
|
36906
|
+
policy: schema.connectorActionPolicies.policy,
|
|
36907
|
+
version: schema.connectorActionPolicies.version,
|
|
36908
|
+
})
|
|
36909
|
+
.from(schema.connectorActionPolicies)
|
|
36910
|
+
.where(eq(schema.connectorActionPolicies.workspaceId, workspaceId))
|
|
36911
|
+
.orderBy(
|
|
36912
|
+
asc(schema.connectorActionPolicies.connectionId),
|
|
36913
|
+
asc(schema.connectorActionPolicies.serverId),
|
|
36914
|
+
asc(schema.connectorActionPolicies.toolName),
|
|
36915
|
+
asc(schema.connectorActionPolicies.actionName),
|
|
36916
|
+
asc(schema.connectorActionPolicies.id),
|
|
36917
|
+
)
|
|
36918
|
+
.limit(2049);
|
|
36919
|
+
if (connectorPolicyRows.length > 2048) {
|
|
36920
|
+
throw new Error("Connector action policy snapshot exceeds the 2048-row bound");
|
|
36921
|
+
}
|
|
36089
36922
|
return await registerSessionTurnAttemptClaim(tx as unknown as Database, {
|
|
36090
36923
|
id: input.attemptId,
|
|
36091
36924
|
accountId: session.accountId,
|
|
@@ -36098,6 +36931,7 @@ export async function claimSessionWorkForAttempt(
|
|
|
36098
36931
|
temporalActivityId: input.dispatchId,
|
|
36099
36932
|
verifiedControlRevision: Number(workspaceControl.revision),
|
|
36100
36933
|
mcpApprovalPolicies,
|
|
36934
|
+
connectorActionPolicies: connectorPolicyRows,
|
|
36101
36935
|
});
|
|
36102
36936
|
};
|
|
36103
36937
|
if (session.activeTurnId !== null) {
|
|
@@ -41383,6 +42217,7 @@ export async function acceptSessionApprovalDecision(
|
|
|
41383
42217
|
accountId: string;
|
|
41384
42218
|
workspaceId: string;
|
|
41385
42219
|
sessionId: string;
|
|
42220
|
+
subjectId: string;
|
|
41386
42221
|
payload: Record<string, unknown>;
|
|
41387
42222
|
clientEventId?: string | null;
|
|
41388
42223
|
},
|
|
@@ -41521,6 +42356,41 @@ export async function acceptSessionApprovalDecision(
|
|
|
41521
42356
|
})
|
|
41522
42357
|
.returning();
|
|
41523
42358
|
if (!event) throw new Error("Failed to append approval decision");
|
|
42359
|
+
const approvalDecision = input.payload.decision;
|
|
42360
|
+
if (approvalDecision === "approve" || approvalDecision === "reject") {
|
|
42361
|
+
const [connectorRequest] = await tx
|
|
42362
|
+
.update(schema.connectorActionRequests)
|
|
42363
|
+
.set({
|
|
42364
|
+
status: approvalDecision === "approve" ? "approved" : "rejected",
|
|
42365
|
+
decision: approvalDecision,
|
|
42366
|
+
decisionBySubjectId: boundedConnectorActionText(
|
|
42367
|
+
input.subjectId,
|
|
42368
|
+
"approval actor",
|
|
42369
|
+
1024,
|
|
42370
|
+
),
|
|
42371
|
+
decisionEventId: event.id,
|
|
42372
|
+
decidedAt: event.occurredAt,
|
|
42373
|
+
updatedAt: new Date(),
|
|
42374
|
+
})
|
|
42375
|
+
.where(
|
|
42376
|
+
and(
|
|
42377
|
+
eq(schema.connectorActionRequests.workspaceId, input.workspaceId),
|
|
42378
|
+
eq(schema.connectorActionRequests.sessionId, input.sessionId),
|
|
42379
|
+
eq(schema.connectorActionRequests.turnId, turn.id),
|
|
42380
|
+
eq(schema.connectorActionRequests.approvalId, approvalId),
|
|
42381
|
+
eq(schema.connectorActionRequests.status, "pending"),
|
|
42382
|
+
),
|
|
42383
|
+
)
|
|
42384
|
+
.returning();
|
|
42385
|
+
if (connectorRequest) {
|
|
42386
|
+
await insertConnectorActionAudit(tx as unknown as Database, {
|
|
42387
|
+
row: connectorRequest,
|
|
42388
|
+
action: "connector.action.approval_decided",
|
|
42389
|
+
subjectId: input.subjectId,
|
|
42390
|
+
extra: { decision: approvalDecision },
|
|
42391
|
+
});
|
|
42392
|
+
}
|
|
42393
|
+
}
|
|
41524
42394
|
await tx
|
|
41525
42395
|
.update(schema.sessions)
|
|
41526
42396
|
.set({
|