@cjhyy/code-shell-server 0.9.36 → 0.9.38

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.
Files changed (31) hide show
  1. package/dist/bin/code-shell-cloud-account.d.ts +2 -0
  2. package/dist/bin/code-shell-cloud-account.js +53 -0
  3. package/dist/index.serve.d.ts +4 -0
  4. package/dist/index.serve.js +3 -0
  5. package/dist/panels/management.js +12 -1
  6. package/dist/panels/types.d.ts +5 -3
  7. package/dist/project-runtime/account-adoption.d.ts +34 -0
  8. package/dist/project-runtime/account-adoption.js +280 -0
  9. package/dist/project-runtime/account-binding.d.ts +18 -0
  10. package/dist/project-runtime/account-binding.js +42 -0
  11. package/dist/project-runtime/backup.d.ts +10 -0
  12. package/dist/project-runtime/backup.js +113 -20
  13. package/dist/project-runtime/cloud-account-auth.d.ts +23 -0
  14. package/dist/project-runtime/cloud-account-auth.js +287 -0
  15. package/dist/project-runtime/cloud-browser-auth.d.ts +15 -0
  16. package/dist/project-runtime/cloud-browser-auth.js +352 -0
  17. package/dist/project-runtime/control-authority.d.ts +46 -0
  18. package/dist/project-runtime/control-authority.js +15 -0
  19. package/dist/project-runtime/control-server.d.ts +17 -0
  20. package/dist/project-runtime/control-server.js +94 -26
  21. package/dist/project-runtime/docker-provider.d.ts +6 -0
  22. package/dist/project-runtime/docker-provider.js +10 -5
  23. package/dist/project-runtime/lock.js +181 -12
  24. package/dist/project-runtime/manager.d.ts +16 -8
  25. package/dist/project-runtime/manager.js +89 -15
  26. package/dist/project-runtime/proxy.d.ts +3 -3
  27. package/dist/project-runtime/proxy.js +15 -5
  28. package/dist/project-runtime/registry.d.ts +21 -1
  29. package/dist/project-runtime/registry.js +224 -6
  30. package/dist/serve/headless-server.js +14 -0
  31. package/package.json +7 -6
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env node
2
+ import { adoptCloudAccount, inspectCloudAccountAdoption, } from "../project-runtime/account-adoption.js";
3
+ const help = `Usage: code-shell-cloud-account --inspect --data-dir <canonical path>
4
+ code-shell-cloud-account --adopt --data-dir <canonical path>
5
+ --evidence-dir <new private directory outside data-dir>
6
+ --account-origin <exact Account HTTPS origin> --account-id <UUID>
7
+ --installation-id <reviewed UUID> --owner-id <reviewed UUID>
8
+ --legacy-username <reviewed sole operator>
9
+
10
+ Stop the Cloud controller and all projects first. Adoption durably preserves
11
+ original authority files and SHA receipts before retiring legacy login. Evidence
12
+ contains private password hashes. Make a full installation/volume backup too.
13
+ Retry a failed adoption with exactly the same source, evidence and identities.
14
+ This tool never contacts Account, accepts a password/token, or runs Docker.
15
+ The bound Account must authenticate through the configured Cloud at first use.`;
16
+ try {
17
+ const args = process.argv.slice(2);
18
+ if (args.length === 1 && ["--help", "-h"].includes(args[0]))
19
+ console.log(help);
20
+ else {
21
+ const mode = args.shift();
22
+ if (mode !== "--inspect" && mode !== "--adopt")
23
+ throw new Error("Choose --inspect or --adopt.");
24
+ const names = {
25
+ "--data-dir": "dataDir",
26
+ "--evidence-dir": "evidenceDirectory",
27
+ "--account-origin": "accountOrigin",
28
+ "--account-id": "accountId",
29
+ "--installation-id": "installationId",
30
+ "--owner-id": "ownerId",
31
+ "--legacy-username": "legacyUsername",
32
+ };
33
+ const options = {};
34
+ while (args.length) {
35
+ const name = args.shift(), key = names[name], value = args.shift();
36
+ if (!key || !value || value.startsWith("--") || key in options)
37
+ throw new Error("Unknown, duplicate or incomplete option.");
38
+ options[key] = value;
39
+ }
40
+ if (!options.dataDir ||
41
+ (mode === "--inspect"
42
+ ? Object.keys(options).length !== 1
43
+ : Object.keys(options).length !== Object.keys(names).length))
44
+ throw new Error("Supply every reviewed identity; use --help for usage.");
45
+ console.log(JSON.stringify(mode === "--inspect"
46
+ ? inspectCloudAccountAdoption(options.dataDir)
47
+ : adoptCloudAccount(options), null, 2));
48
+ }
49
+ }
50
+ catch (error) {
51
+ console.error(error instanceof Error ? error.message : "Cloud account adoption failed.");
52
+ process.exitCode = 1;
53
+ }
@@ -1,6 +1,10 @@
1
1
  /** Ready-made headless HTTP/WebSocket host and CLI composition helpers. */
