@opengeni/core 1.0.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 +34 -2
- package/dist/domain/pr-review.d.ts +149 -0
- package/dist/domain/remember.d.ts +69 -0
- package/dist/domain/resources.d.ts +22 -1
- 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 +40 -6
- 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 +4827 -1164
- 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 +24 -2
- 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 +532 -51
- package/src/domain/pr-review.ts +671 -0
- package/src/domain/remember.ts +549 -0
- package/src/domain/resources.ts +131 -4
- package/src/domain/scheduled-tasks.ts +497 -76
- package/src/domain/session-tool-policy.ts +12 -9
- package/src/domain/sessions.ts +613 -166
- 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,7 +2,9 @@ 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,
|
|
@@ -14,21 +16,78 @@ import {
|
|
|
14
16
|
googleDriveScopesAllowCapability,
|
|
15
17
|
} from "@opengeni/contracts/google-drive";
|
|
16
18
|
import {
|
|
19
|
+
isPersonalGitHubConnection,
|
|
20
|
+
PERSONAL_GITHUB_CONNECTION_SURFACE_ID,
|
|
21
|
+
PersonalGitHubConnectionMetadata,
|
|
22
|
+
} from "@opengeni/contracts/personal-github";
|
|
23
|
+
import {
|
|
24
|
+
getPersonalGitHubRepositorySelectionState,
|
|
17
25
|
getSessionTurnPersonalConnectionDelegations,
|
|
18
26
|
getConnectionMetadata,
|
|
19
27
|
getSocialConnection,
|
|
20
|
-
getWorkspaceGrant,
|
|
21
28
|
listConnectionsMetadata,
|
|
22
29
|
listSocialConnections,
|
|
30
|
+
resolvePersonalConnectionAuthoritySelectionOrigin,
|
|
31
|
+
namedSubjectHasLiveWorkspaceAuthority,
|
|
23
32
|
type Database,
|
|
24
33
|
type ResolveConnectionCredentialInput,
|
|
25
34
|
type ResolveConnectionCredentialResult,
|
|
26
35
|
} from "@opengeni/db";
|
|
36
|
+
import { personalGitHubRepositoryResources } from "./resources";
|
|
27
37
|
|
|
28
38
|
export type PersonalConnectionDelegationSource =
|
|
29
|
-
| {
|
|
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
|
+
}
|
|
30
49
|
| { kind: "turn"; sessionId: string; turnId: string }
|
|
31
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
|
+
}
|
|
32
91
|
export type AuthorizedSocialConnection = { connection: SocialConnection; subjectId: string | null };
|
|
33
92
|
export type AuthorizedAtlassianConnection = {
|
|
34
93
|
connection: ConnectionMetadata;
|
|
@@ -53,17 +112,48 @@ export function personalConnectionDelegationSourceForGrant(
|
|
|
53
112
|
): PersonalConnectionDelegationSource {
|
|
54
113
|
const callerSessionId = grant.metadata?.["sessionId"];
|
|
55
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`.
|
|
56
125
|
if (typeof callerSessionId === "string" && typeof callerTurnId === "string") {
|
|
57
126
|
return { kind: "turn", sessionId: callerSessionId, turnId: callerTurnId };
|
|
58
127
|
}
|
|
59
128
|
if (
|
|
60
129
|
grant.principalKind === "agent_attempt" ||
|
|
61
130
|
grant.principalKind === "service" ||
|
|
62
|
-
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
|
|
63
153
|
) {
|
|
64
154
|
return { kind: "none" };
|
|
65
155
|
}
|
|
66
|
-
return { kind: "subject", subjectId: grant.subjectId };
|
|
156
|
+
return { kind: "subject", subjectId: grant.subjectId, accountId: grant.accountId };
|
|
67
157
|
}
|
|
68
158
|
|
|
69
159
|
export async function authorizedSocialConnectionsForGrant(input: {
|
|
@@ -102,7 +192,13 @@ export async function authorizedSocialConnectionsForGrant(input: {
|
|
|
102
192
|
).filter((item) => item.connectionType === "social");
|
|
103
193
|
const personal: AuthorizedSocialConnection[] = [];
|
|
104
194
|
for (const delegation of delegations) {
|
|
105
|
-
if (
|
|
195
|
+
if (
|
|
196
|
+
!(await ownerStillBelongsToWorkspace(input.db, {
|
|
197
|
+
accountId: input.grant.accountId,
|
|
198
|
+
workspaceId: input.grant.workspaceId,
|
|
199
|
+
subjectId: delegation.ownerSubjectId,
|
|
200
|
+
}))
|
|
201
|
+
)
|
|
106
202
|
continue;
|
|
107
203
|
const connection = await getSocialConnection(
|
|
108
204
|
input.db,
|
|
@@ -159,7 +255,13 @@ export async function authorizedAtlassianConnectionsForGrant(input: {
|
|
|
159
255
|
).filter((item) => item.connectionType === "atlassian");
|
|
160
256
|
const personal: AuthorizedAtlassianConnection[] = [];
|
|
161
257
|
for (const delegation of delegations) {
|
|
162
|
-
if (
|
|
258
|
+
if (
|
|
259
|
+
!(await ownerStillBelongsToWorkspace(input.db, {
|
|
260
|
+
accountId: input.grant.accountId,
|
|
261
|
+
workspaceId: input.grant.workspaceId,
|
|
262
|
+
subjectId: delegation.ownerSubjectId,
|
|
263
|
+
}))
|
|
264
|
+
) {
|
|
163
265
|
continue;
|
|
164
266
|
}
|
|
165
267
|
const connection = await getConnectionMetadata(
|
|
@@ -212,13 +314,19 @@ export function personalConnectionDelegationsFromVisibleConnections(input: {
|
|
|
212
314
|
servers: McpServerConfig[];
|
|
213
315
|
subjectId: string;
|
|
214
316
|
connections: ConnectionMetadata[];
|
|
317
|
+
authoritySelections?: McpConnectionAuthoritySelection[];
|
|
318
|
+
rejectUnselectedActivatedConnections?: boolean;
|
|
215
319
|
}): McpPersonalConnectionDelegation[] {
|
|
216
320
|
const delegations: McpPersonalConnectionDelegation[] = [];
|
|
217
321
|
const connections = canonicalPersonalConnections(input.connections);
|
|
322
|
+
const selections = new Map(
|
|
323
|
+
(input.authoritySelections ?? []).map((selection) => [selection.serverId, selection]),
|
|
324
|
+
);
|
|
218
325
|
for (const server of input.servers) {
|
|
219
326
|
const ref = server.connectionRef;
|
|
220
327
|
if (!ref || ref.subjectScope !== "subject") continue;
|
|
221
|
-
const
|
|
328
|
+
const selection = selections.get(server.id);
|
|
329
|
+
const eligible = connections.filter(
|
|
222
330
|
(candidate) =>
|
|
223
331
|
candidate.subjectId === input.subjectId &&
|
|
224
332
|
candidate.status === "active" &&
|
|
@@ -226,22 +334,69 @@ export function personalConnectionDelegationsFromVisibleConnections(input: {
|
|
|
226
334
|
(!ref.kind || candidate.kind === ref.kind) &&
|
|
227
335
|
(!ref.connectionId || candidate.id === ref.connectionId),
|
|
228
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];
|
|
229
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
|
+
}
|
|
230
364
|
delegations.push({
|
|
231
365
|
serverId: server.id,
|
|
232
366
|
connectionId: connection.id,
|
|
367
|
+
...(selection ? { originWorkspaceId: connection.workspaceId } : {}),
|
|
233
368
|
ownerSubjectId: input.subjectId,
|
|
234
369
|
providerDomain: connection.providerDomain,
|
|
235
370
|
kind: connection.kind,
|
|
371
|
+
...(selection ? { userDelegation: selection.userDelegation } : {}),
|
|
236
372
|
});
|
|
237
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
|
+
}
|
|
238
381
|
return delegations;
|
|
239
382
|
}
|
|
240
383
|
|
|
241
384
|
export function personalConnectionDelegationsFromParent(input: {
|
|
242
385
|
servers: McpServerConfig[];
|
|
243
386
|
parentDelegations: McpPersonalConnectionDelegation[];
|
|
387
|
+
targetSessionId?: string;
|
|
388
|
+
rejectActivatedConnections?: boolean;
|
|
244
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
|
+
};
|
|
245
400
|
const mcp = input.servers.flatMap((server) => {
|
|
246
401
|
const ref = server.connectionRef;
|
|
247
402
|
if (!ref || ref.subjectScope !== "subject") return [];
|
|
@@ -251,19 +406,29 @@ export function personalConnectionDelegationsFromParent(input: {
|
|
|
251
406
|
sameProviderDomain(candidate.providerDomain, ref.providerDomain) &&
|
|
252
407
|
(!ref.kind || !candidate.kind || candidate.kind === ref.kind),
|
|
253
408
|
);
|
|
254
|
-
return delegation ? [{ ...delegation }] : [];
|
|
409
|
+
return delegation && childEligible(delegation) ? [{ ...delegation }] : [];
|
|
255
410
|
});
|
|
256
|
-
|
|
411
|
+
const projected = [
|
|
257
412
|
...mcp,
|
|
258
413
|
...input.parentDelegations
|
|
259
414
|
.filter(
|
|
260
415
|
(item) =>
|
|
261
|
-
item
|
|
262
|
-
item.connectionType === "
|
|
263
|
-
|
|
416
|
+
childEligible(item) &&
|
|
417
|
+
(item.connectionType === "social" ||
|
|
418
|
+
item.connectionType === "atlassian" ||
|
|
419
|
+
item.serverId === GOOGLE_DRIVE_PUBLICATION_SERVER_ID),
|
|
264
420
|
)
|
|
265
421
|
.map((item) => ({ ...item })),
|
|
266
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;
|
|
267
432
|
}
|
|
268
433
|
|
|
269
434
|
/**
|
|
@@ -275,7 +440,10 @@ export function personalConnectionDelegationsFromParent(input: {
|
|
|
275
440
|
export function googleDrivePublicationDelegationFromVisibleConnections(input: {
|
|
276
441
|
subjectId: string;
|
|
277
442
|
connections: ConnectionMetadata[];
|
|
443
|
+
authoritySelection?: McpConnectionAuthoritySelection;
|
|
444
|
+
rejectUnselectedActivatedConnection?: boolean;
|
|
278
445
|
}): McpPersonalConnectionDelegation | null {
|
|
446
|
+
const selection = input.authoritySelection;
|
|
279
447
|
const eligible = input.connections.filter((connection) => {
|
|
280
448
|
if (
|
|
281
449
|
connection.subjectId !== input.subjectId ||
|
|
@@ -294,14 +462,176 @@ export function googleDrivePublicationDelegationFromVisibleConnections(input: {
|
|
|
294
462
|
(!metadata.data.lifecycle || metadata.data.lifecycle.state === "active"),
|
|
295
463
|
);
|
|
296
464
|
});
|
|
297
|
-
|
|
298
|
-
|
|
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
|
+
}
|
|
299
498
|
return {
|
|
300
499
|
serverId: GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
301
500
|
connectionId: connection.id,
|
|
501
|
+
...(selection ? { originWorkspaceId: connection.workspaceId } : {}),
|
|
302
502
|
ownerSubjectId: input.subjectId,
|
|
303
503
|
providerDomain: connection.providerDomain,
|
|
304
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
|
+
},
|
|
305
635
|
};
|
|
306
636
|
}
|
|
307
637
|
|
|
@@ -315,10 +645,15 @@ export function personalConnectionDelegationsEqual(
|
|
|
315
645
|
const other = byServer.get(delegation.serverId);
|
|
316
646
|
return (
|
|
317
647
|
other?.connectionId === delegation.connectionId &&
|
|
648
|
+
other.originWorkspaceId === delegation.originWorkspaceId &&
|
|
318
649
|
other.ownerSubjectId === delegation.ownerSubjectId &&
|
|
319
650
|
sameProviderDomain(other.providerDomain, delegation.providerDomain) &&
|
|
320
651
|
other.kind === delegation.kind &&
|
|
321
|
-
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)
|
|
322
657
|
);
|
|
323
658
|
});
|
|
324
659
|
}
|
|
@@ -368,6 +703,12 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
368
703
|
resolveCredential: ConnectionCredentialResolver;
|
|
369
704
|
settings: Pick<Settings, "mcpServers">;
|
|
370
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
|
+
*/
|
|
371
712
|
ownerHasWorkspaceMembership: (subjectId: string) => Promise<boolean>;
|
|
372
713
|
}): ConnectionCredentialResolver {
|
|
373
714
|
return async (request) => {
|
|
@@ -390,7 +731,11 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
390
731
|
: publicationDelegations.length === 1
|
|
391
732
|
? publicationDelegations[0]!
|
|
392
733
|
: null;
|
|
393
|
-
if (
|
|
734
|
+
if (
|
|
735
|
+
!delegation ||
|
|
736
|
+
(!delegation.userDelegation &&
|
|
737
|
+
!(await input.ownerHasWorkspaceMembership(delegation.ownerSubjectId)))
|
|
738
|
+
) {
|
|
394
739
|
return personalAuthorityUnavailable(request);
|
|
395
740
|
}
|
|
396
741
|
effectiveRequest = {
|
|
@@ -415,16 +760,49 @@ export function withFrozenPersonalConnectionDelegations(input: {
|
|
|
415
760
|
export async function freezePersonalConnectionDelegations(input: {
|
|
416
761
|
db: Database;
|
|
417
762
|
workspaceId: string;
|
|
418
|
-
settings: Pick<Settings, "mcpServers"
|
|
763
|
+
settings: Pick<Settings, "mcpServers"> & Partial<Pick<Settings, "githubPersonalOauthEnabled">>;
|
|
419
764
|
tools: ToolRef[];
|
|
765
|
+
resources?: ResourceRef[];
|
|
420
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;
|
|
421
775
|
}): Promise<McpPersonalConnectionDelegation[]> {
|
|
422
776
|
const servers = selectedPersonalConnectionServers(input.settings, input.tools);
|
|
423
777
|
const includeFirstPartyConnections = input.tools.some((tool) => tool.id === "opengeni");
|
|
424
|
-
|
|
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") {
|
|
425
793
|
return [];
|
|
426
794
|
}
|
|
427
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
|
+
}
|
|
428
806
|
const inherited = personalConnectionDelegationsFromParent({
|
|
429
807
|
servers,
|
|
430
808
|
parentDelegations: await getSessionTurnPersonalConnectionDelegations(
|
|
@@ -433,30 +811,123 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
433
811
|
input.source.sessionId,
|
|
434
812
|
input.source.turnId,
|
|
435
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;
|
|
436
830
|
});
|
|
437
|
-
return includeFirstPartyConnections
|
|
438
|
-
? inherited
|
|
439
|
-
: inherited.filter(
|
|
440
|
-
(item) =>
|
|
441
|
-
item.connectionType !== "social" &&
|
|
442
|
-
item.connectionType !== "atlassian" &&
|
|
443
|
-
item.serverId !== GOOGLE_DRIVE_PUBLICATION_SERVER_ID,
|
|
444
|
-
);
|
|
445
831
|
}
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
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(
|
|
449
839
|
input.db,
|
|
450
840
|
input.workspaceId,
|
|
451
|
-
|
|
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
|
+
}),
|
|
452
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
|
+
}
|
|
453
904
|
const mcp = personalConnectionDelegationsFromVisibleConnections({
|
|
454
905
|
servers,
|
|
455
|
-
subjectId:
|
|
906
|
+
subjectId: ownerSubjectId,
|
|
456
907
|
connections: visibleConnections,
|
|
908
|
+
authoritySelections: (input.authoritySelections ?? []).filter((selection) =>
|
|
909
|
+
selectedMcpServerIds.has(selection.serverId),
|
|
910
|
+
),
|
|
911
|
+
...(input.rejectUnselectedActivatedConnections !== undefined
|
|
912
|
+
? { rejectUnselectedActivatedConnections: input.rejectUnselectedActivatedConnections }
|
|
913
|
+
: {}),
|
|
457
914
|
});
|
|
458
|
-
|
|
459
|
-
|
|
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
|
+
}
|
|
460
931
|
const visible = await listSocialConnections(input.db, input.workspaceId, 500, ownerSubjectId);
|
|
461
932
|
const latest = new Map<"x" | "reddit", (typeof visible)[number]>();
|
|
462
933
|
for (const connection of visible) {
|
|
@@ -470,18 +941,35 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
470
941
|
if (!prior || connection.updatedAt > prior.updatedAt)
|
|
471
942
|
latest.set(connection.provider, connection);
|
|
472
943
|
}
|
|
473
|
-
const personalAtlassian =
|
|
474
|
-
(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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;
|
|
483
970
|
return [
|
|
484
971
|
...mcp,
|
|
972
|
+
...(personalGitHub ? [personalGitHub] : []),
|
|
485
973
|
...(googleDrivePublication ? [googleDrivePublication] : []),
|
|
486
974
|
...[...latest.values()].map((connection) => ({
|
|
487
975
|
serverId: `social:${connection.provider}`,
|
|
@@ -491,13 +979,6 @@ export async function freezePersonalConnectionDelegations(input: {
|
|
|
491
979
|
kind: "oauth2" as const,
|
|
492
980
|
connectionType: "social" as const,
|
|
493
981
|
})),
|
|
494
|
-
...personalAtlassian
|
|
495
|
-
serverId: `atlassian:${connection.id}`,
|
|
496
|
-
connectionId: connection.id,
|
|
497
|
-
ownerSubjectId,
|
|
498
|
-
providerDomain: connection.providerDomain,
|
|
499
|
-
kind: connection.kind,
|
|
500
|
-
connectionType: "atlassian" as const,
|
|
501
|
-
})),
|
|
982
|
+
...personalAtlassian,
|
|
502
983
|
];
|
|
503
984
|
}
|