@opengeni/contracts 2.15.2 → 3.0.1-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/atlassian.js +10 -9
- package/dist/{chunk-NPYLOZAP.js → chunk-5LYFSZ7O.js} +12 -12
- package/dist/{chunk-QP3BIGSC.js → chunk-FBXZZSXA.js} +7668 -7654
- package/dist/chunk-FBXZZSXA.js.map +1 -0
- package/dist/chunk-FY5DZL7T.js +348 -0
- package/dist/chunk-FY5DZL7T.js.map +1 -0
- package/dist/{chunk-6TCVR53X.js → chunk-O5LEQHDM.js} +2 -2
- package/dist/chunk-P3HLDT77.js +59 -0
- package/dist/chunk-P3HLDT77.js.map +1 -0
- package/dist/chunk-TEMFILR7.js +143 -0
- package/dist/chunk-TEMFILR7.js.map +1 -0
- package/dist/connect.d.ts +325 -0
- package/dist/connect.js +182 -0
- package/dist/connect.js.map +1 -0
- package/dist/connection-authority.js +28 -334
- package/dist/connection-authority.js.map +1 -1
- package/dist/editable-artifact-codec-registry.js +2 -2
- package/dist/editable-artifact-live.js +2 -2
- package/dist/editable-artifact-serialized-commit.js +3 -3
- package/dist/editable-artifacts.js +24 -24
- package/dist/external-identities.d.ts +620 -0
- package/dist/external-identities.js +34 -0
- package/dist/external-identities.js.map +1 -0
- package/dist/google-drive.js +12 -11
- package/dist/google-drive.js.map +1 -1
- package/dist/host-mcp-bindings.d.ts +297 -0
- package/dist/host-mcp-bindings.js +195 -0
- package/dist/host-mcp-bindings.js.map +1 -0
- package/dist/index.d.ts +150 -72
- package/dist/index.js +89 -81
- package/dist/personal-github.js +10 -9
- package/dist/personal-github.js.map +1 -1
- package/dist/sandbox-snapshots.d.ts +18 -0
- package/package.json +13 -1
- package/src/connect.ts +202 -0
- package/src/external-identities.ts +213 -0
- package/src/host-mcp-bindings.ts +226 -0
- package/src/index.ts +165 -69
- package/src/sandbox-snapshots.ts +67 -3
- package/dist/chunk-QP3BIGSC.js.map +0 -1
- /package/dist/{chunk-NPYLOZAP.js.map → chunk-5LYFSZ7O.js.map} +0 -0
- /package/dist/{chunk-6TCVR53X.js.map → chunk-O5LEQHDM.js.map} +0 -0
package/src/connect.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { IntegrationInstanceKey, IntegrationSource } from "./index";
|
|
3
|
+
|
|
4
|
+
const opaqueId = z.string().min(1).max(512);
|
|
5
|
+
export const ConnectOwnership = z.enum(["personal", "workspace"]);
|
|
6
|
+
export type ConnectOwnership = z.infer<typeof ConnectOwnership>;
|
|
7
|
+
export const ConnectProvider = z
|
|
8
|
+
.object({
|
|
9
|
+
id: opaqueId,
|
|
10
|
+
label: z.string().min(1).max(256),
|
|
11
|
+
family: opaqueId,
|
|
12
|
+
readiness: z.enum(["available", "needs_configuration", "operator_only", "unsupported"]),
|
|
13
|
+
reason: z.string().max(1024).optional(),
|
|
14
|
+
ownership: z.array(ConnectOwnership).max(2),
|
|
15
|
+
setup: z
|
|
16
|
+
.array(
|
|
17
|
+
z.enum(["none", "oauth", "credentials", "device", "installation", "openapi", "graphql"]),
|
|
18
|
+
)
|
|
19
|
+
.max(7),
|
|
20
|
+
})
|
|
21
|
+
.strict();
|
|
22
|
+
export type ConnectProvider = z.infer<typeof ConnectProvider>;
|
|
23
|
+
export const ConnectAccount = z
|
|
24
|
+
.object({
|
|
25
|
+
id: opaqueId,
|
|
26
|
+
providerId: opaqueId,
|
|
27
|
+
version: z.number().int().positive().optional(),
|
|
28
|
+
label: z.string().max(256),
|
|
29
|
+
ownership: ConnectOwnership,
|
|
30
|
+
status: z.enum(["connected", "auth_needed", "disabled"]),
|
|
31
|
+
})
|
|
32
|
+
.strict();
|
|
33
|
+
export type ConnectAccount = z.infer<typeof ConnectAccount>;
|
|
34
|
+
export const ConnectResource = z
|
|
35
|
+
.object({ id: opaqueId, label: z.string().max(512), kind: opaqueId })
|
|
36
|
+
.strict();
|
|
37
|
+
export type ConnectResource = z.infer<typeof ConnectResource>;
|
|
38
|
+
export const ConnectNextAction = z.discriminatedUnion("type", [
|
|
39
|
+
z.object({ type: z.literal("authorize"), url: z.string().url().max(16_384) }).strict(),
|
|
40
|
+
z
|
|
41
|
+
.object({
|
|
42
|
+
type: z.literal("credentials"),
|
|
43
|
+
fields: z
|
|
44
|
+
.array(
|
|
45
|
+
z
|
|
46
|
+
.object({
|
|
47
|
+
name: opaqueId,
|
|
48
|
+
label: z.string().max(256),
|
|
49
|
+
required: z.boolean(),
|
|
50
|
+
secret: z.boolean(),
|
|
51
|
+
options: z
|
|
52
|
+
.array(z.object({ value: opaqueId, label: z.string().max(512) }).strict())
|
|
53
|
+
.max(1000)
|
|
54
|
+
.optional(),
|
|
55
|
+
})
|
|
56
|
+
.strict(),
|
|
57
|
+
)
|
|
58
|
+
.max(32),
|
|
59
|
+
})
|
|
60
|
+
.strict(),
|
|
61
|
+
z
|
|
62
|
+
.object({
|
|
63
|
+
type: z.literal("wait"),
|
|
64
|
+
pollAfterMs: z.number().int().min(250).max(60_000),
|
|
65
|
+
userCode: z.string().max(128).optional(),
|
|
66
|
+
verificationUrl: z.string().url().max(2048).optional(),
|
|
67
|
+
})
|
|
68
|
+
.strict(),
|
|
69
|
+
z
|
|
70
|
+
.object({ type: z.literal("select_account"), accounts: z.array(ConnectAccount).max(100) })
|
|
71
|
+
.strict(),
|
|
72
|
+
z
|
|
73
|
+
.object({
|
|
74
|
+
type: z.literal("select_resources"),
|
|
75
|
+
resources: z.array(ConnectResource).max(100),
|
|
76
|
+
cursor: z.string().max(2048).optional(),
|
|
77
|
+
})
|
|
78
|
+
.strict(),
|
|
79
|
+
z
|
|
80
|
+
.object({
|
|
81
|
+
type: z.literal("preview"),
|
|
82
|
+
previewId: opaqueId,
|
|
83
|
+
contentHash: opaqueId,
|
|
84
|
+
operations: z.array(ConnectResource).max(2000),
|
|
85
|
+
})
|
|
86
|
+
.strict(),
|
|
87
|
+
z.object({ type: z.literal("none") }).strict(),
|
|
88
|
+
]);
|
|
89
|
+
export type ConnectNextAction = z.infer<typeof ConnectNextAction>;
|
|
90
|
+
export const ConnectInstallationTarget = z
|
|
91
|
+
.object({
|
|
92
|
+
instanceKey: IntegrationInstanceKey,
|
|
93
|
+
displayName: z.string().min(1).max(200),
|
|
94
|
+
expectedInstanceVersion: z.number().int().positive().optional(),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
export type ConnectInstallationTarget = z.infer<typeof ConnectInstallationTarget>;
|
|
98
|
+
export const ConnectAttemptState = z.enum([
|
|
99
|
+
"ready",
|
|
100
|
+
"requires_user_action",
|
|
101
|
+
"credential_input",
|
|
102
|
+
"provider_wait",
|
|
103
|
+
"account_selection",
|
|
104
|
+
"resource_selection",
|
|
105
|
+
"preview",
|
|
106
|
+
"installing",
|
|
107
|
+
"connected_but_incomplete",
|
|
108
|
+
"complete",
|
|
109
|
+
"cancelled",
|
|
110
|
+
"expired",
|
|
111
|
+
"failed",
|
|
112
|
+
"uncertain",
|
|
113
|
+
]);
|
|
114
|
+
export const ConnectAttempt = z
|
|
115
|
+
.object({
|
|
116
|
+
id: opaqueId,
|
|
117
|
+
workspaceId: opaqueId,
|
|
118
|
+
providerId: opaqueId,
|
|
119
|
+
ownership: ConnectOwnership,
|
|
120
|
+
revision: z.number().int().positive(),
|
|
121
|
+
state: ConnectAttemptState,
|
|
122
|
+
credentialsCommitted: z.boolean(),
|
|
123
|
+
integrationInstalled: z.boolean(),
|
|
124
|
+
completionRequirement: z.enum(["connection", "integration", "provider_setup"]),
|
|
125
|
+
nextAction: ConnectNextAction,
|
|
126
|
+
expiresAt: z.string().datetime({ offset: true }),
|
|
127
|
+
account: ConnectAccount.optional(),
|
|
128
|
+
installationTarget: ConnectInstallationTarget.optional(),
|
|
129
|
+
source: IntegrationSource.optional(),
|
|
130
|
+
error: z
|
|
131
|
+
.object({ code: opaqueId, message: z.string().max(2048), retryable: z.boolean() })
|
|
132
|
+
.strict()
|
|
133
|
+
.optional(),
|
|
134
|
+
})
|
|
135
|
+
.strict()
|
|
136
|
+
.superRefine((value, context) => {
|
|
137
|
+
if (
|
|
138
|
+
value.state === "complete" &&
|
|
139
|
+
value.completionRequirement === "integration" &&
|
|
140
|
+
!value.integrationInstalled
|
|
141
|
+
) {
|
|
142
|
+
context.addIssue({
|
|
143
|
+
code: "custom",
|
|
144
|
+
message: "Complete setup requires an installed integration",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (
|
|
148
|
+
value.state === "complete" &&
|
|
149
|
+
value.completionRequirement === "connection" &&
|
|
150
|
+
!value.credentialsCommitted
|
|
151
|
+
) {
|
|
152
|
+
context.addIssue({
|
|
153
|
+
code: "custom",
|
|
154
|
+
message: "Connection setup requires committed credentials",
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (
|
|
158
|
+
["complete", "cancelled", "expired"].includes(value.state) &&
|
|
159
|
+
value.nextAction.type !== "none"
|
|
160
|
+
) {
|
|
161
|
+
context.addIssue({ code: "custom", message: "A terminal setup has no next action" });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
export type ConnectAttempt = z.infer<typeof ConnectAttempt>;
|
|
165
|
+
export const ConnectAdvance = z.discriminatedUnion("type", [
|
|
166
|
+
z
|
|
167
|
+
.object({
|
|
168
|
+
type: z.literal("credentials"),
|
|
169
|
+
values: z.record(z.string().max(256), z.string().max(16_384)),
|
|
170
|
+
})
|
|
171
|
+
.strict(),
|
|
172
|
+
z.object({ type: z.literal("account"), accountId: opaqueId }).strict(),
|
|
173
|
+
z.object({ type: z.literal("resources"), resourceIds: z.array(opaqueId).max(1000) }).strict(),
|
|
174
|
+
z
|
|
175
|
+
.object({
|
|
176
|
+
type: z.literal("install"),
|
|
177
|
+
previewId: opaqueId,
|
|
178
|
+
contentHash: opaqueId,
|
|
179
|
+
operationIds: z.array(opaqueId).max(2000),
|
|
180
|
+
})
|
|
181
|
+
.strict(),
|
|
182
|
+
z.object({ type: z.literal("retry") }).strict(),
|
|
183
|
+
]);
|
|
184
|
+
export type ConnectAdvance = z.infer<typeof ConnectAdvance>;
|
|
185
|
+
export const ConnectOperationRequest = z
|
|
186
|
+
.object({
|
|
187
|
+
expectedRevision: z.number().int().positive().safe(),
|
|
188
|
+
idempotencyKey: opaqueId,
|
|
189
|
+
})
|
|
190
|
+
.strict();
|
|
191
|
+
export const AdvanceConnectRequest = ConnectOperationRequest.extend({ action: ConnectAdvance });
|
|
192
|
+
export const BeginConnectRequest = z
|
|
193
|
+
.object({
|
|
194
|
+
providerId: opaqueId,
|
|
195
|
+
ownership: ConnectOwnership,
|
|
196
|
+
// URL parsing validates separately; never transform this original string.
|
|
197
|
+
returnUrl: z.string().min(1).max(4096),
|
|
198
|
+
idempotencyKey: opaqueId,
|
|
199
|
+
reconnectAccountId: opaqueId.optional(),
|
|
200
|
+
installationTarget: ConnectInstallationTarget.optional(),
|
|
201
|
+
})
|
|
202
|
+
.strict();
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { Permission } from "./permissions";
|
|
3
|
+
|
|
4
|
+
// External identifiers are opaque, case-sensitive strings. In particular a
|
|
5
|
+
// native-looking identifier is still host data, never a native subject claim.
|
|
6
|
+
// Bound bytes, not just JS code units: these values share a PostgreSQL B-tree
|
|
7
|
+
// index entry. Never normalize/truncate an identifier to make it fit, and reject
|
|
8
|
+
// text that the driver would replace or PostgreSQL cannot represent.
|
|
9
|
+
const encoder = new TextEncoder();
|
|
10
|
+
function opaqueText(maxBytes: number) {
|
|
11
|
+
return z
|
|
12
|
+
.string()
|
|
13
|
+
.min(1)
|
|
14
|
+
.max(maxBytes)
|
|
15
|
+
.refine(
|
|
16
|
+
(value) =>
|
|
17
|
+
!value.includes("\0") &&
|
|
18
|
+
!/[\uD800-\uDFFF]/u.test(value) &&
|
|
19
|
+
encoder.encode(value).byteLength <= maxBytes,
|
|
20
|
+
{
|
|
21
|
+
message: `Identifier must be valid PostgreSQL Unicode text of at most ${maxBytes} UTF-8 bytes`,
|
|
22
|
+
},
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const externalId = opaqueText(1024);
|
|
26
|
+
const source = opaqueText(200);
|
|
27
|
+
const externalSubject = z
|
|
28
|
+
.string()
|
|
29
|
+
.regex(/^external_user:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$(?![\s\S])/);
|
|
30
|
+
const nativeSubject = z
|
|
31
|
+
.string()
|
|
32
|
+
.min(1)
|
|
33
|
+
.max(1024)
|
|
34
|
+
.regex(/^user:[^\r\n]+$(?![\s\S])/);
|
|
35
|
+
export const ExternalIdentityReference = z
|
|
36
|
+
.object({
|
|
37
|
+
externalId,
|
|
38
|
+
source: source.default("default"),
|
|
39
|
+
})
|
|
40
|
+
.strict();
|
|
41
|
+
export type ExternalIdentityReference = z.infer<typeof ExternalIdentityReference>;
|
|
42
|
+
|
|
43
|
+
export const AddExternalWorkspaceMemberRequest = z
|
|
44
|
+
.object({
|
|
45
|
+
identity: ExternalIdentityReference,
|
|
46
|
+
permissions: z.array(Permission).min(1).max(Permission.options.length),
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
|
49
|
+
export type AddExternalWorkspaceMemberRequest = z.infer<typeof AddExternalWorkspaceMemberRequest>;
|
|
50
|
+
|
|
51
|
+
/** Uses the organization membership ID returned by lazy identity admission.
|
|
52
|
+
* Reactivation restores admission only, not revoked work or workspace grants. */
|
|
53
|
+
export const UpdateExternalIdentityMembershipRequest = z
|
|
54
|
+
.object({
|
|
55
|
+
kind: z.enum(["suspend", "reactivate", "offboard"]),
|
|
56
|
+
expectedAuthorizationRevision: z.number().int().positive().safe(),
|
|
57
|
+
operationId: z.string().uuid(),
|
|
58
|
+
reason: z.string().max(512).optional(),
|
|
59
|
+
})
|
|
60
|
+
.strict();
|
|
61
|
+
export type UpdateExternalIdentityMembershipRequest = z.infer<
|
|
62
|
+
typeof UpdateExternalIdentityMembershipRequest
|
|
63
|
+
>;
|
|
64
|
+
|
|
65
|
+
export const ExternalActorSelection = z.discriminatedUnion("mode", [
|
|
66
|
+
z.object({ mode: z.literal("external"), identity: ExternalIdentityReference }).strict(),
|
|
67
|
+
z
|
|
68
|
+
.object({
|
|
69
|
+
mode: z.literal("linked_native"),
|
|
70
|
+
identity: ExternalIdentityReference,
|
|
71
|
+
linkId: z.string().uuid(),
|
|
72
|
+
expectedLinkRevision: z.number().int().positive().safe(),
|
|
73
|
+
})
|
|
74
|
+
.strict(),
|
|
75
|
+
]);
|
|
76
|
+
export type ExternalActorSelection = z.infer<typeof ExternalActorSelection>;
|
|
77
|
+
|
|
78
|
+
export const ExternalIdentity = z
|
|
79
|
+
.object({
|
|
80
|
+
id: z.string().uuid(),
|
|
81
|
+
accountId: z.string().uuid(),
|
|
82
|
+
source,
|
|
83
|
+
externalId,
|
|
84
|
+
subjectId: externalSubject,
|
|
85
|
+
organizationMembershipId: z.string().uuid(),
|
|
86
|
+
personalWorkspaceId: z.string().uuid(),
|
|
87
|
+
status: z.enum(["active", "disabled", "revoked"]),
|
|
88
|
+
authorizationRevision: z.number().int().positive().safe(),
|
|
89
|
+
})
|
|
90
|
+
.strict();
|
|
91
|
+
export type ExternalIdentity = z.infer<typeof ExternalIdentity>;
|
|
92
|
+
|
|
93
|
+
// Audit attribution, not authentication. Only the canonical access resolver
|
|
94
|
+
// may establish provenance; accepting this JSON from a browser proves nothing.
|
|
95
|
+
export const ExternalActorAttribution = z
|
|
96
|
+
.object({
|
|
97
|
+
accountId: z.string().uuid(),
|
|
98
|
+
authenticatingApiKeyId: z.string().uuid(),
|
|
99
|
+
externalIdentityId: z.string().uuid(),
|
|
100
|
+
externalSubjectId: externalSubject,
|
|
101
|
+
externalAuthorizationRevision: z.number().int().positive().safe(),
|
|
102
|
+
effectiveSubjectId: z.string().min(1).max(1024),
|
|
103
|
+
actingMode: z.enum(["external", "linked_native"]),
|
|
104
|
+
linkId: z.string().uuid().optional(),
|
|
105
|
+
linkRevision: z.number().int().positive().safe().optional(),
|
|
106
|
+
})
|
|
107
|
+
.strict()
|
|
108
|
+
.superRefine((value, context) => {
|
|
109
|
+
if (value.actingMode === "external") {
|
|
110
|
+
if (
|
|
111
|
+
value.effectiveSubjectId !== value.externalSubjectId ||
|
|
112
|
+
value.linkId !== undefined ||
|
|
113
|
+
value.linkRevision !== undefined
|
|
114
|
+
) {
|
|
115
|
+
context.addIssue({
|
|
116
|
+
code: "custom",
|
|
117
|
+
message: "External mode cannot delegate native identity",
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
} else if (
|
|
121
|
+
!value.linkId ||
|
|
122
|
+
!value.linkRevision ||
|
|
123
|
+
!nativeSubject.safeParse(value.effectiveSubjectId).success
|
|
124
|
+
) {
|
|
125
|
+
context.addIssue({
|
|
126
|
+
code: "custom",
|
|
127
|
+
message: "Linked mode requires exact native delegation provenance",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
export type ExternalActorAttribution = z.infer<typeof ExternalActorAttribution>;
|
|
132
|
+
|
|
133
|
+
/** Server-signed/encrypted continuation data, never sufficient live authority. */
|
|
134
|
+
export const ExternalActorContinuation = z
|
|
135
|
+
.object({
|
|
136
|
+
identity: ExternalIdentityReference,
|
|
137
|
+
actor: ExternalActorAttribution,
|
|
138
|
+
})
|
|
139
|
+
.strict();
|
|
140
|
+
export type ExternalActorContinuation = z.infer<typeof ExternalActorContinuation>;
|
|
141
|
+
|
|
142
|
+
export const ExternalIdentityLink = z
|
|
143
|
+
.object({
|
|
144
|
+
id: z.string().uuid(),
|
|
145
|
+
accountId: z.string().uuid(),
|
|
146
|
+
externalIdentityId: z.string().uuid(),
|
|
147
|
+
externalIdentity: ExternalIdentityReference.optional(),
|
|
148
|
+
nativeSubjectId: nativeSubject.nullable(),
|
|
149
|
+
status: z.enum(["pending", "active", "revoked", "expired"]),
|
|
150
|
+
revision: z.number().int().positive().safe(),
|
|
151
|
+
permissions: z.array(Permission).max(Permission.options.length),
|
|
152
|
+
expiresAt: z.string().datetime({ offset: true }).nullable(),
|
|
153
|
+
})
|
|
154
|
+
.strict()
|
|
155
|
+
.refine(
|
|
156
|
+
(value) => value.status !== "active" || value.nativeSubjectId !== null,
|
|
157
|
+
"An active link requires a confirmed native identity",
|
|
158
|
+
);
|
|
159
|
+
export type ExternalIdentityLink = z.infer<typeof ExternalIdentityLink>;
|
|
160
|
+
|
|
161
|
+
export const ExternalIdentityLinkPage = z
|
|
162
|
+
.object({
|
|
163
|
+
links: z.array(ExternalIdentityLink).max(50),
|
|
164
|
+
nextCursor: z.string().uuid().nullable(),
|
|
165
|
+
})
|
|
166
|
+
.strict();
|
|
167
|
+
export type ExternalIdentityLinkPage = z.infer<typeof ExternalIdentityLinkPage>;
|
|
168
|
+
|
|
169
|
+
/** A native confirmation may narrow this request, never broaden it. The
|
|
170
|
+
* challenge is delivered once to the host and is not an authentication token. */
|
|
171
|
+
export const BeginExternalIdentityLinkRequest = z
|
|
172
|
+
.object({
|
|
173
|
+
permissions: z.array(Permission).min(1).max(Permission.options.length),
|
|
174
|
+
expiresAt: z.string().datetime({ offset: true }).nullable().default(null),
|
|
175
|
+
})
|
|
176
|
+
.strict();
|
|
177
|
+
export type BeginExternalIdentityLinkRequest = z.input<typeof BeginExternalIdentityLinkRequest>;
|
|
178
|
+
export const ConfirmExternalIdentityLinkRequest = z
|
|
179
|
+
.object({
|
|
180
|
+
challenge: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
181
|
+
expectedRevision: z.number().int().positive().safe(),
|
|
182
|
+
permissions: z.array(Permission).min(1).max(Permission.options.length),
|
|
183
|
+
})
|
|
184
|
+
.strict();
|
|
185
|
+
export type ConfirmExternalIdentityLinkRequest = z.infer<typeof ConfirmExternalIdentityLinkRequest>;
|
|
186
|
+
export const BeginExternalIdentityLinkResponse = z
|
|
187
|
+
.object({
|
|
188
|
+
link: ExternalIdentityLink,
|
|
189
|
+
challenge: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
190
|
+
confirmBefore: z.string().datetime({ offset: true }),
|
|
191
|
+
})
|
|
192
|
+
.strict();
|
|
193
|
+
export type BeginExternalIdentityLinkResponse = z.infer<typeof BeginExternalIdentityLinkResponse>;
|
|
194
|
+
export const ExternalIdentityLinkPreview = z
|
|
195
|
+
.object({
|
|
196
|
+
link: ExternalIdentityLink,
|
|
197
|
+
externalIdentity: ExternalIdentityReference,
|
|
198
|
+
nativeSubjectId: nativeSubject,
|
|
199
|
+
organizationId: z.string().uuid(),
|
|
200
|
+
})
|
|
201
|
+
.strict();
|
|
202
|
+
export type ExternalIdentityLinkPreview = z.infer<typeof ExternalIdentityLinkPreview>;
|
|
203
|
+
|
|
204
|
+
/** Immutable accepted-work restriction, not a browser credential. Original key
|
|
205
|
+
* identity remains audit-only; durable use validates the live link and members. */
|
|
206
|
+
export const ExternalLinkWorkSnapshot = z
|
|
207
|
+
.object({
|
|
208
|
+
identity: ExternalIdentityReference,
|
|
209
|
+
actor: ExternalActorAttribution.refine((value) => value.actingMode === "linked_native"),
|
|
210
|
+
permissions: z.array(Permission).max(Permission.options.length),
|
|
211
|
+
})
|
|
212
|
+
.strict();
|
|
213
|
+
export type ExternalLinkWorkSnapshot = z.infer<typeof ExternalLinkWorkSnapshot>;
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export { HostMcpCreateSelections, type HostMcpCreateSelection } from "./index";
|
|
3
|
+
import { McpServerConnectionRef, SessionTenancyVisibility } from "./index";
|
|
4
|
+
import {
|
|
5
|
+
ConnectionUseAuthoritySnapshot,
|
|
6
|
+
IssueConnectionUseGrantRequest,
|
|
7
|
+
} from "./connection-authority";
|
|
8
|
+
import { ExternalIdentity } from "./external-identities";
|
|
9
|
+
export const HostMcpOwnerSubject = z.union([
|
|
10
|
+
ExternalIdentity.shape.subjectId,
|
|
11
|
+
z
|
|
12
|
+
.string()
|
|
13
|
+
.max(4096)
|
|
14
|
+
.regex(/^user:[^\r\n\u0000]+$/),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
/** Credential-free, immutable destination and host connection selection. */
|
|
18
|
+
export const HostMcpBindingDefinition = z
|
|
19
|
+
.object({
|
|
20
|
+
serverId: z.string().min(1).max(256),
|
|
21
|
+
destinationUrl: z
|
|
22
|
+
.string()
|
|
23
|
+
.url()
|
|
24
|
+
.max(4096)
|
|
25
|
+
.refine((value) => {
|
|
26
|
+
const url = new URL(value);
|
|
27
|
+
return url.protocol === "https:" && !url.username && !url.password && !url.hash;
|
|
28
|
+
}, "Host binding requires an HTTPS destination without userinfo or fragment")
|
|
29
|
+
.transform((value) => new URL(value).toString()),
|
|
30
|
+
connectionRef: McpServerConnectionRef,
|
|
31
|
+
})
|
|
32
|
+
.strict()
|
|
33
|
+
.superRefine((value, context) => {
|
|
34
|
+
if (value.connectionRef.hostBinding)
|
|
35
|
+
context.addIssue({
|
|
36
|
+
code: "custom",
|
|
37
|
+
path: ["connectionRef", "hostBinding"],
|
|
38
|
+
message: "A binding definition cannot reference another binding",
|
|
39
|
+
});
|
|
40
|
+
if (value.connectionRef.authoritySource !== "host" || !value.connectionRef.connectionId)
|
|
41
|
+
context.addIssue({
|
|
42
|
+
code: "custom",
|
|
43
|
+
path: ["connectionRef"],
|
|
44
|
+
message: "Explicit host connection authority is required",
|
|
45
|
+
});
|
|
46
|
+
if (new TextEncoder().encode(JSON.stringify(value)).byteLength > 65_536)
|
|
47
|
+
context.addIssue({ code: "custom", message: "Host binding definition exceeds 64 KiB" });
|
|
48
|
+
});
|
|
49
|
+
export type HostMcpBindingDefinition = z.infer<typeof HostMcpBindingDefinition>;
|
|
50
|
+
|
|
51
|
+
export const HostMcpBinding = z
|
|
52
|
+
.object({
|
|
53
|
+
id: z.string().uuid(),
|
|
54
|
+
accountId: z.string().uuid(),
|
|
55
|
+
workspaceId: z.string().uuid(),
|
|
56
|
+
ownerSubjectId: HostMcpOwnerSubject,
|
|
57
|
+
authorizationRevision: z.number().int().positive().safe(),
|
|
58
|
+
generation: z.number().int().positive().safe(),
|
|
59
|
+
status: z.enum(["active", "revoked"]),
|
|
60
|
+
definition: HostMcpBindingDefinition,
|
|
61
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
62
|
+
revokedAt: z.string().datetime({ offset: true }).nullable(),
|
|
63
|
+
})
|
|
64
|
+
.strict();
|
|
65
|
+
export type HostMcpBinding = z.infer<typeof HostMcpBinding>;
|
|
66
|
+
|
|
67
|
+
export const CreateHostMcpBindingRequest = z
|
|
68
|
+
.object({
|
|
69
|
+
operationId: z.string().uuid(),
|
|
70
|
+
definition: HostMcpBindingDefinition,
|
|
71
|
+
})
|
|
72
|
+
.strict();
|
|
73
|
+
export type CreateHostMcpBindingRequest = z.infer<typeof CreateHostMcpBindingRequest>;
|
|
74
|
+
|
|
75
|
+
/** Internal owner-authorized issuance; never itself proves caller identity. */
|
|
76
|
+
const HostMcpDelegationGrant = IssueConnectionUseGrantRequest.superRefine((value, context) => {
|
|
77
|
+
if (
|
|
78
|
+
Boolean(value.sessionId) !== (value.expectedAuthorityEpoch != null) ||
|
|
79
|
+
(value.expectedAuthorityEpoch != null && !Number.isSafeInteger(value.expectedAuthorityEpoch))
|
|
80
|
+
) {
|
|
81
|
+
context.addIssue({
|
|
82
|
+
code: "custom",
|
|
83
|
+
message: "Host session grants require a matching safe authority epoch",
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
export const IssueHostMcpDelegationRequest = z
|
|
88
|
+
.object({
|
|
89
|
+
operationId: z.string().uuid(),
|
|
90
|
+
bindingId: z.string().uuid(),
|
|
91
|
+
expectedBindingGeneration: z.number().int().positive().safe(),
|
|
92
|
+
grant: HostMcpDelegationGrant,
|
|
93
|
+
})
|
|
94
|
+
.strict();
|
|
95
|
+
export type IssueHostMcpDelegationRequest = z.infer<typeof IssueHostMcpDelegationRequest>;
|
|
96
|
+
|
|
97
|
+
export const HostMcpDelegation = z
|
|
98
|
+
.object({
|
|
99
|
+
id: z.string().uuid(),
|
|
100
|
+
accountId: z.string().uuid(),
|
|
101
|
+
workspaceId: z.string().uuid(),
|
|
102
|
+
ownerSubjectId: HostMcpOwnerSubject,
|
|
103
|
+
ownerAuthorizationRevision: z.number().int().positive().safe(),
|
|
104
|
+
bindingId: z.string().uuid(),
|
|
105
|
+
bindingGeneration: z.number().int().positive().safe(),
|
|
106
|
+
grant: HostMcpDelegationGrant,
|
|
107
|
+
generation: z.number().int().positive().safe(),
|
|
108
|
+
status: z.enum(["active", "revoked"]),
|
|
109
|
+
createdAt: z.string().datetime({ offset: true }),
|
|
110
|
+
revokedAt: z.string().datetime({ offset: true }).nullable(),
|
|
111
|
+
})
|
|
112
|
+
.strict();
|
|
113
|
+
export type HostMcpDelegation = z.infer<typeof HostMcpDelegation>;
|
|
114
|
+
|
|
115
|
+
/** Internal admission evidence, not a caller-supplied permission or credential.
|
|
116
|
+
* Persistence must construct this from live authenticated authority and freeze
|
|
117
|
+
* it with accepted work. Runtime use must independently revalidate that work,
|
|
118
|
+
* membership, binding and delegation. No API key is retained as execution rights.
|
|
119
|
+
*/
|
|
120
|
+
export const HostMcpAcceptedAuthority = z
|
|
121
|
+
.object({
|
|
122
|
+
version: z.literal(1),
|
|
123
|
+
accountId: z.string().uuid(),
|
|
124
|
+
workspaceId: z.string().uuid(),
|
|
125
|
+
targetSessionId: z.string().uuid(),
|
|
126
|
+
targetSessionVisibility: SessionTenancyVisibility,
|
|
127
|
+
targetSessionAuthorityEpoch: z.number().int().positive().safe(),
|
|
128
|
+
acceptedWork: ConnectionUseAuthoritySnapshot.shape.acceptedWork,
|
|
129
|
+
bindingId: z.string().uuid(),
|
|
130
|
+
bindingGeneration: z.number().int().positive().safe(),
|
|
131
|
+
definition: HostMcpBindingDefinition,
|
|
132
|
+
ownerSubjectId: HostMcpOwnerSubject,
|
|
133
|
+
ownerOrganizationMembershipId: z.string().uuid(),
|
|
134
|
+
ownerMembershipAuthorizationRevision: z.number().int().positive().safe(),
|
|
135
|
+
delegationId: z.string().uuid(),
|
|
136
|
+
delegationGeneration: z.number().int().positive().safe(),
|
|
137
|
+
scheduledOrigin: z
|
|
138
|
+
.object({
|
|
139
|
+
taskId: z.string().uuid(),
|
|
140
|
+
taskAuthorityRevision: z.number().int().positive().safe(),
|
|
141
|
+
runId: z.string().uuid(),
|
|
142
|
+
})
|
|
143
|
+
.strict()
|
|
144
|
+
.optional(),
|
|
145
|
+
source: z.discriminatedUnion("kind", [
|
|
146
|
+
z.object({ kind: z.literal("direct") }).strict(),
|
|
147
|
+
z.object({ kind: z.literal("scheduled_task") }).strict(),
|
|
148
|
+
z
|
|
149
|
+
.object({
|
|
150
|
+
kind: z.literal("child_turn"),
|
|
151
|
+
sessionId: z.string().uuid(),
|
|
152
|
+
turnId: z.string().uuid(),
|
|
153
|
+
})
|
|
154
|
+
.strict(),
|
|
155
|
+
z
|
|
156
|
+
.object({
|
|
157
|
+
kind: z.literal("inherited_turn"),
|
|
158
|
+
sessionId: z.string().uuid(),
|
|
159
|
+
turnId: z.string().uuid(),
|
|
160
|
+
})
|
|
161
|
+
.strict(),
|
|
162
|
+
]),
|
|
163
|
+
})
|
|
164
|
+
.strict()
|
|
165
|
+
.superRefine((value, context) => {
|
|
166
|
+
if (
|
|
167
|
+
(value.source.kind === "direct" && value.scheduledOrigin) ||
|
|
168
|
+
(value.source.kind === "scheduled_task" &&
|
|
169
|
+
(!value.scheduledOrigin ||
|
|
170
|
+
value.acceptedWork.kind !== "scheduled_task" ||
|
|
171
|
+
value.scheduledOrigin.taskId !== value.acceptedWork.taskId ||
|
|
172
|
+
value.scheduledOrigin.runId !== value.acceptedWork.runId ||
|
|
173
|
+
value.scheduledOrigin.taskAuthorityRevision !== value.acceptedWork.taskAuthorityRevision))
|
|
174
|
+
)
|
|
175
|
+
context.addIssue({
|
|
176
|
+
code: "custom",
|
|
177
|
+
message: "Scheduled host authority must retain its exact run origin",
|
|
178
|
+
});
|
|
179
|
+
if (
|
|
180
|
+
value.acceptedWork.kind === "turn" &&
|
|
181
|
+
value.source.kind === "inherited_turn" &&
|
|
182
|
+
value.acceptedWork.turnId === value.source.turnId
|
|
183
|
+
) {
|
|
184
|
+
context.addIssue({
|
|
185
|
+
code: "custom",
|
|
186
|
+
path: ["source"],
|
|
187
|
+
message: "Accepted work cannot inherit from itself",
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
value.acceptedWork.kind === "scheduled_task" &&
|
|
192
|
+
!Number.isSafeInteger(value.acceptedWork.taskAuthorityRevision)
|
|
193
|
+
) {
|
|
194
|
+
context.addIssue({
|
|
195
|
+
code: "custom",
|
|
196
|
+
path: ["acceptedWork"],
|
|
197
|
+
message: "Task authority revision must be a safe integer",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (new TextEncoder().encode(JSON.stringify(value)).byteLength > 65_536) {
|
|
201
|
+
context.addIssue({ code: "custom", message: "Host accepted authority exceeds 64 KiB" });
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
export type HostMcpAcceptedAuthority = z.infer<typeof HostMcpAcceptedAuthority>;
|
|
205
|
+
|
|
206
|
+
/** Credential-free selection frozen on one immutable scheduled-task revision. */
|
|
207
|
+
export const HostMcpTaskAuthority = z
|
|
208
|
+
.object({
|
|
209
|
+
version: z.literal(1),
|
|
210
|
+
accountId: z.string().uuid(),
|
|
211
|
+
workspaceId: z.string().uuid(),
|
|
212
|
+
taskId: z.string().uuid(),
|
|
213
|
+
taskAuthorityRevision: z.number().int().positive().safe(),
|
|
214
|
+
taskExecutionDigest: z.string().regex(/^[0-9a-f]{64}$/),
|
|
215
|
+
ownerSubjectId: HostMcpOwnerSubject,
|
|
216
|
+
ownerOrganizationMembershipId: z.string().uuid(),
|
|
217
|
+
ownerMembershipAuthorizationRevision: z.number().int().positive().safe(),
|
|
218
|
+
bindingId: z.string().uuid(),
|
|
219
|
+
bindingGeneration: z.number().int().positive().safe(),
|
|
220
|
+
delegationId: z.string().uuid(),
|
|
221
|
+
delegationGeneration: z.number().int().positive().safe(),
|
|
222
|
+
definition: HostMcpBindingDefinition,
|
|
223
|
+
context: SessionTenancyVisibility,
|
|
224
|
+
})
|
|
225
|
+
.strict();
|
|
226
|
+
export type HostMcpTaskAuthority = z.infer<typeof HostMcpTaskAuthority>;
|