2
2
  export * from "./serve/headless-server.js";
3
3
  export { startProjectControlServer, type ProjectControlServerOptions, } from "./project-runtime/control-server.js";
4
+ export { CLOUD_MANAGEMENT_AUTHORITY_VERSION } from "./project-runtime/cloud-account-auth.js";
5
+ export { CLOUD_BROWSER_AUTHORITY_VERSION } from "./project-runtime/cloud-browser-auth.js";
6
+ export { adoptCloudAccount, inspectCloudAccountAdoption, type CloudAccountAdoptionOptions, } from "./project-runtime/account-adoption.js";
7
+ export type { CloudAccountAuthority, CloudAccountPrincipal, CloudManagementSession, } from "./project-runtime/control-authority.js";
4
8
  export { parseServeArgs, resolveWebAppRoot, resolveWorkerCapabilityModules, resolveWorkerEntry, runServeCli, } from "./serve/cli.js";
5
9
  export { backupProjectInstallation, restoreProjectInstallation, type ProjectBackupOptions, type ProjectRestoreOptions, } from "./project-runtime/backup.js";
6
10
  export { readProjectSeccompProfile, type ProjectSeccompProfile, } from "./project-runtime/seccomp.js";
@@ -1,6 +1,9 @@
1
1
  /** Ready-made headless HTTP/WebSocket host and CLI composition helpers. */
2
2
  export * from "./serve/headless-server.js";
3
3
  export { startProjectControlServer, } from "./project-runtime/control-server.js";
4
+ export { CLOUD_MANAGEMENT_AUTHORITY_VERSION } from "./project-runtime/cloud-account-auth.js";
5
+ export { CLOUD_BROWSER_AUTHORITY_VERSION } from "./project-runtime/cloud-browser-auth.js";
6
+ export { adoptCloudAccount, inspectCloudAccountAdoption, } from "./project-runtime/account-adoption.js";
4
7
  export { parseServeArgs, resolveWebAppRoot, resolveWorkerCapabilityModules, resolveWorkerEntry, runServeCli, } from "./serve/cli.js";
5
8
  export { backupProjectInstallation, restoreProjectInstallation, } from "./project-runtime/backup.js";
6
9
  export { readProjectSeccompProfile, } from "./project-runtime/seccomp.js";
@@ -396,6 +396,12 @@ export function createPanelManagement(options) {
396
396
  source,
397
397
  expectedRevision: current?.revision ?? null,
398
398
  compatibility: compatibility(preview),
399
+ ...(current
400
+ ? {
401
+ installedVersion: current.app.version,
402
+ installedPermissions: [...current.app.permissions],
403
+ }
404
+ : {}),
399
405
  };
