@opengeni/core 0.28.1 → 2.1.0-canary.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/dist/access/index.d.ts +37 -0
- package/dist/application/new-session-drafts.d.ts +8 -1
- package/dist/application/session-commands.d.ts +18 -11
- package/dist/application/session-tenancy.d.ts +18 -0
- package/dist/application/user-resource-grants.d.ts +20 -0
- package/dist/{chunk-6WKPWE5S.js → chunk-H7X52RI2.js} +27 -18
- package/dist/chunk-H7X52RI2.js.map +1 -0
- package/dist/{chunk-JJU6I5XD.js → chunk-VCZFFEPB.js} +21 -9
- package/dist/chunk-VCZFFEPB.js.map +1 -0
- package/dist/{chunk-NYPMQ7NO.js → chunk-ZYCNLYGH.js} +29 -27
- package/dist/chunk-ZYCNLYGH.js.map +1 -0
- package/dist/dependencies.d.ts +12 -1
- package/dist/domain/automations.d.ts +51 -0
- package/dist/domain/company-brain-governed-writes.d.ts +70 -0
- package/dist/domain/company-profile-agent-admin.d.ts +24 -0
- package/dist/domain/connection-authority.d.ts +97 -0
- package/dist/domain/connection-authority.js +169 -0
- package/dist/domain/connection-authority.js.map +1 -0
- package/dist/domain/editable-artifacts/service.d.ts +1 -2
- package/dist/domain/environments.d.ts +1 -1
- package/dist/domain/github-repository-bindings.d.ts +117 -0
- package/dist/domain/governed-learning-activation.d.ts +16 -0
- package/dist/domain/governed-learning-evaluator.d.ts +6 -0
- package/dist/domain/governed-learning-slack-publication.d.ts +45 -0
- package/dist/domain/memory-slack-delivery.d.ts +1 -4
- package/dist/domain/organization-membership-lifecycle.d.ts +1 -0
- package/dist/domain/packs.d.ts +5 -1
- package/dist/domain/personal-connection-delegations.d.ts +44 -2
- package/dist/domain/pr-review.d.ts +149 -0
- package/dist/domain/remember.d.ts +69 -0
- package/dist/domain/resources.d.ts +23 -2
- package/dist/domain/scheduled-tasks.d.ts +43 -2
- package/dist/domain/session-tool-policy.d.ts +1 -1
- package/dist/domain/sessions.d.ts +43 -9
- package/dist/editable-artifact-live/application.d.ts +10 -2
- package/dist/editable-artifact-live/ports.d.ts +0 -1
- package/dist/editable-artifact-live/server.d.ts +0 -1
- package/dist/editable-artifact-live/ticket.d.ts +0 -1
- package/dist/editable-artifact-live/types.d.ts +5 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifacts.js +2 -4
- package/dist/index.d.ts +13 -1
- package/dist/index.js +4973 -1243
- package/dist/index.js.map +1 -1
- package/dist/rigs/index.d.ts +22 -5
- package/dist/sandbox/fleet.d.ts +34 -6
- package/dist/sandbox/routing.d.ts +15 -1
- package/dist/sandbox/runtime-settings.d.ts +39 -0
- package/package.json +16 -11
- package/src/access/index.ts +151 -31
- package/src/application/new-session-drafts.ts +32 -5
- package/src/application/session-commands.ts +305 -71
- package/src/application/session-tenancy.ts +321 -0
- package/src/application/user-resource-grants.ts +140 -0
- package/src/billing/limits.ts +25 -15
- package/src/dependencies.ts +12 -1
- package/src/domain/automations.ts +196 -0
- package/src/domain/capabilities.ts +7 -1
- package/src/domain/company-brain-governed-writes.ts +330 -0
- package/src/domain/company-profile-agent-admin.ts +55 -0
- package/src/domain/connection-authority.ts +329 -0
- package/src/domain/editable-artifacts/agent-application.ts +17 -9
- package/src/domain/editable-artifacts/service.ts +12 -1
- package/src/domain/editable-artifacts/snapshot-compaction.ts +9 -5
- package/src/domain/editable-artifacts/snapshot-verifier-genesis.ts +10 -8
- package/src/domain/editable-artifacts/snapshot-verifier.ts +10 -3
- package/src/domain/environments.ts +16 -3
- package/src/domain/github-repository-bindings.ts +295 -0
- package/src/domain/governed-learning-activation.ts +44 -0
- package/src/domain/governed-learning-evaluator.ts +49 -0
- package/src/domain/governed-learning-slack-publication.ts +157 -0
- package/src/domain/insights.ts +9 -0
- package/src/domain/memory-slack-delivery.ts +0 -30
- package/src/domain/organization-membership-lifecycle.ts +9 -0
- package/src/domain/packs.ts +106 -3
- package/src/domain/personal-connection-delegations.ts +597 -42
- package/src/domain/pr-review.ts +671 -0
- package/src/domain/remember.ts +549 -0
- package/src/domain/resources.ts +144 -6
- package/src/domain/scheduled-tasks.ts +504 -77
- package/src/domain/session-tool-policy.ts +12 -9
- package/src/domain/sessions.ts +629 -176
- package/src/editable-artifact-live/application.ts +21 -3
- package/src/editable-artifact-live/ports.ts +0 -1
- package/src/editable-artifact-live/server.ts +13 -7
- package/src/editable-artifact-live/ticket.ts +1 -5
- package/src/editable-artifact-live/types.ts +6 -2
- package/src/editable-artifact-live/wire.ts +0 -10
- package/src/index.ts +13 -1
- package/src/rigs/index.ts +165 -48
- package/src/sandbox/fleet.ts +376 -30
- package/src/sandbox/routing.ts +91 -29
- package/src/sandbox/runtime-settings.ts +274 -0
- package/src/session-authorization.ts +17 -49
- package/dist/chunk-6WKPWE5S.js.map +0 -1
- package/dist/chunk-JJU6I5XD.js.map +0 -1
- package/dist/chunk-NYPMQ7NO.js.map +0 -1
- package/dist/domain/durable-learning-slack-publication.d.ts +0 -72
- package/src/domain/durable-learning-slack-publication.ts +0 -202
|
@@ -2,27 +2,92 @@ import type { McpServerConfig, Settings } from "@opengeni/config";
|
|
|
2
2
|
import type {
|
|
3
3
|
AccessGrant,
|
|
4
4
|
ConnectionMetadata,
|
|
5
|
+
McpConnectionAuthoritySelection,
|
|
5
6
|
McpPersonalConnectionDelegation,
|
|
7
|
+
ResourceRef,
|
|
6
8
|
SessionTurn,
|
|
7
9
|
SocialConnection,
|
|
8
10
|
ToolRef,
|
|
9
11
|
} from "@opengeni/contracts";
|
|
10
12
|
import {
|
|
13
|
+
GOOGLE_DRIVE_PROVIDER_DOMAIN,
|
|
14
|
+
GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
15
|
+
GoogleDriveConnectionMetadata,
|
|
16
|
+
googleDriveScopesAllowCapability,
|
|
17
|
+
} from "@opengeni/contracts/google-drive";
|
|
18
|
+
import {
|
|
19
|
+
isPersonalGitHubConnection,
|
|
20
|
+
PERSONAL_GITHUB_CONNECTION_SURFACE_ID,
|
|
21
|
+
PersonalGitHubConnectionMetadata,
|
|
22
|
+
} from "@opengeni/contracts/personal-github";
|
|
23
|
+
import {
|
|
24
|
+
getPersonalGitHubRepositorySelectionState,
|
|
11
25
|
getSessionTurnPersonalConnectionDelegations,
|
|
12
26
|
getConnectionMetadata,
|
|
13
27
|
getSocialConnection,
|
|
14
|
-
getWorkspaceGrant,
|
|
15
28
|
listConnectionsMetadata,
|
|
16
29
|
listSocialConnections,
|
|
30
|
+
resolvePersonalConnectionAuthoritySelectionOrigin,
|
|
31
|
+
namedSubjectHasLiveWorkspaceAuthority,
|
|
17
32
|
type Database,
|
|
18
33
|
type ResolveConnectionCredentialInput,
|
|
19
34
|
type ResolveConnectionCredentialResult,
|
|
20
35
|
} from "@opengeni/db";
|
|
36
|
+
import { personalGitHubRepositoryResources } from "./resources";
|
|
21
37
|
|
|
22
38
|
export type PersonalConnectionDelegationSource =
|
|
23
|
-
| {
|
|
39
|
+
| {
|
|
40
|
+
kind: "subject";
|
|
41
|
+
subjectId: string;
|
|
42
|
+
/**
|
|
43
|
+
* Organization of the authorizing grant. The owner-only
|
|
44
|
+
* personal-workspace pointer lives on an organization membership, so
|
|
45
|
+
* the account is part of the question, not an optimization.
|
|
46
|
+
*/
|
|
47
|
+
accountId: string;
|
|
48
|
+
}
|
|
24
49
|
| { kind: "turn"; sessionId: string; turnId: string }
|
|
25
50
|
| { kind: "none" };
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Is `subjectId` still authorized in `workspaceId`?
|
|
54
|
+
*
|
|
55
|
+
* Personal-connection authority has always been "the owner must still belong
|
|
56
|
+
* to this workspace", but the old check was `getWorkspaceGrant` - a bare
|
|
57
|
+
* `workspace_memberships` join. A managed human's personal workspace
|
|
58
|
+
* deliberately has no row in that table (migration 0219 raises on one), so the
|
|
59
|
+
* join answered `false` for the one person who always belongs, and every
|
|
60
|
+
* personal connection silently disappeared inside the owner's own private
|
|
61
|
+
* workspace.
|
|
62
|
+
*
|
|
63
|
+
* `namedSubjectHasLiveWorkspaceAuthority` models both halves - the membership
|
|
64
|
+
* row and the organization membership's `personal_workspace_id` pointer - but
|
|
65
|
+
* it is an **oracle, not an authorization**: it answers for whatever subject it
|
|
66
|
+
* is handed and cannot establish who the caller is (see its doc comment in
|
|
67
|
+
* `@opengeni/db`). Every use is safe only because *this* module has already
|
|
68
|
+
* established entitlement to ask, locally:
|
|
69
|
+
*
|
|
70
|
+
* - at freeze time the subject is `source.subjectId`, and
|
|
71
|
+
* `personalConnectionDelegationSourceForGrant` admits only a grant whose
|
|
72
|
+
* `subjectId` came from a real authenticated principal. Agent-attempt,
|
|
73
|
+
* service, service-initiated, and **delegated/bearer** grants never reach
|
|
74
|
+
* this branch: they resolve to `{kind:"none"}`, or - for a worker-signed
|
|
75
|
+
* agent attempt - to `{kind:"turn"}`, which probes only a frozen
|
|
76
|
+
* `ownerSubjectId` already persisted for the grant's own workspace. Either
|
|
77
|
+
* way an unvalidated signed-token subject is never the probed subject; and
|
|
78
|
+
* - on the `*ForGrant` paths the subject is the frozen `ownerSubjectId` of a
|
|
79
|
+
* delegation already persisted for the workspace the caller is already
|
|
80
|
+
* authorized in.
|
|
81
|
+
*
|
|
82
|
+
* Do not pass any other subject through this helper. A subject that was merely
|
|
83
|
+
* looked up, guessed, or supplied by a caller is not entitled.
|
|
84
|
+
*/
|
|
85
|
+
async function ownerStillBelongsToWorkspace(
|
|
86
|
+
db: Database,
|
|
87
|
+
input: { accountId: string; workspaceId: string; subjectId: string },
|
|
88
|
+
): Promise<boolean> {
|
|
89
|
+
return await namedSubjectHasLiveWorkspaceAuthority(db, input);
|
|
90
|
+
}
|
|
26
91
|
export type AuthorizedSocialConnection = { connection: SocialConnection; subjectId: string | null };
|
|
27
92
|
export type AuthorizedAtlassianConnection = {
|
|
28
93
|
connection: ConnectionMetadata;
|
|
@@ -47,17 +112,48 @@ export function personalConnectionDelegationSourceForGrant(
|
|
|
47
112
|
): PersonalConnectionDelegationSource {
|
|
48
113
|
const callerSessionId = grant.metadata?.["sessionId"];
|
|
49
114
|
const callerTurnId = grant.metadata?.["turnId"];
|
|
115
|
+
// LOAD-BEARING ORDERING. This turn branch sits ABOVE the delegated filter
|
|
116
|
+
// below, so a grant carrying both `sessionId` and `turnId` never reaches that
|
|
117
|
+
// filter. That is safe only because `DelegatedAccessTokenPayload`'s
|
|
118
|
+
// `superRefine` forbids `principalKind: "human_session"` from carrying any
|
|
119
|
+
// attempt claim (`turnId`/`attemptId`/`executionGeneration`), so a delegated
|
|
120
|
+
// human-session bearer is structurally unable to take this branch. If that
|
|
121
|
+
// refinement is ever relaxed, a delegated bearer could route around the
|
|
122
|
+
// delegated filter by presenting a turn reference - move the filter above
|
|
123
|
+
// this branch at the same time. Pinned by
|
|
124
|
+
// `packages/contracts/test/delegated-access-token-attempt-claims.test.ts`.
|
|
50
125
|
if (typeof callerSessionId === "string" && typeof callerTurnId === "string") {
|
|
51
126
|
return { kind: "turn", sessionId: callerSessionId, turnId: callerTurnId };
|
|
52
127
|
}
|
|
53
128
|
if (
|
|
54
129
|
grant.principalKind === "agent_attempt" ||
|
|
55
130
|
grant.principalKind === "service" ||
|
|
56
|
-
grant.serviceInitiator
|
|
131
|
+
grant.serviceInitiator ||
|
|
132
|
+
// A delegated bearer's grant is built entirely from signed token payload
|
|
133
|
+
// (`delegatedAccessContext`); no database row binds its `subjectId` or
|
|
134
|
+
// `workspaceId` to anything. It is therefore never an entitled subject for
|
|
135
|
+
// the owner-only personal-workspace pointer, which CLAUDE.md reserves for
|
|
136
|
+
// the canonical managed-cookie session: "Bearer/delegated principals, API
|
|
137
|
+
// keys, and account or organization administrators receive no
|
|
138
|
+
// personal-workspace access through that exception." Before the pointer
|
|
139
|
+
// existed this was moot - `getWorkspaceGrant` returned null for a personal
|
|
140
|
+
// workspace regardless - so the filter belongs with the pointer.
|
|
141
|
+
//
|
|
142
|
+
// Why testing THIS field is sound while inspecting the others is not: a
|
|
143
|
+
// host signs `subjectId`, `principalKind`, and `serviceInitiator` into the
|
|
144
|
+
// token itself, so those are host-chosen and a hostile host picks whatever
|
|
145
|
+
// it likes. `metadata.delegated` is different - it is stamped by
|
|
146
|
+
// `delegatedAccessContext` in `@opengeni/core` AFTER the token signature is
|
|
147
|
+
// verified, and is not carried in the payload at all, so a host cannot
|
|
148
|
+
// clear it. Do not "harden" this by switching to `principalKind`. The
|
|
149
|
+
// strictly better shape is a positive assertion of how the request
|
|
150
|
+
// authenticated (a canonical managed-cookie stamp) rather than any
|
|
151
|
+
// inspection of the grant; that convergence is tracked separately.
|
|
152
|
+
grant.metadata?.delegated === true
|
|
57
153
|
) {
|
|
58
154
|
return { kind: "none" };
|
|
59
155
|
}
|
|
60
|
-
return { kind: "subject", subjectId: grant.subjectId };
|
|
156
|
+
return { kind: "subject", subjectId: grant.subjectId, accountId: grant.accountId };
|
|
61
157
|
}
|
|
62
158
|
|
|
63
159
|
export async function authorizedSocialConnectionsForGrant(input: {
|
|
@@ -96,7 +192,13 @@ export async function authorizedSocialConnectionsForGrant(input: {
|
|
|
96
192
|
).filter((item) => item.connectionType === "social");
|
|
97
193
|
const personal: AuthorizedSocialConnection[] = [];
|
|
98
194
|
for (const delegation of delegations) {
|
|
99
|
-
if (
|
|
195
|
+
if (
|
|
196
|
+
!(await ownerStillBelongsToWorkspace(input.db, {
|
|
197
|
+
accountId: input.grant.accountId,
|
|
198
|
+
workspaceId: input.grant.workspaceId,
|
|
199
|
+
subjectId: delegation.ownerSubjectId,
|
|
200
|
+
}))
|
|
201
|
+
)
|
|
100
202
|
continue;
|
|
101
203
|
const connection = await getSocialConnection(
|
|
102
204
|
input.db,
|
|
@@ -153,7 +255,13 @@ export async function authorizedAtlassianConnectionsForGrant(input: {
|
|
|
153
255
|
).filter((item) => item.connectionType === "atlassian");
|
|
154
256
|
const personal: AuthorizedAtlassianConnection[] = [];
|
|
155
257
|
for (const delegation of delegations) {
|
|
156
|
-
if (
|
|
258
|
+
if (
|
|
259
|
+
!(await ownerStillBelongsToWorkspace(input.db, {
|
|
260
|
+
accountId: input.grant.accountId,
|
|
261
|
+
workspaceId: input.grant.workspaceId,
|
|
262
|
+
subjectId: delegation.ownerSubjectId,
|
|
263
|
+
}))
|
|
264
|
+
) {
|
|
157
265
|
continue;
|
|
158
266
|
}
|
|
159
267
|
const connection = await getConnectionMetadata(
|
|
@@ -206,13 +314,19 @@ export function personalConnectionDelegationsFromVisibleConnections(input: {
|
|
|
206
314
|
servers: McpServerConfig[];
|
|
207
315
|
subjectId: string;
|
|
208
316
|
connections: ConnectionMetadata[];
|
|
317
|
+
authoritySelections?: McpConnectionAuthoritySelection[];
|
|
318
|
+
rejectUnselectedActivatedConnections?: boolean;
|
|
209
319
|
}): McpPersonalConnectionDelegation[] {
|
|
210
320
|
const delegations: McpPersonalConnectionDelegation[] = [];
|
|
211
321
|
const connections = canonicalPersonalConnections(input.connections);
|
|
322
|
+
const selections = new Map(
|
|
323
|
+
(input.authoritySelections ?? []).map((selection) => [selection.serverId, selection]),
|
|
324
|
+
);
|
|
212
325
|
for (const server of input.servers) {
|
|
213
326
|
const ref = server.connectionRef;
|
|
214
327
|
if (!ref || ref.subjectScope !== "subject") continue;
|
|
215
|
-
const
|
|
328
|
+
const selection = selections.get(server.id);
|
|
329
|
+
const eligible = connections.filter(
|
|
216
330
|
(candidate) =>
|
|
217
331
|
candidate.subjectId === input.subjectId &&
|
|
218
332
|
candidate.status === "active" &&
|
|
@@ -220,22 +334,69 @@ export function personalConnectionDelegationsFromVisibleConnections(input: {
|
|
|
220
334
|
(!ref.kind || candidate.kind === ref.kind) &&
|
|
221
335
|
(!ref.connectionId || candidate.id === ref.connectionId),
|
|
222
336
|
);
|
|
337
|
+
// An explicit accepted-work selection is authoritative. Canonical newest
|
|
338
|
+
// ordering is only the bounded legacy/omission fallback; it must never
|
|
339
|
+
// replace a caller's exact opaque connection UUID when several accounts
|
|
340
|
+
// share one server/provider tuple.
|
|
341
|
+
const connection = selection
|
|
342
|
+
? eligible.find((candidate) => candidate.id === selection.connectionId)
|
|
343
|
+
: eligible[0];
|
|
223
344
|
if (!connection) continue;
|
|
345
|
+
if (connection.authorityId) {
|
|
346
|
+
if (
|
|
347
|
+
!selection ||
|
|
348
|
+
selection.connectionId !== connection.id ||
|
|
349
|
+
selection.userDelegation.authorityId !== connection.authorityId
|
|
350
|
+
) {
|
|
351
|
+
if (input.rejectUnselectedActivatedConnections) {
|
|
352
|
+
throw new Error(
|
|
353
|
+
`scheduled connection authority selection is required for activated server ${server.id}`,
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
// Activated organization-user connections are never admitted through
|
|
357
|
+
// the legacy owner tuple. The caller must select one exact opaque grant.
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
selections.delete(server.id);
|
|
361
|
+
} else if (selection) {
|
|
362
|
+
throw new Error(`connection authority is not available for legacy server ${server.id}`);
|
|
363
|
+
}
|
|
224
364
|
delegations.push({
|
|
225
365
|
serverId: server.id,
|
|
226
366
|
connectionId: connection.id,
|
|
367
|
+
...(selection ? { originWorkspaceId: connection.workspaceId } : {}),
|
|
227
368
|
ownerSubjectId: input.subjectId,
|
|
228
369
|
providerDomain: connection.providerDomain,
|
|
229
370
|
kind: connection.kind,
|
|
371
|
+
...(selection ? { userDelegation: selection.userDelegation } : {}),
|
|
230
372
|
});
|
|
231
373
|
}
|
|
374
|
+
if (selections.size > 0) {
|
|
375
|
+
throw new Error(
|
|
376
|
+
`connection authority selection did not match an active selected server: ${[
|
|
377
|
+
...selections.keys(),
|
|
378
|
+
].join(", ")}`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
232
381
|
return delegations;
|
|
233
382
|
}
|
|
234
383
|
|
|
235
384
|
export function personalConnectionDelegationsFromParent(input: {
|
|
236
385
|
servers: McpServerConfig[];
|
|
237
386
|
parentDelegations: McpPersonalConnectionDelegation[];
|
|
387
|
+
targetSessionId?: string;
|
|
388
|
+
rejectActivatedConnections?: boolean;
|
|
238
389
|
}): McpPersonalConnectionDelegation[] {
|
|
390
|
+
// Every successor is new logical work. `once` remains bound to the original
|
|
391
|
+
// accepted turn, `session` may be re-admitted only into that exact session,
|
|
392
|
+
// and `always` may cross to another authorized session.
|
|
393
|
+
const childEligible = (delegation: McpPersonalConnectionDelegation) => {
|
|
394
|
+
const authority = delegation.userDelegation;
|
|
395
|
+
if (!authority) return true;
|
|
396
|
+
if (authority.mode === "once") return false;
|
|
397
|
+
if (authority.mode === "session") return authority.sessionId === input.targetSessionId;
|
|
398
|
+
return true;
|
|
399
|
+
};
|
|
239
400
|
const mcp = input.servers.flatMap((server) => {
|
|
240
401
|
const ref = server.connectionRef;
|
|
241
402
|
if (!ref || ref.subjectScope !== "subject") return [];
|
|
@@ -245,14 +406,233 @@ export function personalConnectionDelegationsFromParent(input: {
|
|
|
245
406
|
sameProviderDomain(candidate.providerDomain, ref.providerDomain) &&
|
|
246
407
|
(!ref.kind || !candidate.kind || candidate.kind === ref.kind),
|
|
247
408
|
);
|
|
248
|
-
return delegation ? [{ ...delegation }] : [];
|
|
409
|
+
return delegation && childEligible(delegation) ? [{ ...delegation }] : [];
|
|
249
410
|
});
|
|
250
|
-
|
|
411
|
+
const projected = [
|
|
251
412
|
...mcp,
|
|
252
413
|
...input.parentDelegations
|
|
253
|
-
.filter(
|
|
414
|
+
.filter(
|
|
415
|
+
(item) =>
|
|
416
|
+
childEligible(item) &&
|
|
417
|
+
(item.connectionType === "social" ||
|
|
418
|
+
item.connectionType === "atlassian" ||
|
|
419
|
+
item.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID),
|
|
420
|
+
)
|
|
254
421
|
.map((item) => ({ ...item })),
|
|
255
422
|
];
|
|
423
|
+
if (
|
|
424
|
+
input.rejectActivatedConnections &&
|
|
425
|
+
projected.some((delegation) => delegation.userDelegation)
|
|
426
|
+
) {
|
|
427
|
+
throw new Error(
|
|
428
|
+
"scheduled connection authority is not available until task occurrence authority is activated",
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
return projected;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Freezes Google Drive publishing only when one exact subject-owned connection
|
|
436
|
+
* is eligible. Multiple writable Google accounts are intentionally ambiguous:
|
|
437
|
+
* callers must narrow the connection before a later turn can advertise or use
|
|
438
|
+
* the private publication tool.
|
|
439
|
+
*/
|
|
440
|
+
export function googleDrivePublicationDelegationFromVisibleConnections(input: {
|
|
441
|
+
subjectId: string;
|
|
442
|
+
connections: ConnectionMetadata[];
|
|
443
|
+
authoritySelection?: McpConnectionAuthoritySelection;
|
|
444
|
+
rejectUnselectedActivatedConnection?: boolean;
|
|
445
|
+
}): McpPersonalConnectionDelegation | null {
|
|
446
|
+
const selection = input.authoritySelection;
|
|
447
|
+
const eligible = input.connections.filter((connection) => {
|
|
448
|
+
if (
|
|
449
|
+
connection.subjectId !== input.subjectId ||
|
|
450
|
+
connection.status !== "active" ||
|
|
451
|
+
connection.kind !== "oauth2" ||
|
|
452
|
+
!sameProviderDomain(connection.providerDomain, GOOGLE_DRIVE_PROVIDER_DOMAIN) ||
|
|
453
|
+
!googleDriveScopesAllowCapability(connection.grantedScopes, "publish_file")
|
|
454
|
+
) {
|
|
455
|
+
return false;
|
|
456
|
+
}
|
|
457
|
+
const metadata = GoogleDriveConnectionMetadata.safeParse(connection.metadata);
|
|
458
|
+
return Boolean(
|
|
459
|
+
metadata.success &&
|
|
460
|
+
metadata.data.outputDestination &&
|
|
461
|
+
metadata.data.lifecycle?.state !== "paused" &&
|
|
462
|
+
(!metadata.data.lifecycle || metadata.data.lifecycle.state === "active"),
|
|
463
|
+
);
|
|
464
|
+
});
|
|
465
|
+
const selectedEligible = selection
|
|
466
|
+
? eligible.filter((connection) => connection.id === selection.connectionId)
|
|
467
|
+
: eligible;
|
|
468
|
+
if (selection && selectedEligible.length !== 1) {
|
|
469
|
+
throw new Error("Google Drive publication authority selection is unavailable");
|
|
470
|
+
}
|
|
471
|
+
if (!selection && selectedEligible.length !== 1) return null;
|
|
472
|
+
const connection = selectedEligible[0]!;
|
|
473
|
+
if (connection.authorityId) {
|
|
474
|
+
if (
|
|
475
|
+
!selection ||
|
|
476
|
+
selection.serverId !== GOOGLE_DRIVE_PUBLICATION_SERVER_ID ||
|
|
477
|
+
selection.connectionId !== connection.id ||
|
|
478
|
+
selection.userDelegation.authorityId !== connection.authorityId
|
|
479
|
+
) {
|
|
480
|
+
if (input.rejectUnselectedActivatedConnection) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
"scheduled connection authority selection is required for activated Google Drive publication",
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
return null;
|
|
486
|
+
}
|
|
487
|
+
} else if (selection) {
|
|
488
|
+
throw new Error("connection authority is not available for legacy Google Drive publication");
|
|
489
|
+
}
|
|
490
|
+
// Freeze the exact output destination on the accepted delegation: a later
|
|
491
|
+
// connection-settings change must never redirect an already-accepted turn's
|
|
492
|
+
// publication. Eligibility above already proved the metadata parses and the
|
|
493
|
+
// destination exists.
|
|
494
|
+
const frozenMetadata = GoogleDriveConnectionMetadata.parse(connection.metadata);
|
|
495
|
+
if (!frozenMetadata.outputDestination) {
|
|
496
|
+
throw new Error("Google Drive publication destination disappeared during acceptance");
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
500
|
+
connectionId: connection.id,
|
|
501
|
+
...(selection ? { originWorkspaceId: connection.workspaceId } : {}),
|
|
502
|
+
ownerSubjectId: input.subjectId,
|
|
503
|
+
providerDomain: connection.providerDomain,
|
|
504
|
+
kind: connection.kind,
|
|
505
|
+
...(selection ? { userDelegation: selection.userDelegation } : {}),
|
|
506
|
+
outputDestination: frozenMetadata.outputDestination,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function personalAtlassianDelegationsFromVisibleConnections(input: {
|
|
511
|
+
subjectId: string;
|
|
512
|
+
connections: ConnectionMetadata[];
|
|
513
|
+
rejectActivatedConnections?: boolean;
|
|
514
|
+
}): McpPersonalConnectionDelegation[] {
|
|
515
|
+
const eligible = input.connections.filter(
|
|
516
|
+
(connection) =>
|
|
517
|
+
connection.subjectId === input.subjectId &&
|
|
518
|
+
connection.status === "active" &&
|
|
519
|
+
sameProviderDomain(connection.providerDomain, "api.atlassian.com"),
|
|
520
|
+
);
|
|
521
|
+
if (input.rejectActivatedConnections && eligible.some((connection) => connection.authorityId)) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
"scheduled connection authority selection is required for activated Atlassian access",
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
// Atlassian's first-party multi-call adapter is a named successor. Until it
|
|
527
|
+
// can consume an accepted snapshot, common-authority rows are omitted rather
|
|
528
|
+
// than falling through the legacy owner-tuple resolver.
|
|
529
|
+
return eligible
|
|
530
|
+
.filter((connection) => !connection.authorityId)
|
|
531
|
+
.map((connection) => ({
|
|
532
|
+
serverId: `atlassian:${connection.id}`,
|
|
533
|
+
connectionId: connection.id,
|
|
534
|
+
ownerSubjectId: input.subjectId,
|
|
535
|
+
providerDomain: connection.providerDomain,
|
|
536
|
+
kind: connection.kind,
|
|
537
|
+
connectionType: "atlassian" as const,
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function personalGitHubDelegationFromVisibleConnections(input: {
|
|
542
|
+
db: Database;
|
|
543
|
+
accountId: string;
|
|
544
|
+
subjectId: string;
|
|
545
|
+
connections: ConnectionMetadata[];
|
|
546
|
+
resources: ResourceRef[];
|
|
547
|
+
authoritySelection?: McpConnectionAuthoritySelection;
|
|
548
|
+
}): Promise<McpPersonalConnectionDelegation | null> {
|
|
549
|
+
const resources = personalGitHubRepositoryResources(input.resources);
|
|
550
|
+
if (resources.length === 0) {
|
|
551
|
+
if (input.authoritySelection) {
|
|
552
|
+
throw new Error("personal GitHub connection authority requires selected repositories");
|
|
553
|
+
}
|
|
554
|
+
return null;
|
|
555
|
+
}
|
|
556
|
+
const bindingIds = new Set(resources.map((resource) => resource.credentialBindingId));
|
|
557
|
+
if (bindingIds.size !== 1) {
|
|
558
|
+
throw new Error("accepted work may use only one personal GitHub account");
|
|
559
|
+
}
|
|
560
|
+
const authoritySelection = input.authoritySelection;
|
|
561
|
+
if (!authoritySelection) {
|
|
562
|
+
throw new Error("personal GitHub repository resources require explicit connection authority");
|
|
563
|
+
}
|
|
564
|
+
const connection = input.connections.find(
|
|
565
|
+
(candidate) =>
|
|
566
|
+
candidate.id === authoritySelection.connectionId &&
|
|
567
|
+
candidate.subjectId === input.subjectId &&
|
|
568
|
+
candidate.status === "active" &&
|
|
569
|
+
isPersonalGitHubConnection(candidate),
|
|
570
|
+
);
|
|
571
|
+
if (
|
|
572
|
+
!connection ||
|
|
573
|
+
!connection.authorityId ||
|
|
574
|
+
connection.authorityId !== authoritySelection.userDelegation.authorityId ||
|
|
575
|
+
connection.grantedScopes.length !== 1 ||
|
|
576
|
+
connection.grantedScopes[0] !== "repo"
|
|
577
|
+
) {
|
|
578
|
+
throw new Error("personal GitHub connection authority selection is unavailable");
|
|
579
|
+
}
|
|
580
|
+
const metadata = PersonalGitHubConnectionMetadata.parse(connection.metadata);
|
|
581
|
+
const credentialBindingId = [...bindingIds][0]!;
|
|
582
|
+
if (metadata.credentialBindingId !== credentialBindingId) {
|
|
583
|
+
throw new Error("personal GitHub credential binding does not match the selected connection");
|
|
584
|
+
}
|
|
585
|
+
const selection = await getPersonalGitHubRepositorySelectionState(input.db, {
|
|
586
|
+
accountId: input.accountId,
|
|
587
|
+
originWorkspaceId: connection.workspaceId,
|
|
588
|
+
subjectId: input.subjectId,
|
|
589
|
+
connectionId: connection.id,
|
|
590
|
+
});
|
|
591
|
+
if (
|
|
592
|
+
!selection ||
|
|
593
|
+
selection.selectionGeneration <= 0 ||
|
|
594
|
+
selection.credentialBindingId !== credentialBindingId ||
|
|
595
|
+
selection.providerPrincipalId !== metadata.providerPrincipalId
|
|
596
|
+
) {
|
|
597
|
+
throw new Error("personal GitHub repository selection changed or is unavailable");
|
|
598
|
+
}
|
|
599
|
+
const selectedById = new Map(
|
|
600
|
+
selection.repositories.map((repository) => [repository.repositoryId, repository]),
|
|
601
|
+
);
|
|
602
|
+
const repositories = resources.map((resource) => {
|
|
603
|
+
const selected = selectedById.get(resource.repositoryId);
|
|
604
|
+
if (
|
|
605
|
+
!selected ||
|
|
606
|
+
selected.canonicalUrl !== resource.uri ||
|
|
607
|
+
(resource.access === "write" && selected.selectedAccess !== "write")
|
|
608
|
+
) {
|
|
609
|
+
throw new Error("personal GitHub repository resource is outside the selected authority");
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
repositoryId: selected.repositoryId,
|
|
613
|
+
fullName: selected.fullName,
|
|
614
|
+
canonicalUrl: selected.canonicalUrl,
|
|
615
|
+
ref: resource.ref,
|
|
616
|
+
access: resource.access,
|
|
617
|
+
selectionGeneration: selected.selectionGeneration,
|
|
618
|
+
};
|
|
619
|
+
});
|
|
620
|
+
return {
|
|
621
|
+
serverId: PERSONAL_GITHUB_CONNECTION_SURFACE_ID,
|
|
622
|
+
connectionId: connection.id,
|
|
623
|
+
originWorkspaceId: connection.workspaceId,
|
|
624
|
+
ownerSubjectId: input.subjectId,
|
|
625
|
+
providerDomain: "github.com",
|
|
626
|
+
kind: "oauth2",
|
|
627
|
+
connectionType: "github_personal",
|
|
628
|
+
userDelegation: authoritySelection.userDelegation,
|
|
629
|
+
personalGitHubRepositorySelection: {
|
|
630
|
+
credentialBindingId,
|
|
631
|
+
connectionAuthorityGeneration: selection.connectionAuthorityGeneration,
|
|
632
|
+
selectionGeneration: selection.selectionGeneration,
|
|
633
|
+
repositories,
|
|
634
|
+
},
|
|
635
|
+
};
|
|
256
636
|
}
|
|
257
637
|
|
|
258
638
|
export function personalConnectionDelegationsEqual(
|
|
@@ -265,10 +645,15 @@ export function personalConnectionDelegationsEqual(
|
|
|
265
645
|
const other = byServer.get(delegation.serverId);
|
|
266
646
|
return (
|
|
267
647
|
other?.connectionId === delegation.connectionId &&
|
|
648
|
+
other.originWorkspaceId === delegation.originWorkspaceId &&
|
|
268
649
|
other.ownerSubjectId === delegation.ownerSubjectId &&
|
|
269
650
|
sameProviderDomain(other.providerDomain, delegation.providerDomain) &&
|
|
270
651
|
other.kind === delegation.kind &&
|
|
271
|
-
other.connectionType === delegation.connectionType
|
|
652
|
+
other.connectionType === delegation.connectionType &&
|
|
653
|
+
JSON.stringify(other.userDelegation ?? null) ===
|
|
654
|
+
JSON.stringify(delegation.userDelegation ?? null) &&
|
|
655
|
+
JSON.stringify(other.personalGitHubRepositorySelection ?? null) ===
|
|
656
|
+
JSON.stringify(delegation.personalGitHubRepositorySelection ?? null)
|
|
272
657
|
);
|
|
273
658
|
});
|
|
274
659
|
}
|
|
@@ -318,16 +703,39 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
318
703
|
resolveCredential: ConnectionCredentialResolver;
|
|
319
704
|
settings: Pick<Settings, "mcpServers">;
|
|
320
705
|
personalConnectionDelegations: McpPersonalConnectionDelegation[];
|
|
706
|
+
/**
|
|
707
|
+
* Does the frozen delegation owner still hold workspace authority? Supply
|
|
708
|
+
* the canonical `namedSubjectHasLiveWorkspaceAuthority` resolver, never a bare
|
|
709
|
+
* `workspace_memberships` lookup: a managed human's personal workspace has
|
|
710
|
+
* no membership row, so a bare lookup revokes the owner's own connections.
|
|
711
|
+
*/
|
|
321
712
|
ownerHasWorkspaceMembership: (subjectId: string) => Promise<boolean>;
|
|
322
713
|
}): ConnectionCredentialResolver {
|
|
323
714
|
return async (request) => {
|
|
324
715
|
let effectiveRequest = request;
|
|
325
716
|
if (request.connectionRef.subjectScope === "subject") {
|
|
326
717
|
const config = input.settings.mcpServers.find((server) => server.id === request.serverId);
|
|
718
|
+
const publicationDelegations =
|
|
719
|
+
request.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID &&
|
|
720
|
+
sameProviderDomain(request.connectionRef.providerDomain, GOOGLE_DRIVE_PROVIDER_DOMAIN)
|
|
721
|
+
? input.personalConnectionDelegations.filter(
|
|
722
|
+
(candidate) =>
|
|
723
|
+
candidate.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID &&
|
|
724
|
+
sameProviderDomain(candidate.providerDomain, GOOGLE_DRIVE_PROVIDER_DOMAIN) &&
|
|
725
|
+
candidate.kind === "oauth2" &&
|
|
726
|
+
request.connectionRef.kind === "oauth2",
|
|
727
|
+
)
|
|
728
|
+
: [];
|
|
327
729
|
const delegation = config
|
|
328
730
|
? personalConnectionDelegationForServer(input.personalConnectionDelegations, config)
|
|
329
|
-
:
|
|
330
|
-
|
|
731
|
+
: publicationDelegations.length === 1
|
|
732
|
+
? publicationDelegations[0]!
|
|
733
|
+
: null;
|
|
734
|
+
if (
|
|
735
|
+
!delegation ||
|
|
736
|
+
(!delegation.userDelegation &&
|
|
737
|
+
!(await input.ownerHasWorkspaceMembership(delegation.ownerSubjectId)))
|
|
738
|
+
) {
|
|
331
739
|
return personalAuthorityUnavailable(request);
|
|
332
740
|
}
|
|
333
741
|
effectiveRequest = {
|
|
@@ -352,16 +760,49 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
352
760
|
export async function freezePersonalConnectionDelegations(input: {
|
|
353
761
|
db: Database;
|
|
354
762
|
workspaceId: string;
|
|
355
|
-
settings: Pick<Settings, "mcpServers"
|
|
763
|
+
settings: Pick<Settings, "mcpServers"> & Partial<Pick<Settings, "githubPersonalOauthEnabled">>;
|
|
356
764
|
tools: ToolRef[];
|
|
765
|
+
resources?: ResourceRef[];
|
|
357
766
|
source: PersonalConnectionDelegationSource;
|
|
767
|
+
authoritySelections?: McpConnectionAuthoritySelection[];
|
|
768
|
+
rejectUnselectedActivatedConnections?: boolean;
|
|
769
|
+
/** Exact first-party export tool + permission gate, not broad opengeni attachment. */
|
|
770
|
+
googleDrivePublicationEnabled?: boolean;
|
|
771
|
+
/** Exact first-party Atlassian tool + permission gate. */
|
|
772
|
+
atlassianEnabled?: boolean;
|
|
773
|
+
/** Exact target for successor-mode projection (`session` never crosses it). */
|
|
774
|
+
targetSessionId?: string;
|
|
358
775
|
}): Promise<McpPersonalConnectionDelegation[]> {
|
|
359
776
|
const servers = selectedPersonalConnectionServers(input.settings, input.tools);
|
|
360
777
|
const includeFirstPartyConnections = input.tools.some((tool) => tool.id === "opengeni");
|
|
361
|
-
|
|
778
|
+
const personalGitHubResources = personalGitHubRepositoryResources(input.resources ?? []);
|
|
779
|
+
if (personalGitHubResources.length > 0 && input.settings.githubPersonalOauthEnabled !== true) {
|
|
780
|
+
throw new Error("personal GitHub repository authority is not enabled");
|
|
781
|
+
}
|
|
782
|
+
if (personalGitHubResources.length > 0 && input.source.kind === "none") {
|
|
783
|
+
throw new Error("personal GitHub repository authority requires an authenticated causal human");
|
|
784
|
+
}
|
|
785
|
+
if (
|
|
786
|
+
servers.length === 0 &&
|
|
787
|
+
!includeFirstPartyConnections &&
|
|
788
|
+
personalGitHubResources.length === 0
|
|
789
|
+
) {
|
|
790
|
+
return [];
|
|
791
|
+
}
|
|
792
|
+
if (input.source.kind === "none") {
|
|
362
793
|
return [];
|
|
363
794
|
}
|
|
364
795
|
if (input.source.kind === "turn") {
|
|
796
|
+
if ((input.authoritySelections?.length ?? 0) > 0) {
|
|
797
|
+
throw new Error(
|
|
798
|
+
"agent-created work inherits connection authority from its exact parent turn",
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (personalGitHubResources.length > 0) {
|
|
802
|
+
throw new Error(
|
|
803
|
+
"agent-created personal GitHub repository authority is not activated in this delivery phase",
|
|
804
|
+
);
|
|
805
|
+
}
|
|
365
806
|
const inherited = personalConnectionDelegationsFromParent({
|
|
366
807
|
servers,
|
|
367
808
|
parentDelegations: await getSessionTurnPersonalConnectionDelegations(
|
|
@@ -370,22 +811,123 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
370
811
|
input.source.sessionId,
|
|
371
812
|
input.source.turnId,
|
|
372
813
|
),
|
|
814
|
+
...(input.targetSessionId ? { targetSessionId: input.targetSessionId } : {}),
|
|
815
|
+
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
816
|
+
? { rejectActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
817
|
+
: {}),
|
|
818
|
+
});
|
|
819
|
+
return inherited.filter((item) => {
|
|
820
|
+
if (item.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID) {
|
|
821
|
+
return input.googleDrivePublicationEnabled === true;
|
|
822
|
+
}
|
|
823
|
+
if (item.connectionType === "atlassian") {
|
|
824
|
+
return includeFirstPartyConnections && input.atlassianEnabled === true;
|
|
825
|
+
}
|
|
826
|
+
if (item.connectionType === "social") {
|
|
827
|
+
return includeFirstPartyConnections;
|
|
828
|
+
}
|
|
829
|
+
return true;
|
|
373
830
|
});
|
|
374
|
-
return includeFirstPartyConnections
|
|
375
|
-
? inherited
|
|
376
|
-
: inherited.filter(
|
|
377
|
-
(item) => item.connectionType !== "social" && item.connectionType !== "atlassian",
|
|
378
|
-
);
|
|
379
831
|
}
|
|
380
|
-
const
|
|
381
|
-
|
|
832
|
+
const ownerSubjectId = input.source.subjectId;
|
|
833
|
+
const membership = await ownerStillBelongsToWorkspace(input.db, {
|
|
834
|
+
accountId: input.source.accountId,
|
|
835
|
+
workspaceId: input.workspaceId,
|
|
836
|
+
subjectId: ownerSubjectId,
|
|
837
|
+
});
|
|
838
|
+
const targetLocalConnections = await listConnectionsMetadata(
|
|
839
|
+
input.db,
|
|
840
|
+
input.workspaceId,
|
|
841
|
+
ownerSubjectId,
|
|
842
|
+
);
|
|
843
|
+
const portableSelections = await Promise.all(
|
|
844
|
+
(input.authoritySelections ?? []).map(async (selection) => {
|
|
845
|
+
const originWorkspaceId = await resolvePersonalConnectionAuthoritySelectionOrigin(input.db, {
|
|
846
|
+
accountId: selection.userDelegation.organizationId,
|
|
847
|
+
targetWorkspaceId: input.workspaceId,
|
|
848
|
+
subjectId: ownerSubjectId,
|
|
849
|
+
connectionId: selection.connectionId,
|
|
850
|
+
delegation: selection.userDelegation,
|
|
851
|
+
});
|
|
852
|
+
if (!originWorkspaceId) {
|
|
853
|
+
throw new Error(`connection authority selection is unavailable: ${selection.serverId}`);
|
|
854
|
+
}
|
|
855
|
+
const connection = await getConnectionMetadata(
|
|
856
|
+
input.db,
|
|
857
|
+
originWorkspaceId,
|
|
858
|
+
selection.connectionId,
|
|
859
|
+
ownerSubjectId,
|
|
860
|
+
);
|
|
861
|
+
if (
|
|
862
|
+
!connection ||
|
|
863
|
+
connection.workspaceId !== originWorkspaceId ||
|
|
864
|
+
connection.subjectId !== ownerSubjectId ||
|
|
865
|
+
connection.authorityId !== selection.userDelegation.authorityId
|
|
866
|
+
) {
|
|
867
|
+
throw new Error(`connection authority selection is unavailable: ${selection.serverId}`);
|
|
868
|
+
}
|
|
869
|
+
return connection;
|
|
870
|
+
}),
|
|
871
|
+
);
|
|
872
|
+
if (!membership && portableSelections.length === 0) {
|
|
873
|
+
if (personalGitHubResources.length > 0) {
|
|
874
|
+
throw new Error("personal GitHub repository authority requires live causal user authority");
|
|
875
|
+
}
|
|
876
|
+
return [];
|
|
877
|
+
}
|
|
878
|
+
const visibleConnections = [
|
|
879
|
+
...new Map(
|
|
880
|
+
[...targetLocalConnections, ...portableSelections].map((connection) => [
|
|
881
|
+
connection.id,
|
|
882
|
+
connection,
|
|
883
|
+
]),
|
|
884
|
+
).values(),
|
|
885
|
+
];
|
|
886
|
+
const selectedMcpServerIds = new Set(servers.map((server) => server.id));
|
|
887
|
+
const supportedSelectionIds = new Set(selectedMcpServerIds);
|
|
888
|
+
if (input.googleDrivePublicationEnabled) {
|
|
889
|
+
supportedSelectionIds.add(GOOGLE_DRIVE_PUBLICATION_SERVER_ID);
|
|
890
|
+
}
|
|
891
|
+
if (personalGitHubResources.length > 0) {
|
|
892
|
+
supportedSelectionIds.add(PERSONAL_GITHUB_CONNECTION_SURFACE_ID);
|
|
893
|
+
}
|
|
894
|
+
const unsupportedSelections = (input.authoritySelections ?? []).filter(
|
|
895
|
+
(selection) => !supportedSelectionIds.has(selection.serverId),
|
|
896
|
+
);
|
|
897
|
+
if (unsupportedSelections.length > 0) {
|
|
898
|
+
throw new Error(
|
|
899
|
+
`connection authority selection did not match a selected MCP server: ${unsupportedSelections
|
|
900
|
+
.map((selection) => selection.serverId)
|
|
901
|
+
.join(", ")}`,
|
|
902
|
+
);
|
|
903
|
+
}
|
|
382
904
|
const mcp = personalConnectionDelegationsFromVisibleConnections({
|
|
383
905
|
servers,
|
|
384
|
-
subjectId:
|
|
385
|
-
connections:
|
|
906
|
+
subjectId: ownerSubjectId,
|
|
907
|
+
connections: visibleConnections,
|
|
908
|
+
authoritySelections: (input.authoritySelections ?? []).filter((selection) =>
|
|
909
|
+
selectedMcpServerIds.has(selection.serverId),
|
|
910
|
+
),
|
|
911
|
+
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
912
|
+
? { rejectUnselectedActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
913
|
+
: {}),
|
|
386
914
|
});
|
|
387
|
-
|
|
388
|
-
|
|
915
|
+
const personalGitHubAuthoritySelection = (input.authoritySelections ?? []).find(
|
|
916
|
+
(selection) => selection.serverId === PERSONAL_GITHUB_CONNECTION_SURFACE_ID,
|
|
917
|
+
);
|
|
918
|
+
const personalGitHub = await personalGitHubDelegationFromVisibleConnections({
|
|
919
|
+
db: input.db,
|
|
920
|
+
accountId: input.source.accountId,
|
|
921
|
+
subjectId: ownerSubjectId,
|
|
922
|
+
connections: visibleConnections,
|
|
923
|
+
resources: input.resources ?? [],
|
|
924
|
+
...(personalGitHubAuthoritySelection
|
|
925
|
+
? { authoritySelection: personalGitHubAuthoritySelection }
|
|
926
|
+
: {}),
|
|
927
|
+
});
|
|
928
|
+
if (!includeFirstPartyConnections) {
|
|
929
|
+
return [...mcp, ...(personalGitHub ? [personalGitHub] : [])];
|
|
930
|
+
}
|
|
389
931
|
const visible = await listSocialConnections(input.db, input.workspaceId, 500, ownerSubjectId);
|
|
390
932
|
const latest = new Map<"x" | "reddit", (typeof visible)[number]>();
|
|
391
933
|
for (const connection of visible) {
|
|
@@ -399,16 +941,36 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
399
941
|
if (!prior || connection.updatedAt > prior.updatedAt)
|
|
400
942
|
latest.set(connection.provider, connection);
|
|
401
943
|
}
|
|
402
|
-
const personalAtlassian =
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
944
|
+
const personalAtlassian = input.atlassianEnabled
|
|
945
|
+
? personalAtlassianDelegationsFromVisibleConnections({
|
|
946
|
+
subjectId: ownerSubjectId,
|
|
947
|
+
connections: visibleConnections,
|
|
948
|
+
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
949
|
+
? { rejectActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
950
|
+
: {}),
|
|
951
|
+
})
|
|
952
|
+
: [];
|
|
953
|
+
const googleDriveAuthoritySelection = input.googleDrivePublicationEnabled
|
|
954
|
+
? (input.authoritySelections ?? []).find(
|
|
955
|
+
(selection) => selection.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
956
|
+
)
|
|
957
|
+
: undefined;
|
|
958
|
+
const googleDrivePublication = input.googleDrivePublicationEnabled
|
|
959
|
+
? googleDrivePublicationDelegationFromVisibleConnections({
|
|
960
|
+
subjectId: ownerSubjectId,
|
|
961
|
+
connections: visibleConnections,
|
|
962
|
+
...(googleDriveAuthoritySelection
|
|
963
|
+
? { authoritySelection: googleDriveAuthoritySelection }
|
|
964
|
+
: {}),
|
|
965
|
+
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
966
|
+
? { rejectUnselectedActivatedConnection: input.rejectUnselectedActivatedConnections }
|
|
967
|
+
: {}),
|
|
968
|
+
})
|
|
969
|
+
: null;
|
|
410
970
|
return [
|
|
411
971
|
...mcp,
|
|
972
|
+
...(personalGitHub ? [personalGitHub] : []),
|
|
973
|
+
...(googleDrivePublication ? [googleDrivePublication] : []),
|
|
412
974
|
...[...latest.values()].map((connection) => ({
|
|
413
975
|
serverId: `social:${connection.provider}`,
|
|
414
976
|
connectionId: connection.id,
|
|
@@ -417,13 +979,6 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
417
979
|
kind: "oauth2" as const,
|
|
418
980
|
connectionType: "social" as const,
|
|
419
981
|
})),
|
|
420
|
-
...personalAtlassian
|
|
421
|
-
serverId: `atlassian:${connection.id}`,
|
|
422
|
-
connectionId: connection.id,
|
|
423
|
-
ownerSubjectId,
|
|
424
|
-
providerDomain: connection.providerDomain,
|
|
425
|
-
kind: connection.kind,
|
|
426
|
-
connectionType: "atlassian" as const,
|
|
427
|
-
})),
|
|
982
|
+
...personalAtlassian,
|
|
428
983
|
];
|
|
429
984
|
}
|