@xfey/tutti 0.1.119 → 0.1.120
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 +47 -137
- package/dist/server-shell/http/routes/project-api/openapi.d.ts +47 -137
- package/dist/server-shell/http/routes/project-api/schemas.d.ts +25 -136
- package/dist/server-shell/http/routes/project-api/schemas.js +4 -58
- package/dist/server-shell/http/routes/project-api/types.d.ts +4 -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 +151 -0
- package/dist/server-shell/local-console/managed-project-service.js +163 -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 +5 -1
- package/dist/server-shell/local-console/project-service.js +63 -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/model-control-client.d.ts +66 -0
- package/dist/server-shell/managed-access/model-control-client.js +114 -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 +108 -0
- package/dist/server-shell/managed-access/records.js +73 -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 +110 -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 +301 -0
- package/dist/server-shell/managed-access/state.d.ts +54 -0
- package/dist/server-shell/managed-access/state.js +287 -0
- package/dist/server-shell/managed-access/transport.d.ts +48 -0
- package/dist/server-shell/managed-access/transport.js +38 -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 +75 -0
- package/node_modules/@tutti/relay-client/dist/project-authority.js +140 -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 +35 -0
- package/node_modules/@tutti/shared/dist/schemas/api/managed-account.js +41 -0
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.d.ts +8 -4
- package/node_modules/@tutti/shared/dist/schemas/api/provider-config.js +6 -0
- package/node_modules/@tutti/shared/dist/schemas/api/runtime-events.d.ts +4 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.d.ts +28 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/creation-validation.js +42 -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 +116 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/managed-runtime.js +87 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.d.ts +120 -0
- package/node_modules/@tutti/shared/dist/schemas/internal/project-authority.js +110 -0
- package/package.json +1 -1
- package/web/assets/{homepage-motion-scene-B6lCCHBq.js → homepage-motion-scene-DyMFsYmT.js} +1 -1
- package/web/assets/index-DSlecDXC.js +70 -0
- package/web/index.html +1 -1
- package/web/assets/index-DepsYMLl.js +0 -70
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type HostDelegationProjection, type PrepareProjectAuthority, type ManagedProjectBootstrap, type NativeCreationProof } from "@tutti/shared/schemas/internal";
|
|
2
|
+
import { type CreationJournal, type Installation } from "./records.js";
|
|
3
|
+
export declare function managedRelayOrigin(input: string): string;
|
|
4
|
+
export declare function findNativeManagedProject(options: {
|
|
5
|
+
tuttiHome: string;
|
|
6
|
+
relayUrl: string;
|
|
7
|
+
projectId: string;
|
|
8
|
+
}): {
|
|
9
|
+
state: NativeManagedState;
|
|
10
|
+
journal: CreationJournal;
|
|
11
|
+
} | null;
|
|
12
|
+
export declare class NativeManagedState {
|
|
13
|
+
readonly origin: string;
|
|
14
|
+
private readonly files;
|
|
15
|
+
constructor(options: {
|
|
16
|
+
tuttiHome: string;
|
|
17
|
+
relayUrl: string;
|
|
18
|
+
});
|
|
19
|
+
installation(now?: Date): Installation;
|
|
20
|
+
createDraft(options: {
|
|
21
|
+
workspaceRoot: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
surface: "standalone" | "feishu";
|
|
25
|
+
tier: "light" | "high" | "max";
|
|
26
|
+
requestRef?: string;
|
|
27
|
+
projectId?: string;
|
|
28
|
+
existing?: {
|
|
29
|
+
relayProjectRef: string;
|
|
30
|
+
hostSecret: string;
|
|
31
|
+
previousRequestRef?: string;
|
|
32
|
+
};
|
|
33
|
+
now?: Date;
|
|
34
|
+
}): CreationJournal;
|
|
35
|
+
read(requestRef: string): CreationJournal;
|
|
36
|
+
attachProject(requestRef: string, now?: Date): CreationJournal;
|
|
37
|
+
project(projectId: string): CreationJournal;
|
|
38
|
+
preparationBody(requestRef: string): PrepareProjectAuthority;
|
|
39
|
+
proof(requestRef: string): NativeCreationProof;
|
|
40
|
+
prepared(requestRef: string, receipt: {
|
|
41
|
+
creation_ref: string;
|
|
42
|
+
project_id: string;
|
|
43
|
+
installation_ref: string;
|
|
44
|
+
surface: string;
|
|
45
|
+
tier: string;
|
|
46
|
+
expires_at: string;
|
|
47
|
+
}, now?: Date): CreationJournal;
|
|
48
|
+
validation(requestRef: string, outcome: "validated" | "validation_unknown" | "validation_failed"): CreationJournal;
|
|
49
|
+
bootstrap(requestRef: string, now?: Date): ManagedProjectBootstrap;
|
|
50
|
+
registered(requestRef: string, authority: HostDelegationProjection): CreationJournal;
|
|
51
|
+
private update;
|
|
52
|
+
private journalName;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1,287 @@
|
|
|
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 = parseJournal(saved, this.origin, requestRef);
|
|
89
|
+
if (record.workspace_root !== workspace ||
|
|
90
|
+
record.display_name !== options.displayName ||
|
|
91
|
+
record.description !== options.description ||
|
|
92
|
+
record.surface !== options.surface ||
|
|
93
|
+
record.tier !== options.tier ||
|
|
94
|
+
(options.projectId !== undefined && record.project_id !== options.projectId) ||
|
|
95
|
+
record.existing_relay_project_ref !== options.existing?.relayProjectRef ||
|
|
96
|
+
record.previous_request_ref !== options.existing?.previousRequestRef ||
|
|
97
|
+
(options.existing !== undefined &&
|
|
98
|
+
record.host_registration_secret !== options.existing.hostSecret) ||
|
|
99
|
+
record.installation_ref !== installation.installation_ref)
|
|
100
|
+
throw new ManagedLocalStateError("managed_creation_mismatch");
|
|
101
|
+
return record;
|
|
102
|
+
}
|
|
103
|
+
const record = parseJournal({
|
|
104
|
+
version: 1,
|
|
105
|
+
request_ref: requestRef,
|
|
106
|
+
project_id: options.projectId ?? createProjectId(),
|
|
107
|
+
relay_origin: this.origin,
|
|
108
|
+
installation_ref: installation.installation_ref,
|
|
109
|
+
workspace_root: workspace,
|
|
110
|
+
display_name: options.displayName,
|
|
111
|
+
...(options.description === undefined ? {} : { description: options.description }),
|
|
112
|
+
surface: options.surface,
|
|
113
|
+
tier: options.tier,
|
|
114
|
+
...(options.existing === undefined
|
|
115
|
+
? {}
|
|
116
|
+
: {
|
|
117
|
+
existing_relay_project_ref: options.existing.relayProjectRef,
|
|
118
|
+
...(options.existing.previousRequestRef === undefined
|
|
119
|
+
? {}
|
|
120
|
+
: { previous_request_ref: options.existing.previousRequestRef }),
|
|
121
|
+
}),
|
|
122
|
+
handoff_token: secret(),
|
|
123
|
+
host_registration_secret: options.existing?.hostSecret ?? secret(),
|
|
124
|
+
created_at: now.toISOString(),
|
|
125
|
+
expires_at: new Date(now.getTime() + DRAFT_TTL_MS).toISOString(),
|
|
126
|
+
state: "awaiting_authorization",
|
|
127
|
+
}, this.origin, requestRef);
|
|
128
|
+
this.files.write(name, record, true);
|
|
129
|
+
return record;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
read(requestRef) {
|
|
133
|
+
return this.files.transaction(() => parseJournal(this.files.read(this.journalName(requestRef)), this.origin, requestRef));
|
|
134
|
+
}
|
|
135
|
+
attachProject(requestRef, now = new Date()) {
|
|
136
|
+
this.bootstrap(requestRef, now);
|
|
137
|
+
return this.files.transaction(() => {
|
|
138
|
+
const row = parseJournal(this.files.read(this.journalName(requestRef)), this.origin, requestRef);
|
|
139
|
+
const name = `project-${row.project_id}.json`;
|
|
140
|
+
const old = this.files.read(name);
|
|
141
|
+
if (old !== null &&
|
|
142
|
+
(!Value.Check(NativeProjectPointerSchema, old) ||
|
|
143
|
+
old.project_id !== row.project_id ||
|
|
144
|
+
(old.request_ref !== requestRef && old.request_ref !== row.previous_request_ref)))
|
|
145
|
+
throw new ManagedLocalStateError("managed_project_binding_conflict");
|
|
146
|
+
if (old === null ||
|
|
147
|
+
(Value.Check(NativeProjectPointerSchema, old) && old.request_ref !== requestRef))
|
|
148
|
+
this.files.write(name, { version: 1, project_id: row.project_id, request_ref: requestRef }, old === null);
|
|
149
|
+
return row;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
project(projectId) {
|
|
153
|
+
if (!isPrefixedId(projectId, ID_PREFIXES.project))
|
|
154
|
+
throw new ManagedLocalStateError("managed_project_id_invalid");
|
|
155
|
+
return this.files.transaction(() => {
|
|
156
|
+
const pointer = this.files.read(`project-${projectId}.json`);
|
|
157
|
+
if (!Value.Check(NativeProjectPointerSchema, pointer) || pointer.project_id !== projectId)
|
|
158
|
+
throw new ManagedLocalStateError("managed_project_binding_invalid");
|
|
159
|
+
const row = parseJournal(this.files.read(this.journalName(pointer.request_ref)), this.origin, pointer.request_ref);
|
|
160
|
+
if (row.project_id !== projectId || (row.state !== "validated" && row.state !== "registered"))
|
|
161
|
+
throw new ManagedLocalStateError("managed_project_binding_invalid");
|
|
162
|
+
return row;
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
preparationBody(requestRef) {
|
|
166
|
+
const row = this.read(requestRef), installation = this.installation();
|
|
167
|
+
if (row.installation_ref !== installation.installation_ref)
|
|
168
|
+
throw new ManagedLocalStateError("managed_installation_changed");
|
|
169
|
+
return {
|
|
170
|
+
request_ref: row.request_ref,
|
|
171
|
+
project_id: row.project_id,
|
|
172
|
+
surface: row.surface,
|
|
173
|
+
tier: row.tier,
|
|
174
|
+
...(row.existing_relay_project_ref === undefined
|
|
175
|
+
? {}
|
|
176
|
+
: { existing_relay_project_ref: row.existing_relay_project_ref }),
|
|
177
|
+
installation_ref: row.installation_ref,
|
|
178
|
+
installation_secret_hash: hash(installation.secret),
|
|
179
|
+
handoff_token_hash: hash(row.handoff_token),
|
|
180
|
+
host_registration_secret_hash: hash(row.host_registration_secret),
|
|
181
|
+
trial_consent: PROJECT_TRIAL_POLICY,
|
|
182
|
+
expires_before: row.expires_at,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
proof(requestRef) {
|
|
186
|
+
const row = this.read(requestRef), installation = this.installation();
|
|
187
|
+
if (row.installation_ref !== installation.installation_ref)
|
|
188
|
+
throw new ManagedLocalStateError("managed_installation_changed");
|
|
189
|
+
return {
|
|
190
|
+
request_ref: row.request_ref,
|
|
191
|
+
project_id: row.project_id,
|
|
192
|
+
installation_ref: row.installation_ref,
|
|
193
|
+
installation_secret: installation.secret,
|
|
194
|
+
handoff_token: row.handoff_token,
|
|
195
|
+
host_registration_secret: row.host_registration_secret,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
// Only native, Relay-verified responses may call these transitions. Browser input is not evidence.
|
|
199
|
+
prepared(requestRef, receipt, now = new Date()) {
|
|
200
|
+
return this.update(requestRef, (row) => {
|
|
201
|
+
if (row.project_id !== receipt.project_id ||
|
|
202
|
+
row.installation_ref !== receipt.installation_ref ||
|
|
203
|
+
row.surface !== receipt.surface ||
|
|
204
|
+
row.tier !== receipt.tier ||
|
|
205
|
+
!Number.isFinite(Date.parse(receipt.expires_at)) ||
|
|
206
|
+
Date.parse(receipt.expires_at) > Date.parse(row.expires_at))
|
|
207
|
+
throw new ManagedLocalStateError("managed_preparation_mismatch");
|
|
208
|
+
if (row.state !== "awaiting_authorization") {
|
|
209
|
+
if (row.creation_ref !== receipt.creation_ref)
|
|
210
|
+
throw new ManagedLocalStateError("managed_preparation_mismatch");
|
|
211
|
+
return row;
|
|
212
|
+
}
|
|
213
|
+
if (receipt.expires_at <= now.toISOString())
|
|
214
|
+
throw new ManagedLocalStateError("managed_preparation_expired");
|
|
215
|
+
return {
|
|
216
|
+
...row,
|
|
217
|
+
state: "prepared",
|
|
218
|
+
creation_ref: receipt.creation_ref,
|
|
219
|
+
expires_at: receipt.expires_at,
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
validation(requestRef, outcome) {
|
|
224
|
+
return this.update(requestRef, (row) => {
|
|
225
|
+
if (row.state === "registered" || row.state === "validated")
|
|
226
|
+
return row;
|
|
227
|
+
if (row.state === "validation_failed") {
|
|
228
|
+
if (outcome !== row.state)
|
|
229
|
+
throw new ManagedLocalStateError("managed_validation_conflict");
|
|
230
|
+
return row;
|
|
231
|
+
}
|
|
232
|
+
if (row.state !== "prepared" && row.state !== "validation_unknown")
|
|
233
|
+
throw new ManagedLocalStateError("managed_creation_not_prepared");
|
|
234
|
+
return { ...row, state: outcome };
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
bootstrap(requestRef, now = new Date()) {
|
|
238
|
+
const row = this.read(requestRef), installation = this.installation();
|
|
239
|
+
if ((row.state !== "validated" && row.state !== "registered") ||
|
|
240
|
+
(row.state !== "registered" && row.expires_at <= now.toISOString()))
|
|
241
|
+
throw new ManagedLocalStateError("managed_creation_not_validated");
|
|
242
|
+
if (row.installation_ref !== installation.installation_ref)
|
|
243
|
+
throw new ManagedLocalStateError("managed_installation_changed");
|
|
244
|
+
return {
|
|
245
|
+
kind: "managed_project_authority_v2",
|
|
246
|
+
creation_ref: row.creation_ref,
|
|
247
|
+
installation_ref: installation.installation_ref,
|
|
248
|
+
installation_secret: installation.secret,
|
|
249
|
+
handoff_token: row.handoff_token,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
registered(requestRef, authority) {
|
|
253
|
+
return this.update(requestRef, (row) => {
|
|
254
|
+
if (!Value.Check(HostDelegationProjectionSchema, authority) ||
|
|
255
|
+
authority.installation_ref !== row.installation_ref ||
|
|
256
|
+
authority.tier !== row.tier ||
|
|
257
|
+
(row.existing_relay_project_ref !== undefined &&
|
|
258
|
+
authority.relay_project_ref !== row.existing_relay_project_ref))
|
|
259
|
+
throw new ManagedLocalStateError("managed_registration_mismatch");
|
|
260
|
+
if (row.state === "registered") {
|
|
261
|
+
if (row.authority.delegation_ref !== authority.delegation_ref ||
|
|
262
|
+
row.authority.grant_ref !== authority.grant_ref ||
|
|
263
|
+
row.authority.relay_project_ref !== authority.relay_project_ref)
|
|
264
|
+
throw new ManagedLocalStateError("managed_registration_mismatch");
|
|
265
|
+
return row;
|
|
266
|
+
}
|
|
267
|
+
if (row.state !== "validated")
|
|
268
|
+
throw new ManagedLocalStateError("managed_creation_not_validated");
|
|
269
|
+
return { ...row, state: "registered", authority };
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
update(requestRef, change) {
|
|
273
|
+
return this.files.transaction(() => {
|
|
274
|
+
const name = this.journalName(requestRef);
|
|
275
|
+
const row = parseJournal(this.files.read(name), this.origin, requestRef);
|
|
276
|
+
const updated = parseJournal(change(row), this.origin, requestRef);
|
|
277
|
+
this.files.write(name, updated);
|
|
278
|
+
return updated;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
journalName(requestRef) {
|
|
282
|
+
if (!isPrefixedId(requestRef, ID_PREFIXES.idempotency))
|
|
283
|
+
throw new ManagedLocalStateError("managed_request_ref_invalid");
|
|
284
|
+
return `${requestRef}.json`;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
tier: "light" | "high" | "max";
|
|
16
|
+
expires_at: null;
|
|
17
|
+
funding_kind: "trial";
|
|
18
|
+
model_access: "runtime_authority_required" | "not_enabled";
|
|
19
|
+
};
|
|
20
|
+
account_ref: string;
|
|
21
|
+
request_ref: string;
|
|
22
|
+
creation: {
|
|
23
|
+
relay_project_ref?: string;
|
|
24
|
+
state: "revoked" | "expired" | "prepared" | "activated";
|
|
25
|
+
installation_ref: string;
|
|
26
|
+
surface: "standalone" | "feishu";
|
|
27
|
+
tier: "light" | "high" | "max";
|
|
28
|
+
expires_at: string;
|
|
29
|
+
project_id: string;
|
|
30
|
+
creation_ref: string;
|
|
31
|
+
};
|
|
32
|
+
}>;
|
|
33
|
+
export declare function validateNativePreparation(options: {
|
|
34
|
+
state: NativeManagedState;
|
|
35
|
+
requestRef: string;
|
|
36
|
+
fetchImpl?: typeof fetch;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
state: "failed" | "unknown" | "started" | "succeeded";
|
|
40
|
+
installation_ref: string;
|
|
41
|
+
tier: "light" | "high" | "max";
|
|
42
|
+
reason_code: "validation_started" | "validation_succeeded" | "validation_route_unavailable" | "validation_http_rejected" | "validation_response_invalid" | "validation_transport_unknown" | "validation_interrupted";
|
|
43
|
+
project_id: string;
|
|
44
|
+
creation_ref: string;
|
|
45
|
+
route_ref: string;
|
|
46
|
+
http_status: number | null;
|
|
47
|
+
}>;
|
|
48
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
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.tier !== result.tier)
|
|
29
|
+
throw new ManagedLocalStateError("managed_validation_mismatch");
|
|
30
|
+
if (result.state === "succeeded")
|
|
31
|
+
options.state.validation(options.requestRef, "validated");
|
|
32
|
+
else if (result.state === "unknown" || result.state === "started")
|
|
33
|
+
options.state.validation(options.requestRef, "validation_unknown");
|
|
34
|
+
else
|
|
35
|
+
options.state.validation(options.requestRef, "validation_failed");
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
//# 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;
|