@opengeni/core 3.0.1 → 4.0.0
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/README.md +1 -1
- package/dist/application/connect-operation.d.ts +3 -0
- package/dist/application/sandbox-recovery.d.ts +49 -0
- package/dist/{chunk-H7X52RI2.js → chunk-TFHSE7XR.js} +13 -2
- package/dist/chunk-TFHSE7XR.js.map +1 -0
- package/dist/dependencies.d.ts +1 -1
- package/dist/domain/capabilities.d.ts +2 -2
- package/dist/domain/connection-account-selection-error.d.ts +6 -0
- package/dist/domain/connector-tool-permissions.d.ts +27 -0
- package/dist/domain/editable-artifacts/agent-application.d.ts +8 -0
- package/dist/domain/environments.d.ts +3 -7
- package/dist/domain/insights-timing.d.ts +16 -0
- package/dist/domain/insights.d.ts +4 -1
- package/dist/domain/knowledge-files.d.ts +1 -1
- package/dist/domain/knowledge-preparation.d.ts +9 -0
- package/dist/domain/knowledge-preparation.test.d.ts +1 -0
- package/dist/domain/mcp-account-bindings.d.ts +19 -0
- package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
- package/dist/domain/organization-integration-catalog.d.ts +15 -0
- package/dist/domain/personal-connection-delegations.d.ts +19 -10
- package/dist/domain/pr-review.d.ts +56 -4
- package/dist/domain/scheduled-tasks.d.ts +8 -5
- package/dist/domain/session-file-access.d.ts +4 -0
- package/dist/domain/sessions.d.ts +8 -6
- package/dist/domain/skills.d.ts +29 -1
- package/dist/editable-artifacts.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +4868 -4803
- package/dist/index.js.map +1 -1
- package/dist/sandbox/routing.d.ts +2 -1
- package/dist/sandbox/runtime-settings.d.ts +1 -15
- package/package.json +11 -19
- package/src/application/composer-submit.ts +2 -1
- package/src/application/connect-operation.ts +32 -3
- package/src/application/sandbox-recovery.ts +55 -0
- package/src/application/user-resource-grants.ts +1 -16
- package/src/dependencies.ts +5 -1
- package/src/domain/capabilities.ts +169 -122
- package/src/domain/connection-account-selection-error.ts +9 -0
- package/src/domain/connector-tool-permissions.ts +305 -0
- package/src/domain/editable-artifacts/agent-application.ts +31 -2
- package/src/domain/environments.ts +4 -9
- package/src/domain/file-owner.ts +16 -1
- package/src/domain/insights-timing.ts +95 -0
- package/src/domain/insights.ts +59 -35
- package/src/domain/knowledge-files.ts +10 -3
- package/src/domain/knowledge-messages.ts +1 -0
- package/src/domain/knowledge-preparation.test.ts +251 -0
- package/src/domain/knowledge-preparation.ts +126 -0
- package/src/domain/mcp-account-bindings.ts +152 -0
- package/src/domain/native-mcp-connection-admission.ts +14 -0
- package/src/domain/organization-integration-catalog.ts +83 -0
- package/src/domain/personal-connection-delegations.ts +226 -177
- package/src/domain/pr-review.ts +22 -6
- package/src/domain/resources.ts +9 -2
- package/src/domain/scheduled-tasks.ts +200 -155
- package/src/domain/session-file-access.ts +47 -0
- package/src/domain/session-tool-policy.ts +12 -4
- package/src/domain/sessions.ts +390 -261
- package/src/domain/skills.ts +9 -0
- package/src/index.ts +10 -6
- package/src/rigs/index.ts +17 -17
- package/src/sandbox/routing.ts +18 -7
- package/src/sandbox/runtime-settings.ts +7 -91
- package/dist/application/host-mcp-owner.d.ts +0 -6
- package/dist/application/host-mcp-resolvers.d.ts +0 -13
- package/dist/chunk-5WZYFL45.js +0 -266
- package/dist/chunk-5WZYFL45.js.map +0 -1
- package/dist/chunk-H7X52RI2.js.map +0 -1
- package/dist/domain/connection-authority.d.ts +0 -97
- package/dist/domain/connection-authority.js +0 -169
- package/dist/domain/connection-authority.js.map +0 -1
- package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
- package/dist/domain/host-mcp-task-admission.d.ts +0 -18
- package/dist/domain/packs.d.ts +0 -59
- package/dist/domain/product-integration-pack.d.ts +0 -44
- package/dist/domain/product-integration-skill.gen.d.ts +0 -5
- package/dist/remote-mcp-credentials.d.ts +0 -23
- package/dist/remote-mcp-credentials.js +0 -11
- package/dist/remote-mcp-credentials.js.map +0 -1
- package/src/application/host-mcp-owner.ts +0 -41
- package/src/application/host-mcp-resolvers.ts +0 -84
- package/src/domain/connection-authority.ts +0 -329
- package/src/domain/host-mcp-authority-source-admission.ts +0 -25
- package/src/domain/host-mcp-task-admission.ts +0 -121
- package/src/domain/packs.ts +0 -699
- package/src/domain/product-integration-pack.ts +0 -42
- package/src/domain/product-integration-skill.gen.ts +0 -52
- package/src/remote-mcp-credentials.ts +0 -361
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ConnectionKind,
|
|
3
|
-
ConnectionStatus,
|
|
4
|
-
SessionTenancyVisibility,
|
|
5
|
-
UserResourceDelegation,
|
|
6
|
-
UserResourceGrantMode,
|
|
7
|
-
UserResourceGrantStatus,
|
|
8
|
-
} from "@opengeni/contracts";
|
|
9
|
-
import {
|
|
10
|
-
ConnectionAuthorityEnvelope,
|
|
11
|
-
ConnectionUseAttribution,
|
|
12
|
-
ConnectionUseAuthoritySnapshot,
|
|
13
|
-
type ConnectionUseAuthorizationResult,
|
|
14
|
-
type ConnectionAuthoritySelectionSource,
|
|
15
|
-
type ConnectionUseDenialReason,
|
|
16
|
-
type ConnectionUseSelectionSource,
|
|
17
|
-
} from "@opengeni/contracts/connection-authority";
|
|
18
|
-
|
|
19
|
-
export type ConnectionAuthorityCandidate = {
|
|
20
|
-
id: string;
|
|
21
|
-
organizationId: string;
|
|
22
|
-
workspaceId: string;
|
|
23
|
-
generation: number;
|
|
24
|
-
status: ConnectionStatus;
|
|
25
|
-
providerDomain: string;
|
|
26
|
-
kind: ConnectionKind;
|
|
27
|
-
subjectId: string | null;
|
|
28
|
-
ownerOrganizationMembershipId: string | null;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export type UserConnectionAuthorityCandidate = {
|
|
32
|
-
id: string;
|
|
33
|
-
organizationId: string;
|
|
34
|
-
resourceKind: "connection";
|
|
35
|
-
resourceId: string;
|
|
36
|
-
originWorkspaceId: string;
|
|
37
|
-
ownerSubjectId: string;
|
|
38
|
-
ownerOrganizationMembershipId: string;
|
|
39
|
-
ownerMembershipAuthorizationRevision: number;
|
|
40
|
-
generation: number;
|
|
41
|
-
status: "active" | "retained" | "revoked";
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
export class ConnectionAuthorityInvariantError extends Error {
|
|
45
|
-
constructor(readonly code: string) {
|
|
46
|
-
super(`connection authority invariant failed: ${code}`);
|
|
47
|
-
this.name = "ConnectionAuthorityInvariantError";
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function captureConnectionUseAuthority(input: {
|
|
52
|
-
organizationId: string;
|
|
53
|
-
targetWorkspaceId: string;
|
|
54
|
-
targetSessionId: string;
|
|
55
|
-
targetSessionVisibility: SessionTenancyVisibility;
|
|
56
|
-
targetSessionAuthorityEpoch: number;
|
|
57
|
-
acceptedWork:
|
|
58
|
-
| { kind: "turn"; turnId: string }
|
|
59
|
-
| {
|
|
60
|
-
kind: "scheduled_task";
|
|
61
|
-
taskId: string;
|
|
62
|
-
taskAuthorityRevision: number;
|
|
63
|
-
runId: string;
|
|
64
|
-
};
|
|
65
|
-
connection: ConnectionAuthorityCandidate;
|
|
66
|
-
authority?: unknown;
|
|
67
|
-
authorityWasOmitted?: boolean;
|
|
68
|
-
userAuthority?: UserConnectionAuthorityCandidate | null;
|
|
69
|
-
selectionSources: ConnectionUseSelectionSource[];
|
|
70
|
-
}): ConnectionUseAuthoritySnapshot {
|
|
71
|
-
const authority = ConnectionAuthorityEnvelope.parse(input.authority ?? {});
|
|
72
|
-
const connection = input.connection;
|
|
73
|
-
if (connection.organizationId !== input.organizationId) {
|
|
74
|
-
throw new ConnectionAuthorityInvariantError("tenant_mismatch");
|
|
75
|
-
}
|
|
76
|
-
if (connection.status !== "active") {
|
|
77
|
-
throw new ConnectionAuthorityInvariantError("connection_status_inactive");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
let authoritySource: ConnectionAuthoritySelectionSource;
|
|
81
|
-
let userDelegation: UserResourceDelegation | null = null;
|
|
82
|
-
if (authority.scope === "workspace") {
|
|
83
|
-
if (connection.subjectId !== null || connection.ownerOrganizationMembershipId !== null) {
|
|
84
|
-
throw new ConnectionAuthorityInvariantError("workspace_borrowed_personal_connection");
|
|
85
|
-
}
|
|
86
|
-
if (connection.workspaceId !== input.targetWorkspaceId) {
|
|
87
|
-
throw new ConnectionAuthorityInvariantError("workspace_connection_scope_mismatch");
|
|
88
|
-
}
|
|
89
|
-
authoritySource = input.authorityWasOmitted
|
|
90
|
-
? "legacy_workspace_omission"
|
|
91
|
-
: "explicit_workspace";
|
|
92
|
-
} else {
|
|
93
|
-
const delegation = authority.userDelegation;
|
|
94
|
-
const resourceAuthority = input.userAuthority;
|
|
95
|
-
if (!delegation || !resourceAuthority) {
|
|
96
|
-
throw new ConnectionAuthorityInvariantError("user_authority_missing");
|
|
97
|
-
}
|
|
98
|
-
if (!connection.subjectId || !connection.ownerOrganizationMembershipId) {
|
|
99
|
-
throw new ConnectionAuthorityInvariantError("personal_owner_missing");
|
|
100
|
-
}
|
|
101
|
-
if (
|
|
102
|
-
resourceAuthority.id !== delegation.authorityId ||
|
|
103
|
-
resourceAuthority.organizationId !== input.organizationId ||
|
|
104
|
-
resourceAuthority.resourceKind !== "connection" ||
|
|
105
|
-
resourceAuthority.resourceId !== connection.id ||
|
|
106
|
-
resourceAuthority.originWorkspaceId !== connection.workspaceId ||
|
|
107
|
-
resourceAuthority.ownerSubjectId !== connection.subjectId ||
|
|
108
|
-
resourceAuthority.ownerOrganizationMembershipId !==
|
|
109
|
-
connection.ownerOrganizationMembershipId ||
|
|
110
|
-
resourceAuthority.generation !== delegation.authorityGeneration ||
|
|
111
|
-
resourceAuthority.status !== "active"
|
|
112
|
-
) {
|
|
113
|
-
throw new ConnectionAuthorityInvariantError("user_authority_mismatch");
|
|
114
|
-
}
|
|
115
|
-
userDelegation = delegation;
|
|
116
|
-
authoritySource = "user_delegation";
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return ConnectionUseAuthoritySnapshot.parse({
|
|
120
|
-
organizationId: input.organizationId,
|
|
121
|
-
originWorkspaceId: connection.workspaceId,
|
|
122
|
-
targetWorkspaceId: input.targetWorkspaceId,
|
|
123
|
-
targetSessionId: input.targetSessionId,
|
|
124
|
-
targetSessionVisibility: input.targetSessionVisibility,
|
|
125
|
-
targetSessionAuthorityEpoch: input.targetSessionAuthorityEpoch,
|
|
126
|
-
acceptedWork: input.acceptedWork,
|
|
127
|
-
connectionId: connection.id,
|
|
128
|
-
connectionGeneration: connection.generation,
|
|
129
|
-
connectionStatus: "active",
|
|
130
|
-
providerDomain: connection.providerDomain.toLowerCase(),
|
|
131
|
-
connectionKind: connection.kind,
|
|
132
|
-
scope: authority.scope,
|
|
133
|
-
ownerSubjectId: connection.subjectId,
|
|
134
|
-
ownerOrganizationMembershipId: connection.ownerOrganizationMembershipId,
|
|
135
|
-
ownerMembershipAuthorizationRevision:
|
|
136
|
-
authority.scope === "user" ? input.userAuthority!.ownerMembershipAuthorizationRevision : null,
|
|
137
|
-
authoritySource,
|
|
138
|
-
selectionSources: input.selectionSources,
|
|
139
|
-
userDelegation,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export type LiveConnectionUseState = {
|
|
144
|
-
organizationId: string;
|
|
145
|
-
targetWorkspaceId: string;
|
|
146
|
-
targetWorkspaceAccessActive: boolean;
|
|
147
|
-
session: {
|
|
148
|
-
id: string;
|
|
149
|
-
organizationId: string;
|
|
150
|
-
workspaceId: string;
|
|
151
|
-
visibility: SessionTenancyVisibility;
|
|
152
|
-
authorityEpoch: number;
|
|
153
|
-
active: boolean;
|
|
154
|
-
};
|
|
155
|
-
connection: ConnectionAuthorityCandidate | null;
|
|
156
|
-
ownerMembership: {
|
|
157
|
-
id: string;
|
|
158
|
-
organizationId: string;
|
|
159
|
-
subjectId: string;
|
|
160
|
-
status: "provisioning" | "active" | "suspended" | "revoked";
|
|
161
|
-
authorizationRevision: number;
|
|
162
|
-
} | null;
|
|
163
|
-
userAuthority: UserConnectionAuthorityCandidate | null;
|
|
164
|
-
grant: {
|
|
165
|
-
id: string;
|
|
166
|
-
authorityId: string;
|
|
167
|
-
organizationId: string;
|
|
168
|
-
workspaceId: string;
|
|
169
|
-
sessionId: string | null;
|
|
170
|
-
action: string;
|
|
171
|
-
mode: UserResourceGrantMode;
|
|
172
|
-
context: SessionTenancyVisibility;
|
|
173
|
-
authorityEpoch: number | null;
|
|
174
|
-
generation: number;
|
|
175
|
-
status: UserResourceGrantStatus;
|
|
176
|
-
expiresAt: string | null;
|
|
177
|
-
consumed: boolean;
|
|
178
|
-
} | null;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
function denied(reason: ConnectionUseDenialReason): ConnectionUseAuthorizationResult {
|
|
182
|
-
return { status: "denied", reason };
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Revalidates every mutable fence immediately before provider use. Callers must
|
|
187
|
-
* resolve the live state in one DB authority boundary; this pure function does
|
|
188
|
-
* not cache, refresh, decrypt, or invoke a provider.
|
|
189
|
-
*/
|
|
190
|
-
export function revalidateConnectionUseAuthority(input: {
|
|
191
|
-
snapshot: ConnectionUseAuthoritySnapshot;
|
|
192
|
-
live: LiveConnectionUseState;
|
|
193
|
-
now?: Date;
|
|
194
|
-
}): ConnectionUseAuthorizationResult {
|
|
195
|
-
const snapshot = ConnectionUseAuthoritySnapshot.parse(input.snapshot);
|
|
196
|
-
const live = input.live;
|
|
197
|
-
const now = input.now ?? new Date();
|
|
198
|
-
if (
|
|
199
|
-
live.organizationId !== snapshot.organizationId ||
|
|
200
|
-
live.targetWorkspaceId !== snapshot.targetWorkspaceId
|
|
201
|
-
) {
|
|
202
|
-
return denied("tenant_mismatch");
|
|
203
|
-
}
|
|
204
|
-
if (!live.targetWorkspaceAccessActive) return denied("workspace_access_inactive");
|
|
205
|
-
if (!live.session.active) return denied("session_inactive");
|
|
206
|
-
if (
|
|
207
|
-
live.session.id !== snapshot.targetSessionId ||
|
|
208
|
-
live.session.organizationId !== snapshot.organizationId ||
|
|
209
|
-
live.session.workspaceId !== snapshot.targetWorkspaceId
|
|
210
|
-
) {
|
|
211
|
-
return denied("session_identity_changed");
|
|
212
|
-
}
|
|
213
|
-
if (live.session.visibility !== snapshot.targetSessionVisibility) {
|
|
214
|
-
return denied("session_visibility_changed");
|
|
215
|
-
}
|
|
216
|
-
if (live.session.authorityEpoch !== snapshot.targetSessionAuthorityEpoch) {
|
|
217
|
-
return denied("session_authority_epoch_changed");
|
|
218
|
-
}
|
|
219
|
-
const connection = live.connection;
|
|
220
|
-
if (!connection) return denied("connection_missing");
|
|
221
|
-
if (
|
|
222
|
-
connection.id !== snapshot.connectionId ||
|
|
223
|
-
connection.organizationId !== snapshot.organizationId ||
|
|
224
|
-
connection.workspaceId !== snapshot.originWorkspaceId ||
|
|
225
|
-
connection.providerDomain.toLowerCase() !== snapshot.providerDomain ||
|
|
226
|
-
connection.kind !== snapshot.connectionKind
|
|
227
|
-
) {
|
|
228
|
-
return denied("connection_identity_changed");
|
|
229
|
-
}
|
|
230
|
-
if (connection.generation !== snapshot.connectionGeneration) {
|
|
231
|
-
return denied("connection_generation_changed");
|
|
232
|
-
}
|
|
233
|
-
if (connection.status !== "active") return denied("connection_status_inactive");
|
|
234
|
-
if (
|
|
235
|
-
connection.subjectId !== snapshot.ownerSubjectId ||
|
|
236
|
-
connection.ownerOrganizationMembershipId !== snapshot.ownerOrganizationMembershipId
|
|
237
|
-
) {
|
|
238
|
-
return denied("connection_owner_changed");
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (snapshot.scope === "workspace") {
|
|
242
|
-
if (connection.subjectId !== null || connection.workspaceId !== snapshot.targetWorkspaceId) {
|
|
243
|
-
return denied("connection_owner_changed");
|
|
244
|
-
}
|
|
245
|
-
return {
|
|
246
|
-
status: "authorized",
|
|
247
|
-
attribution: ConnectionUseAttribution.parse({
|
|
248
|
-
organizationId: snapshot.organizationId,
|
|
249
|
-
workspaceId: snapshot.targetWorkspaceId,
|
|
250
|
-
sessionId: snapshot.targetSessionId,
|
|
251
|
-
connectionId: snapshot.connectionId,
|
|
252
|
-
connectionGeneration: snapshot.connectionGeneration,
|
|
253
|
-
scope: "workspace",
|
|
254
|
-
ownerSubjectId: null,
|
|
255
|
-
authorityId: null,
|
|
256
|
-
grantId: null,
|
|
257
|
-
}),
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const delegation = snapshot.userDelegation!;
|
|
262
|
-
const membership = live.ownerMembership;
|
|
263
|
-
if (
|
|
264
|
-
!membership ||
|
|
265
|
-
membership.status !== "active" ||
|
|
266
|
-
membership.id !== snapshot.ownerOrganizationMembershipId ||
|
|
267
|
-
membership.organizationId !== snapshot.organizationId ||
|
|
268
|
-
membership.subjectId !== snapshot.ownerSubjectId ||
|
|
269
|
-
membership.authorizationRevision !== snapshot.ownerMembershipAuthorizationRevision
|
|
270
|
-
) {
|
|
271
|
-
return denied("owner_membership_inactive");
|
|
272
|
-
}
|
|
273
|
-
const authority = live.userAuthority;
|
|
274
|
-
if (!authority) return denied("authority_missing");
|
|
275
|
-
if (
|
|
276
|
-
authority.id !== delegation.authorityId ||
|
|
277
|
-
authority.organizationId !== snapshot.organizationId ||
|
|
278
|
-
authority.resourceKind !== "connection" ||
|
|
279
|
-
authority.resourceId !== snapshot.connectionId ||
|
|
280
|
-
authority.originWorkspaceId !== snapshot.originWorkspaceId ||
|
|
281
|
-
authority.ownerSubjectId !== snapshot.ownerSubjectId ||
|
|
282
|
-
authority.ownerOrganizationMembershipId !== snapshot.ownerOrganizationMembershipId
|
|
283
|
-
) {
|
|
284
|
-
return denied("authority_identity_changed");
|
|
285
|
-
}
|
|
286
|
-
if (authority.generation !== delegation.authorityGeneration) {
|
|
287
|
-
return denied("authority_generation_changed");
|
|
288
|
-
}
|
|
289
|
-
if (authority.status !== "active") return denied("authority_status_inactive");
|
|
290
|
-
|
|
291
|
-
const grant = live.grant;
|
|
292
|
-
if (!grant) return denied("grant_missing");
|
|
293
|
-
if (
|
|
294
|
-
grant.id !== delegation.grantId ||
|
|
295
|
-
grant.authorityId !== delegation.authorityId ||
|
|
296
|
-
grant.organizationId !== snapshot.organizationId ||
|
|
297
|
-
grant.workspaceId !== snapshot.targetWorkspaceId ||
|
|
298
|
-
grant.sessionId !== delegation.sessionId ||
|
|
299
|
-
grant.action !== "connection.use" ||
|
|
300
|
-
grant.mode !== delegation.mode ||
|
|
301
|
-
grant.context !== snapshot.targetSessionVisibility ||
|
|
302
|
-
grant.authorityEpoch !== delegation.authorityEpoch
|
|
303
|
-
) {
|
|
304
|
-
return denied("grant_identity_changed");
|
|
305
|
-
}
|
|
306
|
-
if (grant.generation !== delegation.grantGeneration) {
|
|
307
|
-
return denied("grant_generation_changed");
|
|
308
|
-
}
|
|
309
|
-
if (grant.status !== "active") return denied("grant_status_inactive");
|
|
310
|
-
if (grant.expiresAt !== null && Date.parse(grant.expiresAt) <= now.getTime()) {
|
|
311
|
-
return denied("grant_expired");
|
|
312
|
-
}
|
|
313
|
-
if (grant.mode === "once" && grant.consumed) return denied("grant_already_consumed");
|
|
314
|
-
|
|
315
|
-
return {
|
|
316
|
-
status: "authorized",
|
|
317
|
-
attribution: ConnectionUseAttribution.parse({
|
|
318
|
-
organizationId: snapshot.organizationId,
|
|
319
|
-
workspaceId: snapshot.targetWorkspaceId,
|
|
320
|
-
sessionId: snapshot.targetSessionId,
|
|
321
|
-
connectionId: snapshot.connectionId,
|
|
322
|
-
connectionGeneration: snapshot.connectionGeneration,
|
|
323
|
-
scope: "user",
|
|
324
|
-
ownerSubjectId: snapshot.ownerSubjectId,
|
|
325
|
-
authorityId: delegation.authorityId,
|
|
326
|
-
grantId: delegation.grantId,
|
|
327
|
-
}),
|
|
328
|
-
};
|
|
329
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { Settings } from "@opengeni/config";
|
|
2
|
-
import type { McpServerConnectionRef } from "@opengeni/contracts";
|
|
3
|
-
import { HTTPException } from "hono/http-exception";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Explicit host-owned MCP refs change which credential authority executes an
|
|
7
|
-
* opaque connection id. Admit new external refs only after the operator has
|
|
8
|
-
* completed the two-phase fleet rollout. Readers and inheritance remain
|
|
9
|
-
* tolerant regardless of this switch, and markerless legacy refs retain their
|
|
10
|
-
* bounded non-UUID compatibility path.
|
|
11
|
-
*/
|
|
12
|
-
export function assertHostMcpAuthoritySourceAdmissionEnabled(
|
|
13
|
-
settings: Pick<Settings, "hostMcpAuthoritySourceAdmissionEnabled">,
|
|
14
|
-
connectionRef: McpServerConnectionRef | null | undefined,
|
|
15
|
-
): void {
|
|
16
|
-
if (
|
|
17
|
-
connectionRef?.authoritySource === "host" &&
|
|
18
|
-
!settings.hostMcpAuthoritySourceAdmissionEnabled
|
|
19
|
-
) {
|
|
20
|
-
throw new HTTPException(422, {
|
|
21
|
-
message:
|
|
22
|
-
"new host-owned MCP connection refs are not admitted; upgrade the complete API/worker/web fleet, then set OPENGENI_HOST_MCP_AUTHORITY_SOURCE_ADMISSION_ENABLED=true",
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { HTTPException } from "hono/http-exception";
|
|
2
|
-
import type { Settings } from "@opengeni/config";
|
|
3
|
-
import type { ToolRef, ScheduledTask, AccessGrant } from "@opengeni/contracts";
|
|
4
|
-
import {
|
|
5
|
-
hostMcpBindingMatchesSelection,
|
|
6
|
-
HostMcpCreateSelections,
|
|
7
|
-
} from "@opengeni/contracts/host-mcp-bindings";
|
|
8
|
-
import {
|
|
9
|
-
captureHostMcpTaskAuthorities,
|
|
10
|
-
getHostMcpBinding,
|
|
11
|
-
getHostMcpDelegation,
|
|
12
|
-
HostMcpDelegationAuthorityError,
|
|
13
|
-
inheritHostMcpTaskAuthoritiesFromAttempt,
|
|
14
|
-
type Database,
|
|
15
|
-
} from "@opengeni/db";
|
|
16
|
-
import { hasPermission, type AccessGrantAuthorization } from "../access";
|
|
17
|
-
import { prepareHostMcpOwnerAuthorization } from "../application/host-mcp-owner";
|
|
18
|
-
import { assertHostMcpAuthoritySourceAdmissionEnabled } from "./host-mcp-authority-source-admission";
|
|
19
|
-
|
|
20
|
-
/** Internal closure, not a caller-provided callback or durable key dependency. */
|
|
21
|
-
export function prepareHostMcpTaskAdmission(input: {
|
|
22
|
-
settings: Settings;
|
|
23
|
-
tools: ToolRef[];
|
|
24
|
-
grant: AccessGrant;
|
|
25
|
-
authorization?: AccessGrantAuthorization;
|
|
26
|
-
selections: unknown;
|
|
27
|
-
}): (tx: Database, task: ScheduledTask) => Promise<void> {
|
|
28
|
-
const selections = HostMcpCreateSelections.parse(input.selections);
|
|
29
|
-
if (
|
|
30
|
-
!input.authorization ||
|
|
31
|
-
input.authorization.grant.accountId !== input.grant.accountId ||
|
|
32
|
-
input.authorization.grant.subjectId !== input.grant.subjectId ||
|
|
33
|
-
input.authorization?.grant.workspaceId !== input.grant.workspaceId ||
|
|
34
|
-
input.grant.metadata?.["sessionId"] ||
|
|
35
|
-
!hasPermission(input.authorization?.grant.permissions ?? [], "connections:read") ||
|
|
36
|
-
!hasPermission(input.grant.permissions, "connections:read")
|
|
37
|
-
)
|
|
38
|
-
throw new HTTPException(403, {
|
|
39
|
-
message: "Host task selection requires a verified owner",
|
|
40
|
-
});
|
|
41
|
-
const reauthorize = prepareHostMcpOwnerAuthorization(
|
|
42
|
-
input.authorization,
|
|
43
|
-
input.grant.workspaceId,
|
|
44
|
-
"connections:read",
|
|
45
|
-
);
|
|
46
|
-
const prepared = selections.map((selection) => {
|
|
47
|
-
const server = input.settings.mcpServers.find(
|
|
48
|
-
(candidate) => candidate.id === selection.serverId,
|
|
49
|
-
);
|
|
50
|
-
if (
|
|
51
|
-
!server?.url ||
|
|
52
|
-
server.connectionRef?.authoritySource !== "host" ||
|
|
53
|
-
!server.connectionRef.hostBinding ||
|
|
54
|
-
!input.tools.some((tool) => tool.kind === "mcp" && tool.id === selection.serverId)
|
|
55
|
-
)
|
|
56
|
-
throw new HTTPException(422, {
|
|
57
|
-
message: "Host task selection must match a selected configured server",
|
|
58
|
-
});
|
|
59
|
-
assertHostMcpAuthoritySourceAdmissionEnabled(input.settings, server.connectionRef);
|
|
60
|
-
return {
|
|
61
|
-
delegationId: selection.delegationId,
|
|
62
|
-
generation: selection.generation,
|
|
63
|
-
configured: structuredClone({
|
|
64
|
-
serverId: selection.serverId,
|
|
65
|
-
destinationUrl: server.url,
|
|
66
|
-
connectionRef: server.connectionRef,
|
|
67
|
-
}),
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
return async (tx, task) => {
|
|
71
|
-
const owner = await reauthorize(tx);
|
|
72
|
-
try {
|
|
73
|
-
const selected = [];
|
|
74
|
-
for (const selection of prepared) {
|
|
75
|
-
const delegation = await getHostMcpDelegation(tx, owner, selection.delegationId);
|
|
76
|
-
const binding = delegation
|
|
77
|
-
? await getHostMcpBinding(tx, owner, delegation.bindingId)
|
|
78
|
-
: null;
|
|
79
|
-
if (!binding || !hostMcpBindingMatchesSelection(binding, selection.configured))
|
|
80
|
-
throw new HostMcpDelegationAuthorityError("Host task selection changed");
|
|
81
|
-
selected.push({
|
|
82
|
-
delegationId: selection.delegationId,
|
|
83
|
-
generation: selection.generation,
|
|
84
|
-
bindingId: binding.id,
|
|
85
|
-
bindingGeneration: binding.generation,
|
|
86
|
-
definition: binding.definition,
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
if (selected.length) await captureHostMcpTaskAuthorities(tx, owner, task, selected);
|
|
90
|
-
} catch (error) {
|
|
91
|
-
if (error instanceof HostMcpDelegationAuthorityError)
|
|
92
|
-
throw new HTTPException(403, { message: error.message });
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function prepareInheritedHostMcpTaskAdmission(
|
|
99
|
-
settings: Settings,
|
|
100
|
-
tools: ToolRef[],
|
|
101
|
-
source: { sessionId: string; turnId: string; attemptId: string; executionGeneration: number },
|
|
102
|
-
): (tx: Database, task: ScheduledTask) => Promise<void> {
|
|
103
|
-
const configured = settings.mcpServers.flatMap((server) => {
|
|
104
|
-
if (
|
|
105
|
-
!tools.some((tool) => tool.kind === "mcp" && tool.id === server.id) ||
|
|
106
|
-
server.connectionRef?.authoritySource !== "host" ||
|
|
107
|
-
!server.connectionRef.hostBinding ||
|
|
108
|
-
!server.url
|
|
109
|
-
)
|
|
110
|
-
return [];
|
|
111
|
-
assertHostMcpAuthoritySourceAdmissionEnabled(settings, server.connectionRef);
|
|
112
|
-
return [
|
|
113
|
-
structuredClone({
|
|
114
|
-
serverId: server.id,
|
|
115
|
-
destinationUrl: server.url,
|
|
116
|
-
connectionRef: server.connectionRef,
|
|
117
|
-
}),
|
|
118
|
-
];
|
|
119
|
-
});
|
|
120
|
-
return (tx, task) => inheritHostMcpTaskAuthoritiesFromAttempt(tx, task, source, configured);
|
|
121
|
-
}
|