@xfey/tutti 0.1.119 → 0.1.121
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/approvals/decision-command.d.ts +2 -2
- package/dist/collaboration-state/clarification-snapshot.d.ts +9 -9
- package/dist/collaboration-state/task-compile-context.d.ts +1 -1
- package/dist/control-plane/workflows/openai.d.ts +3 -4
- package/dist/control-plane/workflows/openai.js +3 -2
- package/dist/providers/openai/app-server/invocation-coordinator.d.ts +1 -0
- package/dist/providers/openai/app-server/invocation-coordinator.js +4 -1
- package/dist/providers/openai/app-server/invocation-deadline.d.ts +3 -0
- package/dist/providers/openai/app-server/invocation-deadline.js +15 -0
- package/dist/providers/openai/app-server/read-only-procedure.d.ts +4 -2
- package/dist/providers/openai/app-server/read-only-procedure.js +16 -0
- package/dist/providers/openai/app-server/workspace-write-run.d.ts +4 -2
- package/dist/providers/openai/app-server/workspace-write-run.js +16 -0
- package/dist/providers/openai/chat-assistant.d.ts +3 -4
- package/dist/providers/openai/chat-assistant.js +2 -1
- package/dist/providers/openai/execution-access.d.ts +17 -0
- package/dist/providers/openai/execution-access.js +13 -0
- package/dist/providers/openai/sdk-procedure-runner.d.ts +2 -2
- package/dist/providers/openai/sdk-procedure-runner.js +17 -0
- package/dist/run-pipeline/artifact-applicability.d.ts +3 -4
- package/dist/run-pipeline/artifact-applicability.js +2 -1
- package/dist/run-pipeline/openai.d.ts +3 -4
- package/dist/run-pipeline/openai.js +3 -2
- package/dist/run-pipeline/task-run-invocation.js +6 -12
- package/dist/server-shell/cli/cli.js +3 -0
- package/dist/server-shell/cli/host-lifecycle.js +1 -0
- package/dist/server-shell/cli/host-server-runtime.d.ts +1 -0
- package/dist/server-shell/cli/host-server-runtime.js +83 -34
- package/dist/server-shell/cli/launch-command.js +1 -1
- package/dist/server-shell/cli/launch.d.ts +7 -3
- package/dist/server-shell/cli/launch.js +23 -4
- package/dist/server-shell/cli/machine-local.d.ts +9 -0
- package/dist/server-shell/cli/machine-local.js +60 -6
- package/dist/server-shell/cli/project-resolver.d.ts +2 -2
- package/dist/server-shell/cli/project-resolver.js +6 -3
- package/dist/server-shell/cli/relay-registration.js +38 -1
- package/dist/server-shell/http/routes/local-control.d.ts +1 -1
- package/dist/server-shell/http/routes/local-control.js +2 -46
- package/dist/server-shell/http/routes/project-api/openapi-workspace-routes.d.ts +45 -137
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +45 -137
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +24 -136
- package/dist/server-shell/http/routes/project-api/schemas.js +4 -58
- package/dist/server-shell/http/routes/project-api/types.d.ts +3 -0
- package/dist/server-shell/local-console/managed-project-routes.d.ts +9 -0
- package/dist/server-shell/local-console/managed-project-routes.js +120 -0
- package/dist/server-shell/local-console/managed-project-service.d.ts +141 -0
- package/dist/server-shell/local-console/managed-project-service.js +158 -0
- package/dist/server-shell/local-console/operation-coordinator.d.ts +1 -1
- package/dist/server-shell/local-console/package-update-hosts.js +2 -1
- package/dist/server-shell/local-console/project-service.d.ts +4 -1
- package/dist/server-shell/local-console/project-service.js +62 -14
- package/dist/server-shell/local-console/server.js +22 -2
- package/dist/server-shell/managed-access/execution-binding.d.ts +9 -0
- package/dist/server-shell/managed-access/execution-binding.js +22 -0
- package/dist/server-shell/managed-access/existing-project.d.ts +15 -0
- package/dist/server-shell/managed-access/existing-project.js +41 -0
- package/dist/server-shell/managed-access/legacy-records.d.ts +2 -0
- package/dist/server-shell/managed-access/legacy-records.js +24 -0
- package/dist/server-shell/managed-access/model-control-client.d.ts +65 -0
- package/dist/server-shell/managed-access/model-control-client.js +112 -0
- package/dist/server-shell/managed-access/private-store.d.ts +19 -0
- package/dist/server-shell/managed-access/private-store.js +275 -0
- package/dist/server-shell/managed-access/project-provider.d.ts +18 -0
- package/dist/server-shell/managed-access/project-provider.js +30 -0
- package/dist/server-shell/managed-access/records.d.ts +103 -0
- package/dist/server-shell/managed-access/records.js +74 -0
- package/dist/server-shell/managed-access/runners.d.ts +11 -0
- package/dist/server-shell/managed-access/runners.js +103 -0
- package/dist/server-shell/managed-access/runtime-records.d.ts +106 -0
- package/dist/server-shell/managed-access/runtime-records.js +65 -0
- package/dist/server-shell/managed-access/runtime.d.ts +48 -0
- package/dist/server-shell/managed-access/runtime.js +300 -0
- package/dist/server-shell/managed-access/state.d.ts +53 -0
- package/dist/server-shell/managed-access/state.js +292 -0
- package/dist/server-shell/managed-access/transport.d.ts +45 -0
- package/dist/server-shell/managed-access/transport.js +39 -0
- package/dist/server-shell/managed-access/workspace.d.ts +10 -0
- package/dist/server-shell/managed-access/workspace.js +69 -0
- package/dist/server-shell/smoke/e2e-client.d.ts +8 -3
- package/dist/server-shell/smoke/e2e-client.js +11 -11
- package/node_modules/@tutti/relay-client/dist/host-control.d.ts +4 -1
- package/node_modules/@tutti/relay-client/dist/host-control.js +26 -3
- package/node_modules/@tutti/relay-client/dist/index.d.ts +1 -0
- package/node_modules/@tutti/relay-client/dist/index.js +1 -0
- package/node_modules/@tutti/relay-client/dist/project-authority.d.ts +70 -0
- package/node_modules/@tutti/relay-client/dist/project-authority.js +139 -0
- package/node_modules/@tutti/shared/dist/ids/index.d.ts +7 -0
- package/node_modules/@tutti/shared/dist/ids/index.js +7 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.d.ts +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/index.js +1 -0
- package/node_modules/@tutti/shared/dist/schemas/api/managed-account.d.ts +34 -0
- package/node_modules/@tutti/shared/dist/schemas/api/managed-account.js +40 -0
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +7 -4
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +5 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +3 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.d.ts +26 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.js +40 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.d.ts +5 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/index.js +5 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.d.ts +12 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-account.js +13 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.d.ts +17 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-models.js +17 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.d.ts +113 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.js +84 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.d.ts +114 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.js +102 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-B6lCCHBq.js → homepage-motion-scene-WfLyzcCG.js} +1 -1
- package/web/assets/{index-DsYi7AhN.css → index-ATsYiBZs.css} +1 -1
- package/web/assets/index-o9YCtzEd.js +70 -0
- package/web/index.html +2 -2
- package/web/assets/index-DepsYMLl.js +0 -70
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { Value } from "@sinclair/typebox/value";
|
|
5
|
+
import { createIdempotencyKey, createPrefixedId, createProjectId, ID_PREFIXES, isPrefixedId, } from "@tutti/shared/ids";
|
|
6
|
+
import { HostDelegationProjectionSchema, PROJECT_TRIAL_POLICY, } from "@tutti/shared/schemas/internal";
|
|
7
|
+
import { ensureManagedPrivateRoot, isWithin, ManagedLocalStateError, ManagedPrivateStore, } from "./private-store.js";
|
|
8
|
+
import { parseInstallation, parseJournal, NativeProjectPointerSchema, } from "./records.js";
|
|
9
|
+
const DRAFT_TTL_MS = 10 * 60_000;
|
|
10
|
+
const secret = () => randomBytes(32).toString("base64url");
|
|
11
|
+
const hash = (value) => `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
12
|
+
export function managedRelayOrigin(input) {
|
|
13
|
+
try {
|
|
14
|
+
const url = new URL(input);
|
|
15
|
+
if (url.username ||
|
|
16
|
+
url.password ||
|
|
17
|
+
url.search ||
|
|
18
|
+
url.hash ||
|
|
19
|
+
url.pathname !== "/" ||
|
|
20
|
+
!(url.protocol === "https:" ||
|
|
21
|
+
(url.protocol === "http:" && ["127.0.0.1", "[::1]"].includes(url.hostname))))
|
|
22
|
+
throw new Error();
|
|
23
|
+
return url.origin;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new ManagedLocalStateError("managed_relay_origin_invalid");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function originDirectory(tuttiHome, relayUrl) {
|
|
30
|
+
return join(tuttiHome, "credentials", "managed", createHash("sha256").update(managedRelayOrigin(relayUrl)).digest("hex"));
|
|
31
|
+
}
|
|
32
|
+
// BYOK reads do not create a managed installation, journal or mutex.
|
|
33
|
+
export function findNativeManagedProject(options) {
|
|
34
|
+
if (!isPrefixedId(options.projectId, ID_PREFIXES.project))
|
|
35
|
+
throw new ManagedLocalStateError("managed_project_id_invalid");
|
|
36
|
+
let origin;
|
|
37
|
+
try {
|
|
38
|
+
origin = new URL(options.relayUrl).origin;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
if (!existsSync(join(originDirectory(options.tuttiHome, origin), `project-${options.projectId}.json`)))
|
|
44
|
+
return null;
|
|
45
|
+
const state = new NativeManagedState(options);
|
|
46
|
+
return { state, journal: state.project(options.projectId) };
|
|
47
|
+
}
|
|
48
|
+
export class NativeManagedState {
|
|
49
|
+
origin;
|
|
50
|
+
files;
|
|
51
|
+
constructor(options) {
|
|
52
|
+
this.origin = managedRelayOrigin(options.relayUrl);
|
|
53
|
+
this.files = new ManagedPrivateStore(ensureManagedPrivateRoot(options.tuttiHome, createHash("sha256").update(this.origin).digest("hex")));
|
|
54
|
+
}
|
|
55
|
+
installation(now = new Date()) {
|
|
56
|
+
return this.files.transaction(() => {
|
|
57
|
+
const saved = this.files.read("installation.json");
|
|
58
|
+
if (saved !== null)
|
|
59
|
+
return parseInstallation(saved, this.origin);
|
|
60
|
+
const value = {
|
|
61
|
+
version: 1,
|
|
62
|
+
relay_origin: this.origin,
|
|
63
|
+
installation_ref: createPrefixedId(ID_PREFIXES.hostInstallation),
|
|
64
|
+
secret: secret(),
|
|
65
|
+
created_at: now.toISOString(),
|
|
66
|
+
};
|
|
67
|
+
this.files.write("installation.json", value, true);
|
|
68
|
+
return parseInstallation(this.files.read("installation.json"), this.origin);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
createDraft(options) {
|
|
72
|
+
const now = options.now ?? new Date();
|
|
73
|
+
let workspace;
|
|
74
|
+
try {
|
|
75
|
+
workspace = realpathSync(options.workspaceRoot);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
throw new ManagedLocalStateError("managed_workspace_unavailable");
|
|
79
|
+
}
|
|
80
|
+
if (isWithin(workspace, this.files.root) || isWithin(this.files.root, workspace))
|
|
81
|
+
throw new ManagedLocalStateError("managed_state_in_workspace");
|
|
82
|
+
const installation = this.installation(now);
|
|
83
|
+
const requestRef = options.requestRef ?? createIdempotencyKey();
|
|
84
|
+
const name = this.journalName(requestRef);
|
|
85
|
+
return this.files.transaction(() => {
|
|
86
|
+
const saved = this.files.read(name);
|
|
87
|
+
if (saved !== null) {
|
|
88
|
+
const record = this.readJournal(requestRef);
|
|
89
|
+
if (record.workspace_root !== workspace ||
|
|
90
|
+
record.display_name !== options.displayName ||
|
|
91
|
+
record.description !== options.description ||
|
|
92
|
+
record.surface !== options.surface ||
|
|
93
|
+
(options.projectId !== undefined && record.project_id !== options.projectId) ||
|
|
94
|
+
record.existing_relay_project_ref !== options.existing?.relayProjectRef ||
|
|
95
|
+
record.previous_request_ref !== options.existing?.previousRequestRef ||
|
|
96
|
+
(options.existing !== undefined &&
|
|
97
|
+
record.host_registration_secret !== options.existing.hostSecret) ||
|
|
98
|
+
record.installation_ref !== installation.installation_ref)
|
|
99
|
+
throw new ManagedLocalStateError("managed_creation_mismatch");
|
|
100
|
+
return record;
|
|
101
|
+
}
|
|
102
|
+
const record = parseJournal({
|
|
103
|
+
version: 2,
|
|
104
|
+
request_ref: requestRef,
|
|
105
|
+
project_id: options.projectId ?? createProjectId(),
|
|
106
|
+
relay_origin: this.origin,
|
|
107
|
+
installation_ref: installation.installation_ref,
|
|
108
|
+
workspace_root: workspace,
|
|
109
|
+
display_name: options.displayName,
|
|
110
|
+
...(options.description === undefined ? {} : { description: options.description }),
|
|
111
|
+
surface: options.surface,
|
|
112
|
+
...(options.existing === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: {
|
|
115
|
+
existing_relay_project_ref: options.existing.relayProjectRef,
|
|
116
|
+
...(options.existing.previousRequestRef === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: { previous_request_ref: options.existing.previousRequestRef }),
|
|
119
|
+
}),
|
|
120
|
+
handoff_token: secret(),
|
|
121
|
+
host_registration_secret: options.existing?.hostSecret ?? secret(),
|
|
122
|
+
created_at: now.toISOString(),
|
|
123
|
+
expires_at: new Date(now.getTime() + DRAFT_TTL_MS).toISOString(),
|
|
124
|
+
state: "awaiting_authorization",
|
|
125
|
+
}, this.origin, requestRef);
|
|
126
|
+
this.files.write(name, record, true);
|
|
127
|
+
return record;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
read(requestRef) {
|
|
131
|
+
return this.files.transaction(() => this.readJournal(requestRef));
|
|
132
|
+
}
|
|
133
|
+
// Called only inside the private-store mutex. Upgrade metadata atomically;
|
|
134
|
+
// never rotate proofs, extend consent or restore revoked runtime credentials.
|
|
135
|
+
readJournal(requestRef) {
|
|
136
|
+
const name = this.journalName(requestRef);
|
|
137
|
+
const saved = this.files.read(name);
|
|
138
|
+
const row = parseJournal(saved, this.origin, requestRef);
|
|
139
|
+
if (saved && typeof saved === "object" && "version" in saved && saved.version === 1)
|
|
140
|
+
this.files.write(name, row);
|
|
141
|
+
return row;
|
|
142
|
+
}
|
|
143
|
+
attachProject(requestRef, now = new Date()) {
|
|
144
|
+
this.bootstrap(requestRef, now);
|
|
145
|
+
return this.files.transaction(() => {
|
|
146
|
+
const row = this.readJournal(requestRef);
|
|
147
|
+
const name = `project-${row.project_id}.json`;
|
|
148
|
+
const old = this.files.read(name);
|
|
149
|
+
if (old !== null &&
|
|
150
|
+
(!Value.Check(NativeProjectPointerSchema, old) ||
|
|
151
|
+
old.project_id !== row.project_id ||
|
|
152
|
+
(old.request_ref !== requestRef && old.request_ref !== row.previous_request_ref)))
|
|
153
|
+
throw new ManagedLocalStateError("managed_project_binding_conflict");
|
|
154
|
+
if (old === null ||
|
|
155
|
+
(Value.Check(NativeProjectPointerSchema, old) && old.request_ref !== requestRef))
|
|
156
|
+
this.files.write(name, { version: 1, project_id: row.project_id, request_ref: requestRef }, old === null);
|
|
157
|
+
return row;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
project(projectId) {
|
|
161
|
+
if (!isPrefixedId(projectId, ID_PREFIXES.project))
|
|
162
|
+
throw new ManagedLocalStateError("managed_project_id_invalid");
|
|
163
|
+
return this.files.transaction(() => {
|
|
164
|
+
const pointer = this.files.read(`project-${projectId}.json`);
|
|
165
|
+
if (!Value.Check(NativeProjectPointerSchema, pointer) || pointer.project_id !== projectId)
|
|
166
|
+
throw new ManagedLocalStateError("managed_project_binding_invalid");
|
|
167
|
+
const row = this.readJournal(pointer.request_ref);
|
|
168
|
+
if (row.project_id !== projectId || (row.state !== "validated" && row.state !== "registered"))
|
|
169
|
+
throw new ManagedLocalStateError("managed_project_binding_invalid");
|
|
170
|
+
return row;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
preparationBody(requestRef) {
|
|
174
|
+
const row = this.read(requestRef), installation = this.installation();
|
|
175
|
+
if (row.installation_ref !== installation.installation_ref)
|
|
176
|
+
throw new ManagedLocalStateError("managed_installation_changed");
|
|
177
|
+
return {
|
|
178
|
+
request_ref: row.request_ref,
|
|
179
|
+
project_id: row.project_id,
|
|
180
|
+
surface: row.surface,
|
|
181
|
+
...(row.existing_relay_project_ref === undefined
|
|
182
|
+
? {}
|
|
183
|
+
: { existing_relay_project_ref: row.existing_relay_project_ref }),
|
|
184
|
+
installation_ref: row.installation_ref,
|
|
185
|
+
installation_secret_hash: hash(installation.secret),
|
|
186
|
+
handoff_token_hash: hash(row.handoff_token),
|
|
187
|
+
host_registration_secret_hash: hash(row.host_registration_secret),
|
|
188
|
+
trial_consent: PROJECT_TRIAL_POLICY,
|
|
189
|
+
expires_before: row.expires_at,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
proof(requestRef) {
|
|
193
|
+
const row = this.read(requestRef), installation = this.installation();
|
|
194
|
+
if (row.installation_ref !== installation.installation_ref)
|
|
195
|
+
throw new ManagedLocalStateError("managed_installation_changed");
|
|
196
|
+
return {
|
|
197
|
+
request_ref: row.request_ref,
|
|
198
|
+
project_id: row.project_id,
|
|
199
|
+
installation_ref: row.installation_ref,
|
|
200
|
+
installation_secret: installation.secret,
|
|
201
|
+
handoff_token: row.handoff_token,
|
|
202
|
+
host_registration_secret: row.host_registration_secret,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// Only native, Relay-verified responses may call these transitions. Browser input is not evidence.
|
|
206
|
+
prepared(requestRef, receipt, now = new Date()) {
|
|
207
|
+
return this.update(requestRef, (row) => {
|
|
208
|
+
if (row.project_id !== receipt.project_id ||
|
|
209
|
+
row.installation_ref !== receipt.installation_ref ||
|
|
210
|
+
row.surface !== receipt.surface ||
|
|
211
|
+
!Number.isFinite(Date.parse(receipt.expires_at)) ||
|
|
212
|
+
Date.parse(receipt.expires_at) > Date.parse(row.expires_at))
|
|
213
|
+
throw new ManagedLocalStateError("managed_preparation_mismatch");
|
|
214
|
+
if (row.state !== "awaiting_authorization") {
|
|
215
|
+
if (row.creation_ref !== receipt.creation_ref)
|
|
216
|
+
throw new ManagedLocalStateError("managed_preparation_mismatch");
|
|
217
|
+
return row;
|
|
218
|
+
}
|
|
219
|
+
if (receipt.expires_at <= now.toISOString())
|
|
220
|
+
throw new ManagedLocalStateError("managed_preparation_expired");
|
|
221
|
+
return {
|
|
222
|
+
...row,
|
|
223
|
+
state: "prepared",
|
|
224
|
+
creation_ref: receipt.creation_ref,
|
|
225
|
+
expires_at: receipt.expires_at,
|
|
226
|
+
};
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
validation(requestRef, outcome) {
|
|
230
|
+
return this.update(requestRef, (row) => {
|
|
231
|
+
if (row.state === "registered" || row.state === "validated")
|
|
232
|
+
return row;
|
|
233
|
+
if (row.state === "validation_failed") {
|
|
234
|
+
if (outcome !== row.state)
|
|
235
|
+
throw new ManagedLocalStateError("managed_validation_conflict");
|
|
236
|
+
return row;
|
|
237
|
+
}
|
|
238
|
+
if (row.state !== "prepared" && row.state !== "validation_unknown")
|
|
239
|
+
throw new ManagedLocalStateError("managed_creation_not_prepared");
|
|
240
|
+
return { ...row, state: outcome };
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
bootstrap(requestRef, now = new Date()) {
|
|
244
|
+
const row = this.read(requestRef), installation = this.installation();
|
|
245
|
+
if ((row.state !== "validated" && row.state !== "registered") ||
|
|
246
|
+
(row.state !== "registered" && row.expires_at <= now.toISOString()))
|
|
247
|
+
throw new ManagedLocalStateError("managed_creation_not_validated");
|
|
248
|
+
if (row.installation_ref !== installation.installation_ref)
|
|
249
|
+
throw new ManagedLocalStateError("managed_installation_changed");
|
|
250
|
+
return {
|
|
251
|
+
kind: "managed_project_authority_v2",
|
|
252
|
+
creation_ref: row.creation_ref,
|
|
253
|
+
installation_ref: installation.installation_ref,
|
|
254
|
+
installation_secret: installation.secret,
|
|
255
|
+
handoff_token: row.handoff_token,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
registered(requestRef, authority) {
|
|
259
|
+
return this.update(requestRef, (row) => {
|
|
260
|
+
if (!Value.Check(HostDelegationProjectionSchema, authority) ||
|
|
261
|
+
authority.installation_ref !== row.installation_ref ||
|
|
262
|
+
(row.existing_relay_project_ref !== undefined &&
|
|
263
|
+
authority.relay_project_ref !== row.existing_relay_project_ref))
|
|
264
|
+
throw new ManagedLocalStateError("managed_registration_mismatch");
|
|
265
|
+
if (row.state === "registered") {
|
|
266
|
+
if (row.authority.delegation_ref !== authority.delegation_ref ||
|
|
267
|
+
row.authority.grant_ref !== authority.grant_ref ||
|
|
268
|
+
row.authority.relay_project_ref !== authority.relay_project_ref)
|
|
269
|
+
throw new ManagedLocalStateError("managed_registration_mismatch");
|
|
270
|
+
return row;
|
|
271
|
+
}
|
|
272
|
+
if (row.state !== "validated")
|
|
273
|
+
throw new ManagedLocalStateError("managed_creation_not_validated");
|
|
274
|
+
return { ...row, state: "registered", authority };
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
update(requestRef, change) {
|
|
278
|
+
return this.files.transaction(() => {
|
|
279
|
+
const name = this.journalName(requestRef);
|
|
280
|
+
const row = parseJournal(this.files.read(name), this.origin, requestRef);
|
|
281
|
+
const updated = parseJournal(change(row), this.origin, requestRef);
|
|
282
|
+
this.files.write(name, updated);
|
|
283
|
+
return updated;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
journalName(requestRef) {
|
|
287
|
+
if (!isPrefixedId(requestRef, ID_PREFIXES.idempotency))
|
|
288
|
+
throw new ManagedLocalStateError("managed_request_ref_invalid");
|
|
289
|
+
return `${requestRef}.json`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { NativeManagedState } from "./state.js";
|
|
2
|
+
export declare function reconcileNativePreparation(options: {
|
|
3
|
+
state: NativeManagedState;
|
|
4
|
+
requestRef: string;
|
|
5
|
+
fetchImpl?: typeof fetch;
|
|
6
|
+
now?: Date;
|
|
7
|
+
signal?: AbortSignal;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
authority?: {
|
|
10
|
+
state: "active";
|
|
11
|
+
delegation_ref: string;
|
|
12
|
+
grant_ref: string;
|
|
13
|
+
installation_ref: string;
|
|
14
|
+
relay_project_ref: string;
|
|
15
|
+
expires_at: null;
|
|
16
|
+
funding_kind: "trial";
|
|
17
|
+
model_access: "runtime_authority_required" | "not_enabled";
|
|
18
|
+
};
|
|
19
|
+
account_ref: string;
|
|
20
|
+
request_ref: string;
|
|
21
|
+
creation: {
|
|
22
|
+
relay_project_ref?: string;
|
|
23
|
+
state: "revoked" | "expired" | "prepared" | "activated";
|
|
24
|
+
installation_ref: string;
|
|
25
|
+
surface: "standalone" | "feishu";
|
|
26
|
+
expires_at: string;
|
|
27
|
+
project_id: string;
|
|
28
|
+
creation_ref: string;
|
|
29
|
+
};
|
|
30
|
+
}>;
|
|
31
|
+
export declare function validateNativePreparation(options: {
|
|
32
|
+
state: NativeManagedState;
|
|
33
|
+
requestRef: string;
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
}): Promise<{
|
|
37
|
+
state: "failed" | "unknown" | "started" | "succeeded";
|
|
38
|
+
installation_ref: string;
|
|
39
|
+
reason_code: "validation_started" | "validation_succeeded" | "validation_route_unavailable" | "validation_http_rejected" | "validation_response_invalid" | "validation_transport_unknown" | "validation_interrupted";
|
|
40
|
+
project_id: string;
|
|
41
|
+
creation_ref: string;
|
|
42
|
+
route_ref: string;
|
|
43
|
+
http_status: number | null;
|
|
44
|
+
}>;
|
|
45
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readNativeCreationAuthority, validateNativeCreationAuthority } from "@tutti/relay-client";
|
|
2
|
+
import { ManagedLocalStateError } from "./private-store.js";
|
|
3
|
+
// The browser never supplies a trusted receipt or account identity to the native journal.
|
|
4
|
+
export async function reconcileNativePreparation(options) {
|
|
5
|
+
const result = await readNativeCreationAuthority({
|
|
6
|
+
relayUrl: options.state.origin,
|
|
7
|
+
body: options.state.proof(options.requestRef),
|
|
8
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
9
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
10
|
+
});
|
|
11
|
+
if (result.creation.state === "expired" || result.creation.state === "revoked")
|
|
12
|
+
throw new ManagedLocalStateError(`managed_preparation_${result.creation.state}`);
|
|
13
|
+
options.state.prepared(options.requestRef, result.creation, options.now ?? new Date());
|
|
14
|
+
if (result.authority !== undefined)
|
|
15
|
+
options.state.registered(options.requestRef, result.authority);
|
|
16
|
+
return result;
|
|
17
|
+
}
|
|
18
|
+
export async function validateNativePreparation(options) {
|
|
19
|
+
const result = await validateNativeCreationAuthority({
|
|
20
|
+
relayUrl: options.state.origin,
|
|
21
|
+
body: options.state.proof(options.requestRef),
|
|
22
|
+
...(options.fetchImpl === undefined ? {} : { fetchImpl: options.fetchImpl }),
|
|
23
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
24
|
+
});
|
|
25
|
+
const row = options.state.read(options.requestRef);
|
|
26
|
+
if (!("creation_ref" in row) ||
|
|
27
|
+
row.creation_ref !== result.creation_ref ||
|
|
28
|
+
row.project_id !== result.project_id ||
|
|
29
|
+
row.installation_ref !== result.installation_ref)
|
|
30
|
+
throw new ManagedLocalStateError("managed_validation_mismatch");
|
|
31
|
+
if (result.state === "succeeded")
|
|
32
|
+
options.state.validation(options.requestRef, "validated");
|
|
33
|
+
else if (result.state === "unknown" || result.state === "started")
|
|
34
|
+
options.state.validation(options.requestRef, "validation_unknown");
|
|
35
|
+
else
|
|
36
|
+
options.state.validation(options.requestRef, "validation_failed");
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { NativeManagedState } from "./state.js";
|
|
2
|
+
export declare function acquireManagedWorkspaceLease(tuttiHome: string, workspaceRoot: string): () => void;
|
|
3
|
+
export declare function materializeManagedWorkspace(options: {
|
|
4
|
+
state: NativeManagedState;
|
|
5
|
+
requestRef: string;
|
|
6
|
+
tuttiHome: string;
|
|
7
|
+
environment: NodeJS.ProcessEnv;
|
|
8
|
+
now?: () => Date;
|
|
9
|
+
}): Promise<import("../cli/launch.js").LaunchPreparationResult>;
|
|
10
|
+
//# sourceMappingURL=workspace.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
import { prepareLaunchProject, resolveLaunchLocalContext } from "../cli/launch.js";
|
|
4
|
+
import { readExistingManagedTarget } from "./existing-project.js";
|
|
5
|
+
import { ensureManagedPrivateRoot, ManagedLocalStateError, ManagedPrivateStore, } from "./private-store.js";
|
|
6
|
+
// A workspace can be selected by different drafts and Relay origins. Serialize by
|
|
7
|
+
// canonical folder in a machine-wide namespace, not by project or creation ID.
|
|
8
|
+
export function acquireManagedWorkspaceLease(tuttiHome, workspaceRoot) {
|
|
9
|
+
const files = new ManagedPrivateStore(ensureManagedPrivateRoot(tuttiHome, createHash("sha256").update("tutti-managed-workspace-locks-v1").digest("hex")));
|
|
10
|
+
return files.acquireExecutionLease(`workspace-${createHash("sha256").update(realpathSync(workspaceRoot)).digest("hex")}`);
|
|
11
|
+
}
|
|
12
|
+
// Native-only materialization after verified validation. Does not start a Host or
|
|
13
|
+
// write a placeholder BYOK credential. Resuming the same draft reuses its identity.
|
|
14
|
+
export async function materializeManagedWorkspace(options) {
|
|
15
|
+
const now = options.now ?? (() => new Date());
|
|
16
|
+
options.state.bootstrap(options.requestRef, now());
|
|
17
|
+
const row = options.state.read(options.requestRef);
|
|
18
|
+
const unlock = acquireManagedWorkspaceLease(options.tuttiHome, row.workspace_root);
|
|
19
|
+
try {
|
|
20
|
+
let workspacePath = row.workspace_root;
|
|
21
|
+
if (row.existing_relay_project_ref !== undefined) {
|
|
22
|
+
const target = readExistingManagedTarget({
|
|
23
|
+
tuttiHome: options.tuttiHome,
|
|
24
|
+
relayOrigin: options.state.origin,
|
|
25
|
+
projectId: row.project_id,
|
|
26
|
+
workspaceRoot: row.workspace_root,
|
|
27
|
+
surface: row.surface,
|
|
28
|
+
requireStopped: row.state !== "registered",
|
|
29
|
+
});
|
|
30
|
+
if (target.relayProjectRef !== row.existing_relay_project_ref ||
|
|
31
|
+
target.hostSecret !== row.host_registration_secret)
|
|
32
|
+
throw new ManagedLocalStateError("managed_existing_project_mismatch");
|
|
33
|
+
workspacePath = target.workspacePath;
|
|
34
|
+
}
|
|
35
|
+
const environment = {
|
|
36
|
+
...options.environment,
|
|
37
|
+
TUTTI_HOME: options.tuttiHome,
|
|
38
|
+
TUTTI_RELAY_URL: options.state.origin,
|
|
39
|
+
};
|
|
40
|
+
const local = resolveLaunchLocalContext({
|
|
41
|
+
workspacePath,
|
|
42
|
+
env: environment,
|
|
43
|
+
});
|
|
44
|
+
if (realpathSync(local.workspace_root) !== row.workspace_root ||
|
|
45
|
+
(local.existing_identity.kind === "present" &&
|
|
46
|
+
local.existing_identity.project_id !== row.project_id))
|
|
47
|
+
throw new ManagedLocalStateError("managed_workspace_identity_changed");
|
|
48
|
+
// Persist the recovery pointer before Git/binding mutation. A crash never leaves
|
|
49
|
+
// an apparently-BYOK project where the managed creation reservation was lost.
|
|
50
|
+
options.state.attachProject(options.requestRef, now());
|
|
51
|
+
return await prepareLaunchProject({
|
|
52
|
+
workspacePath,
|
|
53
|
+
env: environment,
|
|
54
|
+
yes: true,
|
|
55
|
+
projectDisplayName: row.display_name,
|
|
56
|
+
...(row.description === undefined ? {} : { projectDescription: row.description }),
|
|
57
|
+
projectSurface: row.surface,
|
|
58
|
+
reservedIdentity: {
|
|
59
|
+
projectId: row.project_id,
|
|
60
|
+
hostRegistrationSecret: row.host_registration_secret,
|
|
61
|
+
},
|
|
62
|
+
now,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
unlock();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=workspace.js.map
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
-
import { type HostProjectApiClient, type
|
|
1
|
+
import { type HostProjectApiClient, type EmailLoginChallengeProjection, type LoginResponse, type OpenProjectResponse, type RelayApiClient, type TuttiApiFetch, type AcceptJoinResponse, type JoinEntryProjection, type TuttiApiResponse } from "@tutti/api-client";
|
|
2
2
|
import type { RelayProjectRef } from "@tutti/shared/ids";
|
|
3
|
+
export type SmokeEmailLoginInput = {
|
|
4
|
+
email: string;
|
|
5
|
+
display_name?: string;
|
|
6
|
+
readCode: (challenge: EmailLoginChallengeProjection) => Promise<string>;
|
|
7
|
+
};
|
|
3
8
|
export type E2ESmokeLogSink = (line: string) => void;
|
|
4
9
|
export type E2ESmokeClientOptions = {
|
|
5
10
|
relayBaseUrl: string;
|
|
@@ -20,7 +25,7 @@ export type E2ESmokeSession = {
|
|
|
20
25
|
};
|
|
21
26
|
export type LoginJoinAndOpenWorkspaceInput = {
|
|
22
27
|
joinToken: string;
|
|
23
|
-
login
|
|
28
|
+
login: SmokeEmailLoginInput;
|
|
24
29
|
};
|
|
25
30
|
export type LoginJoinAndOpenWorkspaceResult = {
|
|
26
31
|
joinEntry: JoinEntryProjection;
|
|
@@ -53,7 +58,7 @@ export declare class E2ESmokeCookieJar {
|
|
|
53
58
|
private storeSetCookieHeader;
|
|
54
59
|
}
|
|
55
60
|
export declare function createE2ESmokeSession(options: E2ESmokeClientOptions): E2ESmokeSession;
|
|
56
|
-
export declare function loginRelaySmokeAccount(session: E2ESmokeSession, input
|
|
61
|
+
export declare function loginRelaySmokeAccount(session: E2ESmokeSession, input: SmokeEmailLoginInput): Promise<LoginResponse>;
|
|
57
62
|
export declare function loginJoinAndOpenWorkspace(session: E2ESmokeSession, input: LoginJoinAndOpenWorkspaceInput): Promise<LoginJoinAndOpenWorkspaceResult>;
|
|
58
63
|
export declare function pollE2ESmoke<TValue>(options: E2ESmokePollOptions<TValue>): Promise<TValue>;
|
|
59
64
|
//# sourceMappingURL=e2e-client.d.ts.map
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
2
2
|
import { createHostProjectApiClientFromRelay, createRelayApiClientFromRelay, } from "@tutti/api-client";
|
|
3
3
|
import { serializeRuntimeLogLine } from "@tutti/shared/utils";
|
|
4
|
-
const DEFAULT_SMOKE_LOGIN_PAYLOAD = {
|
|
5
|
-
email: "tutti-smoke@example.invalid",
|
|
6
|
-
display_name: "Tutti Smoke",
|
|
7
|
-
};
|
|
8
4
|
const DEFAULT_POLL_TIMEOUT_MS = 30_000;
|
|
9
5
|
const DEFAULT_POLL_INTERVAL_MS = 500;
|
|
10
6
|
export class E2ESmokeFlowError extends Error {
|
|
@@ -160,12 +156,6 @@ function emitSmokeLog(session, stage, fields = {}) {
|
|
|
160
156
|
now: session.now,
|
|
161
157
|
}));
|
|
162
158
|
}
|
|
163
|
-
function loginPayload(input) {
|
|
164
|
-
return {
|
|
165
|
-
...DEFAULT_SMOKE_LOGIN_PAYLOAD,
|
|
166
|
-
...(input ?? {}),
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
159
|
function readRelayProjectRefFromJoin(joinEntry) {
|
|
170
160
|
if (joinEntry.relay_project !== undefined) {
|
|
171
161
|
return joinEntry.relay_project.relay_project_ref;
|
|
@@ -216,7 +206,17 @@ export function createE2ESmokeSession(options) {
|
|
|
216
206
|
};
|
|
217
207
|
}
|
|
218
208
|
export async function loginRelaySmokeAccount(session, input) {
|
|
219
|
-
const
|
|
209
|
+
const start = await session.relay.startEmailLogin({ email: input.email }, { now: session.now });
|
|
210
|
+
if (start.result === undefined)
|
|
211
|
+
throw new Error("Email login challenge was not returned");
|
|
212
|
+
const code = await input.readCode(start.result.challenge);
|
|
213
|
+
const response = await session.relay.verifyEmailLogin({
|
|
214
|
+
challenge_ref: start.result.challenge.challenge_ref,
|
|
215
|
+
code,
|
|
216
|
+
}, { now: session.now });
|
|
217
|
+
if (response.result?.account.profile_complete === false && input.display_name !== undefined) {
|
|
218
|
+
await session.relay.completeAccountProfile({ display_name: input.display_name }, { now: session.now });
|
|
219
|
+
}
|
|
220
220
|
emitSmokeLog(session, "relay_login", {
|
|
221
221
|
disposition: response.disposition.kind,
|
|
222
222
|
account_ref: response.result?.account.account_ref,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type HostConnectionRef, type IdempotencyKey, type ProjectId, type RelayProjectRef, type RelayUploadRef } from "@tutti/shared/ids";
|
|
2
2
|
import type { ProjectSurfaceProfile } from "@tutti/shared/domain";
|
|
3
3
|
import type { RelayUploadPurpose, Sha256Digest } from "@tutti/shared/schemas/api";
|
|
4
|
-
import { type FeishuSetupProjection, type PlatformOutboundHandoffResponse, type PlatformOutboundIntent } from "@tutti/shared/schemas/internal";
|
|
4
|
+
import { type HostDelegationProjection, type ManagedProjectBootstrap, type FeishuSetupProjection, type PlatformOutboundHandoffResponse, type PlatformOutboundIntent } from "@tutti/shared/schemas/internal";
|
|
5
5
|
export type RelayHostControlFetch = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
6
6
|
export type RelayProjectProjection = {
|
|
7
7
|
relay_project_ref: RelayProjectRef;
|
|
@@ -25,6 +25,7 @@ export type JoinTokenProjection = {
|
|
|
25
25
|
visibility: "visible_once" | "not_recoverable";
|
|
26
26
|
};
|
|
27
27
|
export type RegisterRelayHostConnectionPayload = {
|
|
28
|
+
managed_bootstrap?: ManagedProjectBootstrap;
|
|
28
29
|
project_identity: {
|
|
29
30
|
source: "git_config";
|
|
30
31
|
project_id: ProjectId;
|
|
@@ -64,6 +65,7 @@ export type RegisterRelayHostConnectionPayload = {
|
|
|
64
65
|
};
|
|
65
66
|
};
|
|
66
67
|
export type RegisterRelayHostConnectionResult = {
|
|
68
|
+
managed_authority?: HostDelegationProjection;
|
|
67
69
|
relay_project: RelayProjectProjection;
|
|
68
70
|
host_connection: HostConnectionProjection;
|
|
69
71
|
join_token?: JoinTokenProjection;
|
|
@@ -152,6 +154,7 @@ export type DeliverRelayPlatformOutboundOptions = {
|
|
|
152
154
|
signal?: AbortSignal;
|
|
153
155
|
};
|
|
154
156
|
export type RegisterRelayHostConnectionOptions = {
|
|
157
|
+
managedBootstrap?: ManagedProjectBootstrap;
|
|
155
158
|
relayUrl: string;
|
|
156
159
|
projectId: ProjectId;
|
|
157
160
|
hostRegistrationSecret: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createIdempotencyKey, } from "@tutti/shared/ids";
|
|
2
|
-
import { isPlatformOutboundHandoffResponse, } from "@tutti/shared/schemas/internal";
|
|
2
|
+
import { isPlatformOutboundHandoffResponse, isHostDelegationProjection, isManagedProjectBootstrap, } from "@tutti/shared/schemas/internal";
|
|
3
3
|
export class RelayHostControlClientError extends Error {
|
|
4
4
|
code;
|
|
5
5
|
retryable;
|
|
@@ -91,6 +91,12 @@ function createRegisterPayload(options) {
|
|
|
91
91
|
account_handoff_token: options.accountHandoffToken,
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
+
if (options.managedBootstrap !== undefined) {
|
|
95
|
+
if (options.accountHandoffToken !== undefined ||
|
|
96
|
+
!isManagedProjectBootstrap(options.managedBootstrap))
|
|
97
|
+
throw new RelayHostControlClientError("relay_registration_failed", "Managed Host authority input is invalid", false);
|
|
98
|
+
payload.managed_bootstrap = options.managedBootstrap;
|
|
99
|
+
}
|
|
94
100
|
return payload;
|
|
95
101
|
}
|
|
96
102
|
function createRefreshJoinTokenPayload(options) {
|
|
@@ -199,6 +205,10 @@ function assertRegistrationResponse(value) {
|
|
|
199
205
|
throw new RelayHostControlClientError("relay_registration_failed", "Relay host registration response is invalid", false);
|
|
200
206
|
}
|
|
201
207
|
const dispositionKind = stringField(value.disposition, "kind");
|
|
208
|
+
if (isRecord(value.result) &&
|
|
209
|
+
value.result.managed_authority !== undefined &&
|
|
210
|
+
!isHostDelegationProjection(value.result.managed_authority))
|
|
211
|
+
throw new RelayHostControlClientError("relay_registration_failed", "Managed Host authority response is invalid", false);
|
|
202
212
|
if (dispositionKind !== "registered" && dispositionKind !== "interrupted") {
|
|
203
213
|
throw new RelayHostControlClientError("relay_registration_failed", "Relay host registration disposition is invalid", false);
|
|
204
214
|
}
|
|
@@ -249,6 +259,7 @@ export async function registerRelayHostConnection(options) {
|
|
|
249
259
|
const fetchImpl = options.fetch ?? fetch;
|
|
250
260
|
const commandId = options.commandId ?? createIdempotencyKey();
|
|
251
261
|
const issuedAt = options.issuedAt ?? new Date();
|
|
262
|
+
const payload = createRegisterPayload(options);
|
|
252
263
|
const response = await (async () => {
|
|
253
264
|
try {
|
|
254
265
|
return await fetchWithTimeout(fetchImpl, registrationUrl(options.relayUrl), {
|
|
@@ -260,7 +271,7 @@ export async function registerRelayHostConnection(options) {
|
|
|
260
271
|
body: JSON.stringify({
|
|
261
272
|
command_id: commandId,
|
|
262
273
|
issued_at: issuedAt.toISOString(),
|
|
263
|
-
payload
|
|
274
|
+
payload,
|
|
264
275
|
}),
|
|
265
276
|
}, options.timeoutMs ?? DEFAULT_TIMEOUT_MS, options.signal);
|
|
266
277
|
}
|
|
@@ -278,7 +289,19 @@ export async function registerRelayHostConnection(options) {
|
|
|
278
289
|
}
|
|
279
290
|
throw new RelayHostControlClientError(response.status >= 500 ? "relay_unavailable" : "relay_registration_failed", `Relay host registration failed with HTTP ${response.status}`, response.status >= 500, response.status, relayErrorCode);
|
|
280
291
|
}
|
|
281
|
-
|
|
292
|
+
const result = assertRegistrationResponse(await response.json());
|
|
293
|
+
if (options.managedBootstrap !== undefined &&
|
|
294
|
+
result.result !== undefined &&
|
|
295
|
+
result.result.managed_authority === undefined)
|
|
296
|
+
throw new RelayHostControlClientError("relay_registration_failed", "Relay does not support managed Host authority", false);
|
|
297
|
+
if (options.managedBootstrap !== undefined &&
|
|
298
|
+
result.result?.managed_authority !== undefined &&
|
|
299
|
+
(result.result.managed_authority.installation_ref !==
|
|
300
|
+
options.managedBootstrap.installation_ref ||
|
|
301
|
+
result.result.managed_authority.relay_project_ref !==
|
|
302
|
+
result.result.relay_project?.relay_project_ref))
|
|
303
|
+
throw new RelayHostControlClientError("relay_registration_failed", "Managed Host authority response does not match registration", false);
|
|
304
|
+
return result;
|
|
282
305
|
}
|
|
283
306
|
export async function refreshRelayJoinToken(options) {
|
|
284
307
|
const fetchImpl = options.fetch ?? fetch;
|