400
406
  reviews.set(reviewToken, {
401
407
  owner: context.ownerId,
@@ -458,7 +464,12 @@ export function createPanelManagement(options) {
458
464
  preview: publicPreview,
459
465
  expectedRevision: currentRevision,
460
466
  compatibility: compatibility(preview),
461
- ...(current ? { installedVersion: current.app.version } : {}),
467
+ ...(current
468
+ ? {
469
+ installedVersion: current.app.version,
470
+ installedPermissions: [...current.app.permissions],
471
+ }
472
+ : {}),
462
473
  };
463
474
  reviews.set(value.reviewToken, {
464
475
  owner: context.ownerId,
@@ -57,11 +57,13 @@ export interface PanelReview {
57
57
  source: PanelGitSource;
58
58
  expectedRevision: string | null;
59
59
  compatibility: PanelCompatibility;
60
- }
61
- /** Trusted native hosts may review local sources without exposing paths to Web routes. */
62
- export interface PanelProjectReview extends Omit<PanelReview, "source"> {
60
+ /** The installed package selected for this project, when its manifest is available. */
63
61
  installedVersion?: string;
62
+ /** Reviewed permissions of that same installed package; an empty array is a known baseline. */
63
+ installedPermissions?: PanelAppPreview["permissions"];
64
64
  }
65
+ /** Trusted native hosts may review local sources without exposing paths to Web routes. */
66
+ export type PanelProjectReview = Omit<PanelReview, "source">;
65
67
  export interface PanelOperationContext {
66
68
  ownerId: string;
67
69
  authorize: () => boolean | Promise<boolean>;
@@ -0,0 +1,34 @@
1
+ import { fsyncSync } from "node:fs";
2
+ import { type CloudAccountBinding } from "./account-binding.js";
3
+ export interface CloudAccountAdoptionOptions {
4
+ dataDir: string;
5
+ /** A new private directory outside the source. Authority evidence contains password hashes. */
6
+ evidenceDirectory: string;
7
+ accountOrigin: string;
8
+ accountId: string;
9
+ /** Both identities must be explicitly reviewed from the existing offline installation. */
10
+ installationId: string;
11
+ ownerId: string;
12
+ legacyUsername: string;
13
+ /** Fault seam; no user request can configure these hooks. */
14
+ io?: {
15
+ fsync?: typeof fsyncSync;
16
+ };
17
+ }
18
+ /** Read-only operator review; private runtime passwords and Hub hashes are excluded. */
19
+ export declare function inspectCloudAccountAdoption(dataDir: string): {
20
+ installationId: string;
21
+ owners: any;
22
+ accountBinding: CloudAccountBinding | null;
23
+ projects: {
24
+ id: string;
25
+ status: import("./registry.js").ProjectStatus;
26
+ generation: number;
27
+ }[];
28
+ pending: boolean;
29
+ };
30
+ /** Offline, explicit adoption. Never rolls back or guesses authority from a username. */
31
+ export declare function adoptCloudAccount(options: CloudAccountAdoptionOptions): {
32
+ binding: CloudAccountBinding;
33
+ evidenceDirectory: string;
34
+ };
@@ -0,0 +1,280 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { constants, closeSync, existsSync, fstatSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
+ import { HubAuthStore } from "../hub/auth-store.js";
5
+ import { CLOUD_ADOPTION_PENDING, isCloudAccountBinding, isCloudAccountOrigin, } from "./account-binding.js";
6
+ import { ProjectRegistry } from "./registry.js";
7
+ const sha = (bytes) => createHash("sha256").update(bytes).digest("hex");
8
+ const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
9
+ const FORMAT = "codeshell.cloud-account-adoption";
10
+ const SOURCES = [
11
+ "project-control/registry.json",
12
+ "project-control/initialized",
13
+ "hub/auth.json",
14
+ ];
15
+ function privateBytes(file, maximum = 1024 * 1024) {
16
+ const before = lstatSync(file);
17
+ if (!before.isFile() ||
18
+ before.isSymbolicLink() ||
19
+ before.nlink !== 1 ||
20
+ before.mode & 0o077 ||
21
+ before.size > maximum)
22
+ throw new Error("Adoption evidence must be a bounded private regular file.");
23
+ const fd = openSync(file, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
24
+ try {
25
+ const after = fstatSync(fd);
26
+ if (after.dev !== before.dev ||
27
+ after.ino !== before.ino ||
28
+ after.nlink !== 1 ||
29
+ after.size !== before.size)
30
+ throw new Error("Adoption source changed while opening.");
31
+ const bytes = readFileSync(fd);
32
+ if (bytes.length !== before.size || bytes.length > maximum)
33
+ throw new Error("Adoption source changed while reading.");
34
+ return bytes;
35
+ }
36
+ finally {
37
+ closeSync(fd);
38
+ }
39
+ }
40
+ function ordinaryDirectory(path) {
41
+ const stat = lstatSync(path);
42
+ if (!stat.isDirectory() || stat.isSymbolicLink() || stat.mode & 0o077)
43
+ throw new Error("Adoption requires private ordinary directories.");
44
+ return stat;
45
+ }
46
+ /** Read-only operator review; private runtime passwords and Hub hashes are excluded. */
47
+ export function inspectCloudAccountAdoption(dataDir) {
48
+ const source = realpathSync(dataDir);
49
+ if (source !== resolve(dataDir))
50
+ throw new Error("Adoption source must be canonical.");
51
+ ordinaryDirectory(source);
52
+ for (const name of SOURCES)
53
+ privateBytes(join(source, name));
54
+ const registry = new ProjectRegistry(source, Date.now, { offlineAccountAdoption: true });
55
+ try {
56
+ const data = JSON.parse(privateBytes(join(source, SOURCES[0])).toString("utf8"));
57
+ return {
58
+ installationId: registry.installationId,
59
+ owners: data.owners,
60
+ accountBinding: registry.accountBinding ?? null,
61
+ projects: registry.all().map(({ id, status, generation }) => ({ id, status, generation })),
62
+ pending: existsSync(join(source, "project-control", CLOUD_ADOPTION_PENDING)),
63
+ };
64
+ }
65
+ finally {
66
+ registry.close();
67
+ }
68
+ }
69
+ /** Offline, explicit adoption. Never rolls back or guesses authority from a username. */
70
+ export function adoptCloudAccount(options) {
71
+ if (!isCloudAccountOrigin(options.accountOrigin) ||
72
+ !UUID.test(options.accountId) ||
73
+ !UUID.test(options.ownerId) ||
74
+ !UUID.test(options.installationId))
75
+ throw new Error("Explicit Cloud/Account UUIDs and exact Account origin are required.");
76
+ const source = realpathSync(options.dataDir);
77
+ if (source !== resolve(options.dataDir))
78
+ throw new Error("Adoption source must be canonical.");
79
+ const sourceStat = ordinaryDirectory(source);
80
+ const control = join(source, "project-control");
81
+ const controlStat = ordinaryDirectory(control);
82
+ ordinaryDirectory(join(source, "hub"));
83
+ const destination = resolve(options.evidenceDirectory);
84
+ const parent = realpathSync(dirname(destination));
85
+ ordinaryDirectory(parent);
86
+ if (parent !== dirname(destination))
87
+ throw new Error("Evidence destination must have a canonical parent.");
88
+ const rel = relative(source, destination);
89
+ if (!rel || (!isAbsolute(rel) && rel !== ".." && !rel.startsWith(`..${sep}`)))
90
+ throw new Error("Preserve evidence outside the installation.");
91
+ // Never initialize a missing/wrong installation while performing adoption.
92
+ for (const name of SOURCES)
93
+ privateBytes(join(source, name));
94
+ const flush = options.io?.fsync ?? fsyncSync;
95
+ const sync = (path) => {
96
+ const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
97
+ try {
98
+ flush(fd);
99
+ }
100
+ finally {
101
+ closeSync(fd);
102
+ }
103
+ };
104
+ const save = (file, bytes) => {
105
+ if (existsSync(file)) {
106
+ if (!privateBytes(file).equals(bytes))
107
+ throw new Error("Adoption evidence changed; preserve it for diagnosis.");
108
+ }
109
+ else {
110
+ const fd = openSync(file, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | (constants.O_NOFOLLOW ?? 0), 0o600);
111
+ try {
112
+ writeFileSync(fd, bytes);
113
+ flush(fd);
114
+ }
115
+ finally {
116
+ closeSync(fd);
117
+ }
118
+ }
119
+ sync(file);
120
+ sync(dirname(file));
121
+ };
122
+ const registry = new ProjectRegistry(source, Date.now, {
123
+ fsync: flush,
124
+ offlineAccountAdoption: true,
125
+ });
126
+ try {
127
+ if (registry.installationId !== options.installationId ||
128
+ registry.all().some((project) => project.status !== "stopped"))
129
+ throw new Error("Adoption requires the reviewed installation with all projects stopped.");
130
+ if (!existsSync(destination)) {
131
+ mkdirSync(destination, { mode: 0o700 });
132
+ sync(parent);
133
+ }
134
+ ordinaryDirectory(destination);
135
+ const receiptPath = join(destination, "receipt.json");
136
+ let receipt;
137
+ if (existsSync(receiptPath)) {
138
+ receipt = JSON.parse(privateBytes(receiptPath).toString("utf8"));
139
+ if (receipt.format !== FORMAT ||
140
+ receipt.version !== 1 ||
141
+ !isCloudAccountBinding(receipt.binding) ||
142
+ receipt.source !== source ||
143
+ receipt.sourceDev !== sourceStat.dev ||
144
+ receipt.sourceIno !== sourceStat.ino ||
145
+ receipt.controllerDev !== controlStat.dev ||
146
+ receipt.controllerIno !== controlStat.ino ||
147
+ receipt.installationId !== options.installationId ||
148
+ receipt.binding.accountOrigin !== options.accountOrigin ||
149
+ receipt.binding.accountId !== options.accountId ||
150
+ receipt.binding.ownerId !== options.ownerId)
151
+ throw new Error("Adoption receipt does not match this explicit source and destination.");
152
+ }
153
+ else {
154
+ if (registry.accountBinding)
155
+ throw new Error("Bound Cloud requires its original adoption receipt.");
156
+ const originalRegistry = privateBytes(join(source, SOURCES[0]));
157
+ const data = JSON.parse(originalRegistry.toString("utf8"));
158
+ const auth = privateBytes(join(source, SOURCES[2]));
159
+ const authData = JSON.parse(auth.toString("utf8"));
160
+ const hub = new HubAuthStore({ dataDir: source });
161
+ if (!hub.isInitialized() ||
162
+ data.version !== 1 ||
163
+ data.owners.length !== 1 ||
164
+ data.owners[0].id !== options.ownerId ||
165
+ data.owners[0].username !== options.legacyUsername ||
166
+ authData.account?.username !== options.legacyUsername)
167
+ throw new Error("Explicit legacy owner must match the sole initialized operator.");
168
+ const binding = {
169
+ version: 1,
170
+ accountOrigin: options.accountOrigin,
171
+ accountId: options.accountId,
172
+ ownerId: options.ownerId,
173
+ managementEpoch: randomUUID(),
174
+ createdAt: Date.now(),
175
+ };
176
+ const bound = { ...data, version: 2, accountBinding: binding };
177
+ const revoked = { ...authData, sessions: [] };
178
+ receipt = {
179
+ format: FORMAT,
180
+ version: 1,
181
+ source,
182
+ sourceDev: sourceStat.dev,
183
+ sourceIno: sourceStat.ino,
184
+ controllerDev: controlStat.dev,
185
+ controllerIno: controlStat.ino,
186
+ installationId: options.installationId,
187
+ binding,
188
+ originals: {},
189
+ registryAfter: sha(Buffer.from(JSON.stringify(bound, null, 2) + "\n")),
190
+ authAfter: sha(Buffer.from(JSON.stringify(revoked, null, 2))),
191
+ };
192
+ for (const name of SOURCES) {
193
+ const bytes = privateBytes(join(source, name));
194
+ receipt.originals[name] = sha(bytes);
195
+ save(join(destination, name.replaceAll("/", "--")), bytes);
196
+ }
197
+ // Original evidence is fully flushed before publishing any adoption barrier.
198
+ save(receiptPath, Buffer.from(JSON.stringify(receipt, null, 2) + "\n"));
199
+ }
200
+ for (const name of SOURCES) {
201
+ const original = privateBytes(join(destination, name.replaceAll("/", "--")));
202
+ if (sha(original) !== receipt.originals[name])
203
+ throw new Error("Adoption evidence checksum mismatch.");
204
+ }
205
+ const originalData = JSON.parse(privateBytes(join(destination, SOURCES[0].replaceAll("/", "--"))).toString("utf8"));
206
+ const originalAuth = JSON.parse(privateBytes(join(destination, SOURCES[2].replaceAll("/", "--"))).toString("utf8"));
207
+ if (originalData.version !== 1 ||
208
+ originalData.installationId !== options.installationId ||
209
+ originalData.owners?.length !== 1 ||
210
+ originalData.owners[0].id !== options.ownerId ||
211
+ originalData.owners[0].username !== options.legacyUsername ||
212
+ originalAuth.account?.username !== options.legacyUsername ||
213
+ receipt.registryAfter !==
214
+ sha(Buffer.from(JSON.stringify({ ...originalData, version: 2, accountBinding: receipt.binding }, null, 2) + "\n")) ||
215
+ receipt.authAfter !==
216
+ sha(Buffer.from(JSON.stringify({ ...originalAuth, sessions: [] }, null, 2))))
217
+ throw new Error("Adoption receipt does not preserve the explicitly reviewed legacy authority.");
218
+ sync(receiptPath);
219
+ sync(destination);
220
+ const assertSource = () => {
221
+ const current = ordinaryDirectory(source), controller = ordinaryDirectory(control);
222
+ if (current.dev !== receipt.sourceDev ||
223
+ current.ino !== receipt.sourceIno ||
224
+ controller.dev !== receipt.controllerDev ||
225
+ controller.ino !== receipt.controllerIno)
226
+ throw new Error("Adoption source directory changed.");
227
+ for (const name of SOURCES) {
228
+ const value = sha(privateBytes(join(source, name)));
229
+ const allowed = [
230
+ receipt.originals[name],
231
+ name === SOURCES[0]
232
+ ? receipt.registryAfter
233
+ : name === SOURCES[2]
234
+ ? receipt.authAfter
235
+ : undefined,
236
+ ];
237
+ if (!allowed.includes(value))
238
+ throw new Error("Adoption source changed; preserve evidence and diagnose before retrying.");
239
+ }
240
+ };
241
+ assertSource();
242
+ const pending = join(control, CLOUD_ADOPTION_PENDING);
243
+ const marker = Buffer.from(JSON.stringify({
244
+ format: FORMAT,
245
+ version: 1,
246
+ evidenceDirectory: destination,
247
+ receipt: sha(privateBytes(receiptPath)),
248
+ }) + "\n");
249
+ save(pending, marker);
250
+ assertSource();
251
+ if (!registry.accountBinding)
252
+ registry.bindAccount(receipt.binding);
253
+ if (JSON.stringify(registry.accountBinding) !== JSON.stringify(receipt.binding) ||
254
+ sha(privateBytes(join(source, SOURCES[0]))) !== receipt.registryAfter)
255
+ throw new Error("Cloud adoption binding changed.");
256
+ sync(join(source, SOURCES[0]));
257
+ sync(control);
258
+ assertSource();
259
+ new HubAuthStore({ dataDir: source }).revokeAllSessions();
260
+ sync(join(source, SOURCES[2]));
261
+ sync(join(source, "hub"));
262
+ assertSource();
263
+ save(join(destination, "completed.json"), Buffer.from(JSON.stringify({
264
+ format: FORMAT,
265
+ receipt: sha(privateBytes(receiptPath)),
266
+ registry: receipt.registryAfter,
267
+ auth: receipt.authAfter,
268
+ }) + "\n"));
269
+ if (existsSync(pending)) {
270
+ if (!privateBytes(pending).equals(marker))
271
+ throw new Error("Adoption pending marker changed.");
272
+ unlinkSync(pending);
273
+ sync(control);
274
+ }
275
+ return { binding: { ...receipt.binding }, evidenceDirectory: destination };
276
+ }
277
+ finally {
278
+ registry.close();
279
+ }
280
+ }
@@ -0,0 +1,18 @@
1
+ /** Explicit single-operator adoption; account display names never confer ownership. */
2
+ export interface CloudAccountBinding {
3
+ version: 1;
4
+ accountOrigin: string;
5
+ accountId: string;
6
+ ownerId: string;
7
+ managementEpoch: string;
8
+ createdAt: number;
9
+ }
10
+ /** Fresh installation policy. Each verified Account receives its own internal owner. */
11
+ export interface CloudMultiAccountConfiguration {
12
+ accountOrigin: string;
13
+ managementEpoch: string;
14
+ }
15
+ export declare const CLOUD_ADOPTION_PENDING = "account-adoption-pending.json";
16
+ export declare function isCloudAccountOrigin(value: unknown): value is string;
17
+ export declare function isCloudAccountBinding(value: unknown): value is CloudAccountBinding;
18
+ export declare function isCloudMultiAccountConfiguration(value: unknown): value is CloudMultiAccountConfiguration;
@@ -0,0 +1,42 @@
1
+ export const CLOUD_ADOPTION_PENDING = "account-adoption-pending.json";
2
+ const UUID = /^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/;
3
+ export function isCloudAccountOrigin(value) {
4
+ if (typeof value !== "string" || value.length > 2048)
5
+ return false;
6
+ try {
7
+ const url = new URL(value);
8
+ return (url.origin === value &&
9
+ !url.username &&
10
+ !url.password &&
11
+ (url.protocol === "https:" ||
12
+ (url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))));
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ }
18
+ export function isCloudAccountBinding(value) {
19
+ if (!value || typeof value !== "object" || Array.isArray(value))
20
+ return false;
21
+ const row = value;
22
+ return (Object.keys(row).sort().join() ===
23
+ ["version", "accountOrigin", "accountId", "ownerId", "managementEpoch", "createdAt"]
24
+ .sort()
25
+ .join() &&
26
+ row.version === 1 &&
27
+ isCloudAccountOrigin(row.accountOrigin) &&
28
+ UUID.test(row.accountId) &&
29
+ UUID.test(row.ownerId) &&
30
+ UUID.test(row.managementEpoch) &&
31
+ Number.isSafeInteger(row.createdAt) &&
32
+ row.createdAt >= 0);
33
+ }
34
+ export function isCloudMultiAccountConfiguration(value) {
35
+ if (!value || typeof value !== "object" || Array.isArray(value))
36
+ return false;
37
+ const row = value;
38
+ return (Object.keys(row).sort().join() === "accountOrigin,managementEpoch" &&
39
+ isCloudAccountOrigin(row.accountOrigin) &&
40
+ typeof row.managementEpoch === "string" &&
41
+ UUID.test(row.managementEpoch));
42
+ }
@@ -1,13 +1,23 @@
1
+ import { fsyncSync } from "node:fs";
2
+ import { rm } from "node:fs/promises";
1
3
  export interface ProjectBackupOptions {
2
4
  dataDir: string;
3
5
  destination: string;
4
6
  /** Locally available, administrator-reviewed Linux image containing tar. No pull occurs. */
5
7
  helperImage: string;
8
+ /** Explicit policy for a schema 3 installation; never inferred from backup data. */
9
+ multiAccountOrigin?: string;
6
10
  }
7
11
  export interface ProjectRestoreOptions {
8
12
  source: string;
9
13
  destination: string;
10
14
  helperImage: string;
15
+ multiAccountOrigin?: string;
16
+ /** Fault injection for offline restore durability tests. */
17
+ io?: {
18
+ fsync?: typeof fsyncSync;
19
+ removeMarker?: typeof rm;
20
+ };
11
21
  }
12
22
  /** Offline, owner-only backup. It never stops projects or modifies their volumes. */
13
23
  export declare function backupProjectInstallation(options: ProjectBackupOptions): Promise<{