@ornncompute/cli 0.2.24 → 1.0.25

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 (85) hide show
  1. package/README.md +298 -426
  2. package/package.json +12 -6
  3. package/src/admin-capacity-reservations.mjs +280 -0
  4. package/src/admin-command-handlers.mjs +57 -0
  5. package/src/admin-enrollment.mjs +680 -0
  6. package/src/admin-mutations.mjs +187 -0
  7. package/src/admin-node-diagnostics.mjs +412 -0
  8. package/src/admin-node-lifecycle.mjs +908 -0
  9. package/src/admin-shell.mjs +322 -0
  10. package/src/api-client.mjs +81 -28
  11. package/src/atomic-output.mjs +112 -0
  12. package/src/auth-store.mjs +182 -11
  13. package/src/capability-catalog.mjs +7 -0
  14. package/src/catalog-shell.mjs +1350 -0
  15. package/src/cli.mjs +123 -10266
  16. package/src/connect-mode-handlers.mjs +170 -0
  17. package/src/device-auth.mjs +178 -61
  18. package/src/evidence-commands.mjs +1340 -0
  19. package/src/external-bucket-commands.mjs +1374 -0
  20. package/src/interaction-runtime.mjs +310 -0
  21. package/src/kubernetes-commands.mjs +2229 -0
  22. package/src/launch-mode-handlers.mjs +119 -0
  23. package/src/machine-reference.mjs +277 -0
  24. package/src/mount-commands.mjs +1146 -0
  25. package/src/session-commands.mjs +565 -0
  26. package/src/slurm-commands.mjs +263 -0
  27. package/src/startup.mjs +34 -11
  28. package/src/storage-commands.mjs +1144 -0
  29. package/src/update.mjs +172 -24
  30. package/src/vm-commands.mjs +4273 -0
  31. package/src/vpn-secret-output.mjs +150 -0
  32. package/src/wait-runtime.mjs +231 -0
  33. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +126 -0
  34. package/vendor/capabilities/capabilities/admin-enrollment.js +321 -0
  35. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +88 -0
  36. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +235 -0
  37. package/vendor/capabilities/capabilities/admin.js +89 -0
  38. package/vendor/capabilities/capabilities/capacity-offers.js +368 -0
  39. package/vendor/capabilities/capabilities/cli.js +156 -0
  40. package/vendor/capabilities/capabilities/clusters.js +150 -1
  41. package/vendor/capabilities/capabilities/compute.js +522 -0
  42. package/vendor/capabilities/capabilities/evidence.js +145 -0
  43. package/vendor/capabilities/capabilities/external-buckets.js +191 -0
  44. package/vendor/capabilities/capabilities/fleet.js +21 -5
  45. package/vendor/capabilities/capabilities/identity.js +114 -3
  46. package/vendor/capabilities/capabilities/listings.js +14 -8
  47. package/vendor/capabilities/capabilities/mounts.js +176 -0
  48. package/vendor/capabilities/capabilities/networks.js +1257 -0
  49. package/vendor/capabilities/capabilities/observability.js +38 -8
  50. package/vendor/capabilities/capabilities/tokens.js +20 -8
  51. package/vendor/capabilities/capabilities/volumes.js +178 -0
  52. package/vendor/capabilities/capability.js +19 -13
  53. package/vendor/capabilities/cli-catalog.js +65 -0
  54. package/vendor/capabilities/cli-migrations.js +288 -0
  55. package/vendor/capabilities/cli.js +7 -0
  56. package/vendor/capabilities/software-catalog.js +36 -0
  57. package/vendor/capabilities/spec-catalog.js +2 -2
  58. package/src/catalog-dispatch.mjs +0 -605
  59. package/src/color.mjs +0 -71
  60. package/vendor/capabilities/capabilities/clusters.d.ts +0 -2
  61. package/vendor/capabilities/capabilities/fleet.d.ts +0 -8
  62. package/vendor/capabilities/capabilities/identity.d.ts +0 -3
  63. package/vendor/capabilities/capabilities/listings.d.ts +0 -51
  64. package/vendor/capabilities/capabilities/observability.d.ts +0 -5
  65. package/vendor/capabilities/capabilities/staff-access.d.ts +0 -29
  66. package/vendor/capabilities/capabilities/staff-access.js +0 -278
  67. package/vendor/capabilities/capabilities/staff-commerce.d.ts +0 -160
  68. package/vendor/capabilities/capabilities/staff-commerce.js +0 -689
  69. package/vendor/capabilities/capabilities/staff-fleet.d.ts +0 -140
  70. package/vendor/capabilities/capabilities/staff-fleet.js +0 -538
  71. package/vendor/capabilities/capabilities/staff-gtm.d.ts +0 -142
  72. package/vendor/capabilities/capabilities/staff-gtm.js +0 -521
  73. package/vendor/capabilities/capabilities/staff-shared.d.ts +0 -47
  74. package/vendor/capabilities/capabilities/staff-shared.js +0 -98
  75. package/vendor/capabilities/capabilities/tokens.d.ts +0 -10
  76. package/vendor/capabilities/capabilities/users.d.ts +0 -2
  77. package/vendor/capabilities/capability.d.ts +0 -93
  78. package/vendor/capabilities/catalog.d.ts +0 -3
  79. package/vendor/capabilities/catalog.js +0 -26
  80. package/vendor/capabilities/index.d.ts +0 -16
  81. package/vendor/capabilities/index.js +0 -16
  82. package/vendor/capabilities/names.d.ts +0 -6
  83. package/vendor/capabilities/role.d.ts +0 -6
  84. package/vendor/capabilities/role.js +0 -17
  85. package/vendor/capabilities/spec-catalog.d.ts +0 -78
@@ -0,0 +1,4273 @@
1
+ import { spawn as spawnProcessDefault } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { readFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { isIP } from "node:net";
6
+ import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
7
+ import { resolvedMountPath } from "./mount-commands.mjs";
8
+ import {
9
+ isReferenceMiss,
10
+ listActiveDirectReservations,
11
+ listOwnedActiveMachines,
12
+ reservationChoiceLabel as reservationLabel,
13
+ resolveMachineReference,
14
+ } from "./machine-reference.mjs";
15
+
16
+ const CANONICAL_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
17
+ const ACTIVE_RESERVATION = "active";
18
+ const TERMINAL_MACHINE_STATES = new Set([
19
+ "deleted",
20
+ "destroyed",
21
+ "failed",
22
+ "rejected",
23
+ "revoked",
24
+ "terminated",
25
+ "torn_down",
26
+ ]);
27
+ const READY_MACHINE_STATES = new Set(["active", "online", "provisioned", "ready", "running"]);
28
+
29
+ function record(value) {
30
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
31
+ }
32
+
33
+ function text(value) {
34
+ return typeof value === "string" && value.trim() ? value.trim() : null;
35
+ }
36
+
37
+ function resourceId(value) {
38
+ const item = record(value);
39
+ return text(item.id) ?? text(item.reservation_id) ?? text(item.reservationId);
40
+ }
41
+
42
+ function machineId(value) {
43
+ const machine = record(value);
44
+ return (
45
+ text(machine.id) ??
46
+ text(machine.machine_id) ??
47
+ text(machine.instance_id) ??
48
+ text(machine.node_id)
49
+ );
50
+ }
51
+
52
+ function machineState(value) {
53
+ const machine = record(value);
54
+ return (
55
+ text(machine.state) ??
56
+ text(machine.status) ??
57
+ text(machine.actual_state) ??
58
+ "unknown"
59
+ ).toLowerCase();
60
+ }
61
+
62
+ function machineAccessMode(value) {
63
+ const node = record(value);
64
+ const mode =
65
+ text(node.access_mode) ??
66
+ (["bare-metal", "virtual-machine"].includes(node.status) ? node.status : null);
67
+ return mode === "virtual-machine" ? "vm" : mode;
68
+ }
69
+
70
+ function reservationAccessMode(value) {
71
+ const reservation = record(value);
72
+ const mode =
73
+ (text(reservation.access_mode) ?? text(reservation.accessMode))?.toLowerCase() ?? null;
74
+ return mode === "virtual-machine" ? "vm" : mode;
75
+ }
76
+
77
+ function isDirectReservation(value) {
78
+ const mode = reservationAccessMode(value);
79
+ return !mode || mode === "vm" || mode === "bare-metal";
80
+ }
81
+
82
+ function machineIsReady(value) {
83
+ const machine = record(value);
84
+ if (machine.health_readiness?.ready === false) return false;
85
+ return (
86
+ READY_MACHINE_STATES.has(machineState(machine)) ||
87
+ (["bare-metal", "virtual-machine"].includes(machineState(machine)) &&
88
+ machine.health_readiness?.ready === true)
89
+ );
90
+ }
91
+
92
+ function machineLifecycle(value) {
93
+ const state = machineState(value);
94
+ if (TERMINAL_MACHINE_STATES.has(state)) return { class: "failed", phase: state };
95
+ if (machineIsReady(value)) return { class: "ready", phase: state };
96
+ return { class: "working", phase: state };
97
+ }
98
+
99
+ export function machineReachability(value) {
100
+ const machine = record(value);
101
+ if (text(machine.customer_lifecycle_state)?.toLowerCase() === "failed") {
102
+ return { class: "failed", phase: "operation_failed" };
103
+ }
104
+ if (TERMINAL_MACHINE_STATES.has(machineState(machine))) {
105
+ return { class: "failed", phase: "machine_terminal" };
106
+ }
107
+ if (!machineIsReady(machine)) {
108
+ return { class: "working", phase: "machine_not_ready" };
109
+ }
110
+ if (!sshInvocation(machine)) return { class: "working", phase: "ssh_pending" };
111
+ const fingerprints = machine.authorized_key_fingerprints;
112
+ if (Array.isArray(fingerprints) && fingerprints.length === 0) {
113
+ return { class: "working", phase: "key_delivery_pending" };
114
+ }
115
+ return { class: "ready", phase: "ssh_ready" };
116
+ }
117
+
118
+ function sshText(value) {
119
+ if (value == null || value === "") return null;
120
+ if (typeof value !== "string" || /[\s\u0000-\u001f\u007f]/.test(value)) {
121
+ throw new Error("SSH connection details contain invalid whitespace or control characters.");
122
+ }
123
+ return value;
124
+ }
125
+
126
+ function safeSshHost(host) {
127
+ const literal = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
128
+ if (literal.includes(":")) {
129
+ return /^[A-Za-z0-9:._%-]+$/.test(literal) && isIP(literal) === 6;
130
+ }
131
+ return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(host);
132
+ }
133
+
134
+ function machineHostname(value) {
135
+ return text(record(value).hostname) ?? "";
136
+ }
137
+
138
+ function machineUsername(value) {
139
+ const machine = record(value);
140
+ const endpoint = sshText(machine.ssh_endpoint);
141
+ const at = endpoint?.indexOf("@") ?? -1;
142
+ return at > 0
143
+ ? endpoint.slice(0, at)
144
+ : (sshText(machine.linux_username) ??
145
+ sshText(machine.tenant_username) ??
146
+ sshText(machine.username));
147
+ }
148
+
149
+ function machineHost(value) {
150
+ const machine = record(value);
151
+ const endpoint = sshText(machine.ssh_endpoint);
152
+ if (endpoint) {
153
+ const at = endpoint.indexOf("@");
154
+ return at >= 0 ? endpoint.slice(at + 1) : endpoint;
155
+ }
156
+ return (
157
+ sshText(machine.ssh_host) ??
158
+ sshText(machine.public_ip) ??
159
+ sshText(machine.external_ip) ??
160
+ sshText(machine.ip_address) ??
161
+ sshText(machine.ip) ??
162
+ sshText(machine.access_host) ??
163
+ (machineIsReady(machine) ? sshText(machine.hostname) : null)
164
+ );
165
+ }
166
+
167
+ function machinePort(value) {
168
+ const raw = record(value).ssh_port ?? record(value).port ?? 22;
169
+ const port =
170
+ typeof raw === "number" || (typeof raw === "string" && /^\d+$/.test(raw)) ? Number(raw) : NaN;
171
+ if (!Number.isInteger(port) || port < 1 || port > 65_535) {
172
+ throw new Error("The gateway returned an invalid SSH port.");
173
+ }
174
+ return port;
175
+ }
176
+
177
+ function shellQuote(value) {
178
+ const raw = String(value);
179
+ return /^[A-Za-z0-9_./:@%+=,-]+$/.test(raw)
180
+ ? raw
181
+ : `\u0027${raw.replace(/\u0027/g, "\u0027\\\u0027\u0027")}\u0027`;
182
+ }
183
+
184
+ function sshInvocation(machine, input = {}) {
185
+ const host = machineHost(machine);
186
+ if (host) {
187
+ const user = sshText(input.user) ?? machineUsername(machine) ?? "root";
188
+ if (!safeSshHost(host) || !/^[A-Za-z_][A-Za-z0-9_.-]*[$]?$/.test(user)) {
189
+ throw new Error("SSH host or login name is invalid.");
190
+ }
191
+ if (input.identityFile && /[\u0000-\u001f\u007f]/.test(input.identityFile)) {
192
+ throw new Error("SSH identity file contains control characters.");
193
+ }
194
+ const args = [];
195
+ if (text(input.identityFile)) args.push("-i", expandPath(input.identityFile));
196
+ const port = machinePort(machine);
197
+ if (port !== 22) args.push("-p", String(port));
198
+ args.push("--", `${user}@${host}`);
199
+ return { command: "ssh", args };
200
+ }
201
+ const raw = text(record(machine).ssh_command) ?? text(record(machine).iap_ssh_command);
202
+ return raw ? { raw } : null;
203
+ }
204
+
205
+ function commandText(invocation) {
206
+ return invocation.raw ?? [invocation.command, ...invocation.args].map(shellQuote).join(" ");
207
+ }
208
+
209
+ function expandPath(path) {
210
+ const raw = String(path);
211
+ return raw === "~" ? homedir() : raw.startsWith("~/") ? `${homedir()}/${raw.slice(2)}` : raw;
212
+ }
213
+
214
+ function keysFromPayload(payload) {
215
+ if (Array.isArray(payload)) return payload.map(record);
216
+ const data = record(payload);
217
+ const keys = Array.isArray(data.ssh_keys)
218
+ ? data.ssh_keys
219
+ : Array.isArray(data.keys)
220
+ ? data.keys
221
+ : null;
222
+ if (!keys) throw new Error("Ornn returned invalid SSH-key data.");
223
+ return keys.map(record);
224
+ }
225
+
226
+ function machinesFromPayload(payload) {
227
+ if (Array.isArray(payload)) return payload.map(record);
228
+ const machines = record(payload).nodes ?? record(payload).machines;
229
+ if (!Array.isArray(machines)) throw new Error("Ornn returned invalid machine data.");
230
+ return machines.map(record);
231
+ }
232
+
233
+ function activeKeys(keys) {
234
+ return keys.filter(
235
+ (key) => (text(key.status) ?? "active").toLowerCase() === "active" && !key.revoked_at
236
+ );
237
+ }
238
+
239
+ function activeMachines(machines) {
240
+ return machines.filter((machine) => !TERMINAL_MACHINE_STATES.has(machineState(machine)));
241
+ }
242
+
243
+ function projectKey(value) {
244
+ const key = record(value);
245
+ return {
246
+ id: resourceId(key),
247
+ label: text(key.label),
248
+ fingerprint: text(key.fingerprint) ?? fingerprint(text(key.public_key) ?? ""),
249
+ status: text(key.status) ?? "active",
250
+ };
251
+ }
252
+
253
+ function projectMachine(value) {
254
+ const machine = record(value);
255
+ const reachability = machineReachability(machine);
256
+ const lifecycleIntent = record(machine.customer_lifecycle_intent);
257
+ return {
258
+ id: machineId(machine),
259
+ hostname: machineHostname(machine) || null,
260
+ reservation_id: text(machine.reservation_id) ?? text(machine.commerce_reservation_id),
261
+ access_mode: machineAccessMode(machine),
262
+ network_mode: text(machine.network_mode),
263
+ lifecycle: machineLifecycle(machine),
264
+ operation: {
265
+ state: text(machine.customer_lifecycle_state),
266
+ intent: Object.keys(lifecycleIntent).length > 0 ? lifecycleIntent : null,
267
+ error: text(machine.customer_lifecycle_error),
268
+ },
269
+ reachability,
270
+ ssh: {
271
+ host: reachability.class === "ready" ? machineHost(machine) : null,
272
+ port: machinePort(machine),
273
+ user: machineUsername(machine),
274
+ },
275
+ };
276
+ }
277
+
278
+ function failedOperationRecoveryCommand(value, interaction) {
279
+ const machine = record(value);
280
+ if (text(machine.customer_lifecycle_state)?.toLowerCase() !== "failed") return null;
281
+ const intent = record(machine.customer_lifecycle_intent);
282
+ const operation = text(intent.operation)?.toLowerCase();
283
+ if (operation === "launch") {
284
+ const reservationId = text(intent.reservation_id) ?? text(machine.reservation_id);
285
+ return reservationId
286
+ ? `ornn launch ${shellQuote(reservationId)} --guided`
287
+ : "ornn launch --guided";
288
+ }
289
+ const requestId = shellQuote(interaction.idempotencyKey());
290
+ if (operation === "switch") {
291
+ const reservationId = text(intent.reservation_id) ?? text(machine.reservation_id);
292
+ const rawAccessMode = text(intent.target_access_mode)?.toLowerCase();
293
+ const accessMode = rawAccessMode === "virtual-machine" ? "vm" : rawAccessMode;
294
+ const rawSourceMode = text(intent.source_access_mode)?.toLowerCase();
295
+ const sourceMode = rawSourceMode === "virtual-machine" ? "vm" : rawSourceMode;
296
+ const networkMode = text(intent.target_network_mode)?.toLowerCase();
297
+ if (!reservationId || !["vm", "bare-metal"].includes(accessMode)) return null;
298
+ if (!["public", "private"].includes(networkMode)) return null;
299
+ return [
300
+ "ornn machines switch",
301
+ shellQuote(reservationId),
302
+ `--mode ${accessMode}`,
303
+ `--network ${networkMode}`,
304
+ accessMode !== sourceMode ? "--erase-local-data" : null,
305
+ "--yes",
306
+ `--idempotency-key ${requestId}`,
307
+ ]
308
+ .filter(Boolean)
309
+ .join(" ");
310
+ }
311
+ if (operation === "maintenance") {
312
+ const command = text(intent.mode) === "hard_reset" ? "reset" : "reboot";
313
+ const id = text(intent.instance_id) ?? machineId(machine);
314
+ if (!id) return null;
315
+ return [
316
+ `ornn machines ${command}`,
317
+ shellQuote(id),
318
+ command === "reset" ? "--erase-local-data" : null,
319
+ "--yes",
320
+ `--idempotency-key ${requestId}`,
321
+ ]
322
+ .filter(Boolean)
323
+ .join(" ");
324
+ }
325
+ return null;
326
+ }
327
+
328
+ function fingerprint(publicKey) {
329
+ const parts = String(publicKey).trim().split(/\s+/);
330
+ if (parts.length < 2) return null;
331
+ try {
332
+ return `SHA256:${createHash("sha256").update(Buffer.from(parts[1], "base64")).digest("base64").replace(/=/g, "")}`;
333
+ } catch {
334
+ return null;
335
+ }
336
+ }
337
+
338
+ function isPublicKey(value) {
339
+ return /^(?:ssh-(?:ed25519|rsa|dss)|ecdsa-sha2-\S+|sk-(?:ssh-ed25519|ecdsa-sha2-\S+)@\S+)\s+[A-Za-z0-9+/=]+(?:\s|$)/i.test(
340
+ String(value).trim()
341
+ );
342
+ }
343
+
344
+ async function publicKeyFromValue(value) {
345
+ const raw = text(value);
346
+ if (!raw) throw new Error("SSH public key is required.");
347
+ if (/PRIVATE KEY/.test(raw)) {
348
+ throw new Error(
349
+ "Refusing to upload private SSH-key material. Pass the matching public key file."
350
+ );
351
+ }
352
+ if (isPublicKey(raw)) return raw;
353
+ const path = expandPath(raw);
354
+ let contents;
355
+ try {
356
+ contents = (await readFile(path, "utf8")).trim();
357
+ } catch {
358
+ throw new Error("Could not read the selected SSH public-key file.");
359
+ }
360
+ if (isPublicKey(contents)) return contents;
361
+ if (!path.endsWith(".pub")) {
362
+ try {
363
+ const sibling = (await readFile(`${path}.pub`, "utf8")).trim();
364
+ if (isPublicKey(sibling)) return sibling;
365
+ } catch {
366
+ // The public-key validation below supplies the customer-facing result.
367
+ }
368
+ }
369
+ if (/PRIVATE KEY/.test(contents)) {
370
+ throw new Error(
371
+ "Refusing to upload private SSH-key material. Pass the matching public key file."
372
+ );
373
+ }
374
+ throw new Error("File does not contain a supported SSH public key.");
375
+ }
376
+
377
+ function requireCanonical(interaction, value, field, label) {
378
+ const ref = text(value);
379
+ if (!ref || !CANONICAL_ID.test(ref)) {
380
+ interaction.usage(`${label} must be a canonical identifier.`, {
381
+ code: "invalid_reference",
382
+ field,
383
+ });
384
+ }
385
+ return ref;
386
+ }
387
+
388
+ function positional(input, expected, usage, interaction) {
389
+ const values = Array.isArray(input.$positionals) ? input.$positionals : [];
390
+ if (values.length !== expected) interaction.usage(`Usage: ${usage}`);
391
+ return values;
392
+ }
393
+
394
+ function machineMutationPreflight(input, interaction, { requireYes = true } = {}) {
395
+ if (!interaction.machine) return;
396
+ if (!text(input.idempotencyKey)) {
397
+ interaction.usage("--idempotency-key is required for automation.", {
398
+ code: "idempotency_key_required",
399
+ field: "idempotencyKey",
400
+ });
401
+ }
402
+ if (requireYes && input.yes !== true) {
403
+ interaction.usage("--yes is required for automation after reviewing the exact intent.", {
404
+ code: "consent_required",
405
+ field: "yes",
406
+ });
407
+ }
408
+ }
409
+
410
+ async function selectedOrganization(context, input, interaction) {
411
+ const session = context.cli?.session;
412
+ if (!session) throw new Error("The current organization session is unavailable.");
413
+ let result = record(await session("context", { organization: input.organization }));
414
+ let data = record(result.data);
415
+ let selected = record(data.selected_organization);
416
+ if (!text(selected.id) && interaction.eligibleHuman) {
417
+ const choices = (Array.isArray(data.organizations) ? data.organizations : [])
418
+ .map(record)
419
+ .filter(
420
+ (organization) =>
421
+ organization.approved === true && text(organization.id) && text(organization.name)
422
+ )
423
+ .map((organization) => ({ label: text(organization.name), value: text(organization.id) }));
424
+ if (choices.length > 1) {
425
+ const answer = await interaction.ask({ label: "Organization", choices });
426
+ if (!answer)
427
+ interaction.usage("Choose an approved organization before launch.", {
428
+ code: "organization_required",
429
+ });
430
+ result = record(await session("context", { organization: answer }));
431
+ data = record(result.data);
432
+ selected = record(data.selected_organization);
433
+ }
434
+ }
435
+ const id = text(selected.id);
436
+ const name = text(selected.name);
437
+ if (!id || !name || selected.approved !== true) {
438
+ interaction.usage("Select one approved organization before launch.", {
439
+ code: "organization_required",
440
+ field: "organization",
441
+ });
442
+ }
443
+ return { id, name };
444
+ }
445
+
446
+ function assertActiveReservation(value, expectedRef, interaction, expectedMode = null) {
447
+ const reservation = record(value);
448
+ const ref = resourceId(reservation);
449
+ if (!ref || ref !== expectedRef) {
450
+ interaction.error("The gateway returned an unrelated capacity reservation.", {
451
+ code: "reservation_correlation_changed",
452
+ });
453
+ }
454
+ if ((text(reservation.status) ?? "").toLowerCase() !== ACTIVE_RESERVATION) {
455
+ interaction.error("The selected capacity reservation is no longer active and cannot launch.", {
456
+ code: "reservation_not_active",
457
+ exitCode: 1,
458
+ });
459
+ }
460
+ if (expectedMode && reservationAccessMode(reservation) !== expectedMode) {
461
+ interaction.error(
462
+ `The selected capacity reservation is for ${reservationAccessMode(reservation) ?? "another access mode"}, not ${expectedMode}.`,
463
+ {
464
+ code: "reservation_access_mode_mismatch",
465
+ exitCode: 2,
466
+ }
467
+ );
468
+ }
469
+ return reservation;
470
+ }
471
+
472
+ async function resolveReservation(context, input, interaction, expectedMode = null) {
473
+ const positionals = Array.isArray(input.$positionals) ? input.$positionals : [];
474
+ if (positionals.length > 1 || (positionals.length === 1 && input.reservation)) {
475
+ interaction.usage(
476
+ "Use one capacity-reservation target in either the positional argument or --reservation.",
477
+ {
478
+ code: "target_conflict",
479
+ field: "reservation",
480
+ }
481
+ );
482
+ }
483
+ const supplied = text(input.reservation) ?? text(positionals[0]);
484
+ if (supplied) {
485
+ const ref = requireCanonical(interaction, supplied, "reservation", "Capacity reservation");
486
+ return assertActiveReservation(
487
+ await context.fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(ref)}`),
488
+ ref,
489
+ interaction,
490
+ expectedMode
491
+ );
492
+ }
493
+ if (!interaction.eligibleHuman || interaction.machine) {
494
+ interaction.usage("A canonical capacity-reservation target is required for automation.", {
495
+ code: "missing_input",
496
+ field: "reservation",
497
+ });
498
+ }
499
+ const payload = await context.fetch("/v1/workflows/commerce/reservations?status=active&limit=50");
500
+ const requestedMode = expectedMode ?? text(input.mode)?.toLowerCase() ?? null;
501
+ const candidates = (Array.isArray(payload) ? payload : (record(payload).reservations ?? []))
502
+ .map(record)
503
+ .filter(
504
+ (reservation) =>
505
+ (text(reservation.status) ?? "").toLowerCase() === ACTIVE_RESERVATION &&
506
+ resourceId(reservation) &&
507
+ (!requestedMode || reservationAccessMode(reservation) === requestedMode)
508
+ );
509
+ if (!candidates.length) {
510
+ interaction.error("No active capacity reservation is eligible for launch.", {
511
+ code: "no_active_reservation",
512
+ });
513
+ }
514
+ let ref = resourceId(candidates[0]);
515
+ if (candidates.length > 1) {
516
+ ref = await interaction.ask({
517
+ label: "Capacity reservation",
518
+ choices: candidates.map((reservation) => ({
519
+ label: reservationLabel(reservation),
520
+ value: resourceId(reservation),
521
+ })),
522
+ });
523
+ }
524
+ if (!ref)
525
+ interaction.usage("Choose one active capacity reservation before launch.", {
526
+ code: "missing_input",
527
+ });
528
+ return assertActiveReservation(
529
+ await context.fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(ref)}`),
530
+ ref,
531
+ interaction,
532
+ expectedMode
533
+ );
534
+ }
535
+
536
+ async function fetchOrganizationKeys(context) {
537
+ return activeKeys(
538
+ keysFromPayload(await context.fetch("/v1/workflows/compute/nodes/organizations/me/ssh-keys"))
539
+ );
540
+ }
541
+
542
+ export function keyInputShapePreflight(input, interaction) {
543
+ const selectors = [input.key, input.keyId, input.publicKey, input.publicKeyFile].filter((value) =>
544
+ text(value)
545
+ );
546
+ if (selectors.length > 1) {
547
+ interaction.usage(
548
+ "Select one SSH key with --key, --key-id, --public-key, or --public-key-file.",
549
+ {
550
+ code: "key_selector_conflict",
551
+ field: "key",
552
+ }
553
+ );
554
+ }
555
+ if (text(input.label) && !text(input.publicKey) && !text(input.publicKeyFile)) {
556
+ interaction.usage("--label is only valid when adding a new public key.", {
557
+ code: "key_label_without_new_key",
558
+ field: "label",
559
+ });
560
+ }
561
+ const reference = text(input.keyId) ?? text(input.key);
562
+ if (reference && interaction.machine && !CANONICAL_ID.test(reference)) {
563
+ interaction.usage("Automation must select a canonical SSH-key identifier.", {
564
+ code: "invalid_reference",
565
+ field: "key",
566
+ });
567
+ }
568
+ }
569
+
570
+ export async function keyPlanFromInput(context, input, interaction) {
571
+ keyInputShapePreflight(input, interaction);
572
+ const keys = await fetchOrganizationKeys(context);
573
+ const reference = text(input.keyId) ?? text(input.key);
574
+ if (reference) {
575
+ const matches = keys.filter(
576
+ (key) =>
577
+ resourceId(key) === reference || (!interaction.machine && text(key.label) === reference)
578
+ );
579
+ if (matches.length !== 1) {
580
+ interaction.error(
581
+ matches.length
582
+ ? `More than one SSH key has the exact label ${reference}.`
583
+ : `SSH key not found: ${reference}.`,
584
+ { code: matches.length ? "ssh_key_ambiguous" : "ssh_key_not_found" }
585
+ );
586
+ }
587
+ return { kind: "existing", key: matches[0] };
588
+ }
589
+
590
+ let publicKeyInput = text(input.publicKey) ?? text(input.publicKeyFile);
591
+ if (!publicKeyInput && interaction.eligibleHuman) {
592
+ if (keys.length === 1) return { kind: "existing", key: keys[0] };
593
+ if (keys.length > 1) {
594
+ const selected = await interaction.ask({
595
+ label: "SSH key",
596
+ choices: keys.map((key) => ({
597
+ label: `${text(key.label) ?? "SSH key"} (${text(key.fingerprint) ?? resourceId(key)})`,
598
+ value: resourceId(key),
599
+ })),
600
+ });
601
+ const key = keys.find((candidate) => resourceId(candidate) === selected);
602
+ if (key) return { kind: "existing", key };
603
+ }
604
+ publicKeyInput = await interaction.ask({ label: "SSH public key file or value" });
605
+ }
606
+ if (!publicKeyInput) {
607
+ interaction.usage("Select or add an SSH key before launch.", {
608
+ code: "ssh_key_required",
609
+ field: "key",
610
+ });
611
+ }
612
+ const publicKey = await publicKeyFromValue(publicKeyInput);
613
+ const existing = keys.find((key) => text(key.public_key) === publicKey);
614
+ if (existing) {
615
+ const requestedLabel = text(input.label);
616
+ if (requestedLabel && text(existing.label) !== requestedLabel) {
617
+ interaction.usage(
618
+ "This public key already exists under a different label; select the existing key instead.",
619
+ {
620
+ code: "ssh_key_label_conflict",
621
+ field: "label",
622
+ }
623
+ );
624
+ }
625
+ return { kind: "existing", key: existing };
626
+ }
627
+ let label = text(input.label);
628
+ if (!label && interaction.eligibleHuman)
629
+ label = await interaction.ask({ label: "SSH key label" });
630
+ if (!label) {
631
+ interaction.usage("--label is required when adding a new SSH key.", {
632
+ code: "missing_input",
633
+ field: "label",
634
+ });
635
+ }
636
+ return {
637
+ kind: "create",
638
+ key: {
639
+ id: null,
640
+ label,
641
+ fingerprint: fingerprint(publicKey),
642
+ public_key: publicKey,
643
+ status: "active",
644
+ },
645
+ };
646
+ }
647
+
648
+ export async function materializeKey(context, plan, interaction, idempotencyKey) {
649
+ const keys = await fetchOrganizationKeys(context);
650
+ if (plan.kind === "existing") {
651
+ const ref = resourceId(plan.key);
652
+ const current = keys.find((key) => resourceId(key) === ref);
653
+ if (!current) {
654
+ interaction.error("The selected SSH key changed before launch.", {
655
+ code: "ssh_key_changed",
656
+ });
657
+ }
658
+ return current;
659
+ }
660
+ const publicKey = text(plan.key.public_key);
661
+ const replay = keys.find((key) => text(key.public_key) === publicKey);
662
+ if (replay) return replay;
663
+ const created = record(
664
+ await context.fetch("/v1/workflows/compute/nodes/organizations/me/ssh-keys", {
665
+ method: "POST",
666
+ headers: { "Idempotency-Key": idempotencyKey },
667
+ body: { label: text(plan.key.label), public_key: publicKey },
668
+ })
669
+ );
670
+ if (!resourceId(created) || text(created.public_key) !== publicKey) {
671
+ interaction.error("The saved SSH key did not match the reviewed public key.", {
672
+ code: "ssh_key_correlation_changed",
673
+ });
674
+ }
675
+ return created;
676
+ }
677
+
678
+ async function fetchAssignedKeys(context, reservationId) {
679
+ return activeKeys(
680
+ keysFromPayload(
681
+ await context.fetch(
682
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/ssh-keys`
683
+ )
684
+ )
685
+ );
686
+ }
687
+
688
+ async function ensureAssignment(context, reservationId, key, idempotencyKey, interaction) {
689
+ const keyId = resourceId(key);
690
+ const assigned = await fetchAssignedKeys(context, reservationId);
691
+ const existing = assigned.find((candidate) => resourceId(candidate) === keyId);
692
+ if (existing) return { created: false, key: existing };
693
+ const created = record(
694
+ await context.fetch(
695
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/ssh-keys`,
696
+ {
697
+ method: "POST",
698
+ headers: { "Idempotency-Key": idempotencyKey },
699
+ body: { ssh_key_id: keyId },
700
+ }
701
+ )
702
+ );
703
+ if (resourceId(created) !== keyId) {
704
+ interaction.error("The SSH-key assignment did not match the reviewed key.", {
705
+ code: "ssh_key_assignment_changed",
706
+ });
707
+ }
708
+ return { created: true, key: created };
709
+ }
710
+
711
+ async function fetchMachines(context, reservationId) {
712
+ return activeMachines(
713
+ machinesFromPayload(
714
+ await context.fetch(
715
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/machines`
716
+ )
717
+ )
718
+ );
719
+ }
720
+
721
+ function keyDeliveryForMachine(key, machine) {
722
+ const keyId = resourceId(key);
723
+ const metadata = Array.isArray(machine.authorized_key_metadata)
724
+ ? machine.authorized_key_metadata
725
+ .map(record)
726
+ .find(
727
+ (item) =>
728
+ text(item.ssh_key_id) === keyId || text(item.fingerprint) === text(key.fingerprint)
729
+ )
730
+ : null;
731
+ const fingerprints = Array.isArray(machine.authorized_key_fingerprints)
732
+ ? machine.authorized_key_fingerprints
733
+ : [];
734
+ const installed =
735
+ text(key.fingerprint) && fingerprints.includes(text(key.fingerprint)) && machine.keys_pushed_at;
736
+ return {
737
+ machine_id: machineId(machine),
738
+ hostname: machineHostname(machine) || null,
739
+ machine_state: machineState(machine),
740
+ status: text(metadata?.status) ?? (installed ? "installed" : "associated"),
741
+ queued_at: text(metadata?.queued_at),
742
+ pushed_at: text(metadata?.pushed_at) ?? (installed ? text(machine.keys_pushed_at) : null),
743
+ failed_at: text(metadata?.failed_at),
744
+ failure_reason: text(metadata?.failure_reason),
745
+ request_id: text(metadata?.request_id),
746
+ };
747
+ }
748
+
749
+ function assignmentStatus(reservationId, keys, machines) {
750
+ return {
751
+ reservation_id: reservationId,
752
+ machine_count: machines.length,
753
+ keys: keys.map((key) => ({
754
+ ...projectKey(key),
755
+ machines: machines.map((machine) => keyDeliveryForMachine(key, machine)),
756
+ })),
757
+ };
758
+ }
759
+
760
+ function isSshReady(machine, key) {
761
+ if (machineReachability(machine).class !== "ready") return false;
762
+ const fingerprints = Array.isArray(machine.authorized_key_fingerprints)
763
+ ? machine.authorized_key_fingerprints
764
+ : null;
765
+ const keyFingerprint = text(key?.fingerprint);
766
+ return !fingerprints || !keyFingerprint || fingerprints.includes(keyFingerprint);
767
+ }
768
+
769
+ function launchInputPreflight(input, interaction) {
770
+ const positionals = Array.isArray(input.$positionals) ? input.$positionals : [];
771
+ if (positionals.length > 1 || (positionals.length === 1 && text(input.reservation))) {
772
+ interaction.usage(
773
+ "Use one capacity-reservation target in either the positional argument or --reservation.",
774
+ {
775
+ code: "target_conflict",
776
+ field: "reservation",
777
+ }
778
+ );
779
+ }
780
+ const selectors = [input.key, input.keyId, input.publicKey, input.publicKeyFile].filter((value) =>
781
+ text(value)
782
+ );
783
+ if (selectors.length > 1) {
784
+ interaction.usage(
785
+ "Select one SSH key with --key, --key-id, --public-key, or --public-key-file.",
786
+ {
787
+ code: "key_selector_conflict",
788
+ field: "key",
789
+ }
790
+ );
791
+ }
792
+ if (input.wait && input.noWait) {
793
+ interaction.usage("--wait and --no-wait cannot be combined.", {
794
+ code: "wait_conflict",
795
+ field: "wait",
796
+ });
797
+ }
798
+ if (input.noWait && text(input.timeout)) {
799
+ interaction.usage("--timeout cannot be combined with --no-wait.", {
800
+ code: "wait_conflict",
801
+ field: "timeout",
802
+ });
803
+ }
804
+ if (text(input.timeout)) timeoutMilliseconds(input.timeout, interaction);
805
+
806
+ const target = text(input.reservation) ?? text(positionals[0]);
807
+ if (interaction.eligibleHuman && !interaction.machine) return;
808
+ requireCanonical(interaction, target, "reservation", "Capacity reservation");
809
+ requireCanonical(interaction, input.organization, "organization", "Organization");
810
+ const keyRef = text(input.keyId) ?? text(input.key);
811
+ const suppliedPublicKey = text(input.publicKey) ?? text(input.publicKeyFile);
812
+ if (!keyRef && !suppliedPublicKey) {
813
+ interaction.usage("--key or an explicit public key is required for automation.", {
814
+ code: "ssh_key_required",
815
+ field: "key",
816
+ });
817
+ }
818
+ if (keyRef) requireCanonical(interaction, keyRef, "key", "SSH key");
819
+ if (suppliedPublicKey && !text(input.label)) {
820
+ interaction.usage("--label is required when adding a new SSH key.", {
821
+ code: "missing_input",
822
+ field: "label",
823
+ });
824
+ }
825
+ if (input.yes !== true) {
826
+ interaction.usage("--yes is required for automation after reviewing the launch intent.", {
827
+ code: "consent_required",
828
+ field: "yes",
829
+ });
830
+ }
831
+ if (!text(input.idempotencyKey)) {
832
+ interaction.usage("--idempotency-key is required for automation.", {
833
+ code: "idempotency_key_required",
834
+ field: "idempotencyKey",
835
+ });
836
+ }
837
+ if (input.wait && !text(input.timeout)) {
838
+ interaction.usage("--wait requires a bounded --timeout for automation.", {
839
+ code: "timeout_required",
840
+ field: "timeout",
841
+ });
842
+ }
843
+ if (!input.wait && text(input.timeout)) {
844
+ interaction.usage("--timeout requires --wait for automation.", {
845
+ code: "wait_required",
846
+ field: "wait",
847
+ });
848
+ }
849
+ }
850
+
851
+ function timeoutMilliseconds(value, interaction) {
852
+ if (!text(value)) return 600_000;
853
+ const match = /^([0-9]+(?:\.[0-9]+)?)([smh]?)$/i.exec(text(value));
854
+ if (!match)
855
+ interaction.usage("--timeout must be a positive duration such as 60s, 10m, or 1h.", {
856
+ field: "timeout",
857
+ });
858
+ const multiplier = { "": 1_000, s: 1_000, m: 60_000, h: 3_600_000 }[match[2].toLowerCase()];
859
+ const result = Number(match[1]) * multiplier;
860
+ if (!Number.isFinite(result) || result <= 0)
861
+ interaction.usage("--timeout must be a positive duration.", { field: "timeout" });
862
+ return result;
863
+ }
864
+
865
+ async function normalizePublicKeyInput(input) {
866
+ const source = text(input.publicKey) ?? text(input.publicKeyFile);
867
+ if (!source) return input;
868
+ return {
869
+ ...input,
870
+ publicKey: await publicKeyFromValue(source),
871
+ publicKeyFile: undefined,
872
+ };
873
+ }
874
+ function isPreassignedUnlaunchedCapacity(machines, assignedKeys, reviewedKey) {
875
+ if (machines.length !== 1) return false;
876
+ const reviewedKeyId = resourceId(reviewedKey);
877
+ if (assignedKeys.some((key) => resourceId(key) !== reviewedKeyId)) return false;
878
+ const machine = machines[0];
879
+ const fingerprints = Array.isArray(machine.authorized_key_fingerprints)
880
+ ? machine.authorized_key_fingerprints.filter(text)
881
+ : [];
882
+ const delivery = Array.isArray(machine.authorized_key_metadata)
883
+ ? machine.authorized_key_metadata
884
+ : [];
885
+ return (
886
+ machineAccessMode(machine) === "bare-metal" &&
887
+ fingerprints.length === 0 &&
888
+ !text(machine.keys_pushed_at) &&
889
+ delivery.length === 0
890
+ );
891
+ }
892
+
893
+ function launchOwnerSnapshot(machines) {
894
+ if (machines.length !== 1) return null;
895
+ return [machineId(machines[0]), machineUsername(machines[0])];
896
+ }
897
+
898
+ function withAuthoritativeUsername(input, _machines, interaction) {
899
+ const requested = text(input.username);
900
+ if (requested && requested !== "tenant") {
901
+ interaction.usage("Linux login is always tenant; --username cannot request another name.", {
902
+ code: "launch_username_unsupported",
903
+ });
904
+ }
905
+ return { ...input, username: "tenant" };
906
+ }
907
+
908
+ function validatedMachine(machines, key, input, interaction, expectedMode = "vm") {
909
+ if (!machines.length) return null;
910
+ if (machines.length !== 1) {
911
+ interaction.error(
912
+ "The capacity reservation already has multiple active machines; launch cannot choose one.",
913
+ {
914
+ code: "machine_ambiguous",
915
+ }
916
+ );
917
+ }
918
+ const machine = machines[0];
919
+ if (machineAccessMode(machine) !== expectedMode) {
920
+ interaction.error(
921
+ "The existing machine has a different access mode than the reviewed launch.",
922
+ {
923
+ code: "launch_input_changed",
924
+ }
925
+ );
926
+ }
927
+ const username = text(input.username);
928
+ if (username && username !== "tenant") {
929
+ interaction.usage("Linux login is always tenant; --username cannot request another name.", {
930
+ code: "launch_username_unsupported",
931
+ });
932
+ }
933
+ const fingerprintValue = text(key.fingerprint);
934
+ const assigned = Array.isArray(machine.authorized_key_fingerprints)
935
+ ? machine.authorized_key_fingerprints
936
+ : [];
937
+ if (fingerprintValue && assigned.length && !assigned.includes(fingerprintValue)) {
938
+ interaction.error(
939
+ "The existing machine has a different SSH-key intent than the reviewed launch.",
940
+ {
941
+ code: "launch_input_changed",
942
+ }
943
+ );
944
+ }
945
+ return machine;
946
+ }
947
+
948
+ function machineReplayState(machines, key, input, interaction, expectedMode = "vm") {
949
+ const existing = validatedMachine(machines, key, input, interaction, expectedMode);
950
+ // A projected machine is not proof that its owner packet was durably
951
+ // completed. Re-submit the stable request while provisioning so the owner
952
+ // can reconstruct a packet lost with an orchestrator instance restart.
953
+ return {
954
+ existing,
955
+ machine: existing && machineReachability(existing).class !== "working" ? existing : null,
956
+ replayed: Boolean(existing),
957
+ };
958
+ }
959
+
960
+ function launchData(reservationId, machine, key, waitPolicy, replayed) {
961
+ const invocation = sshInvocation(machine);
962
+ return {
963
+ mode: "vm",
964
+ replayed,
965
+ capacity_reservation: { id: reservationId, status: ACTIVE_RESERVATION },
966
+ machine: projectMachine(machine),
967
+ ssh_key: projectKey(key),
968
+ wait: waitPolicy,
969
+ ssh_command: invocation ? commandText(invocation) : null,
970
+ created_resources: { private_network: false, volume: false },
971
+ };
972
+ }
973
+
974
+ function acceptedWaitFailure(interaction, error, { data, label, phase, resume, retryCommand }) {
975
+ const status = Number(error?.status);
976
+ const authorization = status === 401 || status === 403;
977
+ if (!isRecoverableWaitFailure(error)) throw error;
978
+ const authentication = status === 401;
979
+ const command = authorization ? "ornn login" : retryCommand;
980
+ return interaction.result({
981
+ action: {
982
+ command,
983
+ description: authorization
984
+ ? "Restore an authorized session, then resume the exact accepted resource."
985
+ : "Retry the exact accepted resource wait.",
986
+ type: "command",
987
+ },
988
+ data: { ...data, remote_work_cancelled: false },
989
+ errors: [
990
+ {
991
+ code: authentication
992
+ ? "authentication_required"
993
+ : authorization
994
+ ? "authorization_denied"
995
+ : "service_unavailable",
996
+ message: authorization
997
+ ? `The session could not continue waiting for ${label}; accepted remote work continues.`
998
+ : `The local wait for ${label} lost contact with the service; accepted remote work continues.`,
999
+ retryable: status !== 403,
1000
+ resource_ref: resume.ref,
1001
+ },
1002
+ ],
1003
+ exitCode: authorization ? 5 : 6,
1004
+ outcome: { class: "working", phase },
1005
+ resume,
1006
+ presentation: {
1007
+ lines: [`Accepted ${label} work continues; no remote cancellation was sent.`],
1008
+ next: command,
1009
+ },
1010
+ });
1011
+ }
1012
+
1013
+ function uncertainMutationFailure(
1014
+ interaction,
1015
+ error,
1016
+ { data, label, phase, resume, retryCommand }
1017
+ ) {
1018
+ if (!isRecoverableWaitFailure(error)) throw error;
1019
+ const status = Number(error?.status);
1020
+ const authentication = status === 401;
1021
+ const authorization = status === 403;
1022
+ const command = authentication ? "ornn login" : retryCommand;
1023
+ return interaction.result({
1024
+ action: {
1025
+ command,
1026
+ description: authentication
1027
+ ? "Sign in, then resume the preserved exact operation."
1028
+ : "Reconcile the same exact reviewed operation.",
1029
+ type: "command",
1030
+ },
1031
+ data: { ...data, remote_work_cancelled: false },
1032
+ errors: [
1033
+ {
1034
+ code: authentication
1035
+ ? "authentication_required"
1036
+ : authorization
1037
+ ? "authorization_denied"
1038
+ : "service_unavailable",
1039
+ message: authorization
1040
+ ? `Authorization ended while reconciling ${label}; no cancellation was sent.`
1041
+ : `The response for ${label} was lost or unavailable; its remote outcome must be reconciled.`,
1042
+ retryable: status !== 403,
1043
+ resource_ref: resume.ref,
1044
+ },
1045
+ ],
1046
+ exitCode: authorization ? 5 : 6,
1047
+ outcome: { class: "working", phase },
1048
+ resume,
1049
+ presentation: {
1050
+ lines: [`The exact ${label} intent is preserved; its remote outcome is not yet known.`],
1051
+ next: command,
1052
+ },
1053
+ });
1054
+ }
1055
+
1056
+ export const vmLaunchModeHandler = {
1057
+ mode: "vm",
1058
+ async launch({ context, input, interaction }) {
1059
+ launchInputPreflight(input, interaction);
1060
+ input = await normalizePublicKeyInput(input);
1061
+ const organization = await selectedOrganization(context, input, interaction);
1062
+ const reservation = await resolveReservation(context, input, interaction, "vm");
1063
+ const reservationId = resourceId(reservation);
1064
+ const keyPlan = await keyPlanFromInput(context, input, interaction);
1065
+ const [reviewedMachines, reviewedRelationships] = await Promise.all([
1066
+ fetchMachines(context, reservationId),
1067
+ bareMetalRelationships(context, reservationId),
1068
+ ]);
1069
+ input = withAuthoritativeUsername(input, reviewedMachines, interaction);
1070
+ const reviewedOwner = launchOwnerSnapshot(reviewedMachines);
1071
+ const reviewedRelationshipSnapshot = JSON.stringify(reviewedRelationships);
1072
+ const waitByDefault = interaction.eligibleHuman && !interaction.machine;
1073
+ const shouldWait = input.noWait ? false : input.wait ? true : waitByDefault;
1074
+ const reviewAccepted = await interaction.review({
1075
+ title: "VM launch review",
1076
+ terms: [
1077
+ { label: "Organization", value: organization.name },
1078
+ {
1079
+ label: "Capacity reservation",
1080
+ value: `${reservationLabel(reservation)} (${reservationId})`,
1081
+ },
1082
+ { label: "Access mode", value: "VM" },
1083
+ {
1084
+ label: "SSH key",
1085
+ value: `${text(keyPlan.key.label) ?? "SSH key"} (${text(keyPlan.key.fingerprint) ?? resourceId(keyPlan.key)})`,
1086
+ },
1087
+ { label: "SSH user", value: text(input.username) ?? "Owner default" },
1088
+ { label: "Machine count", value: "1" },
1089
+ { label: "Network", value: relationshipLabel(reviewedRelationships, "network") },
1090
+ { label: "Storage", value: relationshipLabel(reviewedRelationships, "storage") },
1091
+ {
1092
+ label: "Wait",
1093
+ value: shouldWait ? "Until SSH is usable" : "Return after durable acceptance",
1094
+ },
1095
+ ],
1096
+ prompt: "Launch this VM?",
1097
+ });
1098
+ if (!reviewAccepted) {
1099
+ return interaction.result({
1100
+ data: null,
1101
+ errors: [{ code: "declined", message: "VM launch declined.", retryable: false }],
1102
+ exitCode: 1,
1103
+ outcome: { class: "ended", phase: "declined" },
1104
+ presentation: {
1105
+ lines: ["VM launch declined. No machine was created."],
1106
+ next: "ornn launch --mode vm",
1107
+ },
1108
+ });
1109
+ }
1110
+
1111
+ const currentReservation = assertActiveReservation(
1112
+ await context.fetch(
1113
+ `/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`
1114
+ ),
1115
+ reservationId,
1116
+ interaction,
1117
+ "vm"
1118
+ );
1119
+ if (reservationLabel(currentReservation) !== reservationLabel(reservation)) {
1120
+ interaction.error(
1121
+ "Capacity eligibility changed after review; launch stopped before mutation.",
1122
+ {
1123
+ code: "reservation_eligibility_changed",
1124
+ }
1125
+ );
1126
+ }
1127
+ const idempotencyKey = text(input.idempotencyKey) ?? interaction.idempotencyKey();
1128
+ const [existingAssigned, projectedMachines, currentRelationships] = await Promise.all([
1129
+ fetchAssignedKeys(context, reservationId),
1130
+ fetchMachines(context, reservationId),
1131
+ bareMetalRelationships(context, reservationId),
1132
+ ]);
1133
+ const currentOwner = launchOwnerSnapshot(projectedMachines);
1134
+ if (
1135
+ JSON.stringify(currentOwner) !== JSON.stringify(reviewedOwner) ||
1136
+ JSON.stringify(currentRelationships) !== reviewedRelationshipSnapshot
1137
+ ) {
1138
+ interaction.error(
1139
+ "The reservation owner, network, or volume intent changed after review; launch stopped before mutation.",
1140
+ { code: "reservation_eligibility_changed" }
1141
+ );
1142
+ }
1143
+ const existingMachines = isPreassignedUnlaunchedCapacity(
1144
+ projectedMachines,
1145
+ existingAssigned,
1146
+ keyPlan.key
1147
+ )
1148
+ ? []
1149
+ : projectedMachines;
1150
+ if (existingMachines.length && keyPlan.kind === "create") {
1151
+ interaction.error(
1152
+ "The existing machine has a different SSH-key intent than the reviewed launch.",
1153
+ {
1154
+ code: "launch_input_changed",
1155
+ }
1156
+ );
1157
+ }
1158
+ const key = await materializeKey(context, keyPlan, interaction, idempotencyKey);
1159
+ const replay = machineReplayState(existingMachines, key, input, interaction, "vm");
1160
+ let machine = replay.machine;
1161
+ if (
1162
+ replay.existing &&
1163
+ !existingAssigned.some((candidate) => resourceId(candidate) === resourceId(key))
1164
+ ) {
1165
+ interaction.error("The existing machine does not use the reviewed SSH-key assignment.", {
1166
+ code: "launch_input_changed",
1167
+ });
1168
+ }
1169
+ if (!machine) {
1170
+ let launch;
1171
+ try {
1172
+ launch = await context.fetch(
1173
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/launch`,
1174
+ {
1175
+ method: "POST",
1176
+ body: {
1177
+ access_mode: "virtual-machine",
1178
+ network_mode: "public",
1179
+ image_id: null,
1180
+ machine_count: 1,
1181
+ request_id: idempotencyKey,
1182
+ ssh_key_ids: [resourceId(key)],
1183
+ zone: null,
1184
+ },
1185
+ }
1186
+ );
1187
+ } catch (error) {
1188
+ if (!isRecoverableWaitFailure(error)) throw error;
1189
+ const discovered = await fetchMachines(context, reservationId).catch(() => []);
1190
+ const candidate =
1191
+ activeMachines(discovered).find((row) => machineAccessMode(row) === "vm") ?? null;
1192
+ const retryCommand = [
1193
+ "ornn",
1194
+ "launch",
1195
+ reservationId,
1196
+ "--mode",
1197
+ "vm",
1198
+ "--key",
1199
+ resourceId(key),
1200
+ "--idempotency-key",
1201
+ idempotencyKey,
1202
+ ...(interaction.machine
1203
+ ? ["--organization", resourceId(organization), "--yes", "--no-wait", "--format", "json"]
1204
+ : []),
1205
+ ].join(" ");
1206
+ return uncertainMutationFailure(interaction, error, {
1207
+ data: {
1208
+ capacity_reservation: { id: reservationId },
1209
+ machine: candidate ? projectMachine(candidate) : null,
1210
+ ssh_key: projectKey(key),
1211
+ wait: { enabled: shouldWait, timeout: text(input.timeout) ?? "10m" },
1212
+ },
1213
+ label: "VM launch",
1214
+ phase: "launch_reconciliation",
1215
+ resume: candidate
1216
+ ? { kind: "machine", ref: machineId(candidate) }
1217
+ : { kind: "reservation", ref: reservationId },
1218
+ retryCommand,
1219
+ });
1220
+ }
1221
+ const launched = activeMachines(machinesFromPayload(launch));
1222
+ if (launched.length !== 1) {
1223
+ interaction.error("Launch did not return one durable machine reference.", {
1224
+ code: "machine_correlation_failed",
1225
+ retryable: true,
1226
+ });
1227
+ }
1228
+ machine = validatedMachine(launched, key, input, interaction, "vm");
1229
+ }
1230
+
1231
+ const waitPolicy = shouldWait
1232
+ ? { enabled: true, timeout: text(input.timeout) ?? "10m" }
1233
+ : { enabled: false, timeout: null };
1234
+ const resume = { kind: "machine", ref: machineId(machine) };
1235
+ if (!shouldWait) {
1236
+ const ready = isSshReady(machine, key);
1237
+ return interaction.result({
1238
+ data: launchData(reservationId, machine, key, waitPolicy, replay.replayed),
1239
+ outcome: ready
1240
+ ? { class: "ready", phase: "ssh_ready" }
1241
+ : { class: "working", phase: "provisioning" },
1242
+ resume,
1243
+ presentation: {
1244
+ lines: [
1245
+ ready
1246
+ ? `VM is SSH-ready: ${machineHostname(machine) || "selected VM"}`
1247
+ : "VM launch accepted; provisioning continues.",
1248
+ ],
1249
+ next: "ornn connect",
1250
+ },
1251
+ });
1252
+ }
1253
+
1254
+ let waitOutcome;
1255
+ try {
1256
+ waitOutcome = await interaction.waitForGoal({
1257
+ command: "launch",
1258
+ initial: machine,
1259
+ timeoutMs: timeoutMilliseconds(input.timeout, interaction),
1260
+ isReady: (current) => isSshReady(current, key),
1261
+ isTerminal: (current) => TERMINAL_MACHINE_STATES.has(machineState(current)),
1262
+ message: (current, elapsedMs) =>
1263
+ isSshReady(current, key)
1264
+ ? "The selected VM is SSH-ready."
1265
+ : `The selected VM is ${machineState(current)}; waiting for SSH (${Math.floor(elapsedMs / 1000)}s).`,
1266
+ projectProgress: (current) => ({ machine: projectMachine(current) }),
1267
+ poll: async ({ signal }) =>
1268
+ context.fetch(`/v1/workflows/compute/nodes/${encodeURIComponent(machineId(machine))}`, {
1269
+ signal,
1270
+ }),
1271
+ });
1272
+ } catch (error) {
1273
+ return acceptedWaitFailure(interaction, error, {
1274
+ data: launchData(reservationId, machine, key, waitPolicy, replay.replayed),
1275
+ label: "VM launch",
1276
+ phase: "provisioning",
1277
+ resume,
1278
+ retryCommand: `ornn connect ${machineId(machine)}`,
1279
+ });
1280
+ }
1281
+ machine = waitOutcome.value;
1282
+ const data = launchData(reservationId, machine, key, waitPolicy, replay.replayed);
1283
+ if (waitOutcome.kind === "ready") {
1284
+ return interaction.result({
1285
+ data,
1286
+ outcome: { class: "ready", phase: "ssh_ready" },
1287
+ resume,
1288
+ presentation: {
1289
+ lines: [`VM is SSH-ready: ${machineHostname(machine) || "selected VM"}`],
1290
+ next: "ornn connect",
1291
+ },
1292
+ });
1293
+ }
1294
+ if (waitOutcome.kind === "timeout") {
1295
+ return interaction.result({
1296
+ action: {
1297
+ command: `ornn connect ${machineId(machine)}`,
1298
+ description: "Retry the exact machine connection",
1299
+ type: "command",
1300
+ },
1301
+ data,
1302
+ errors: [
1303
+ {
1304
+ code: "wait_timeout",
1305
+ message: "Local wait ended before SSH was ready; the remote launch continues.",
1306
+ retryable: true,
1307
+ },
1308
+ ],
1309
+ exitCode: 124,
1310
+ outcome: { class: "working", phase: "provisioning" },
1311
+ resume,
1312
+ presentation: {
1313
+ lines: [
1314
+ `VM remains ${machineState(machine)}. Local wait timed out; remote launch continues.`,
1315
+ ],
1316
+ next: "ornn connect",
1317
+ },
1318
+ });
1319
+ }
1320
+ if (waitOutcome.kind === "interrupted") {
1321
+ return interaction.result({
1322
+ action: {
1323
+ command: `ornn connect ${machineId(machine)}`,
1324
+ description: "Resume from the exact machine",
1325
+ type: "command",
1326
+ },
1327
+ data,
1328
+ errors: [
1329
+ {
1330
+ code: "interrupted",
1331
+ message: "Local wait was interrupted; the remote launch continues.",
1332
+ retryable: true,
1333
+ },
1334
+ ],
1335
+ exitCode: 130,
1336
+ outcome: { class: "working", phase: "provisioning" },
1337
+ resume,
1338
+ presentation: {
1339
+ lines: [
1340
+ `VM remains ${machineState(machine)}. Local wait stopped; remote launch continues.`,
1341
+ ],
1342
+ next: "ornn connect",
1343
+ },
1344
+ });
1345
+ }
1346
+ return interaction.result({
1347
+ data,
1348
+ errors: [
1349
+ {
1350
+ code: "machine_failed",
1351
+ message: "The machine entered a terminal state before SSH was ready.",
1352
+ retryable: false,
1353
+ },
1354
+ ],
1355
+ exitCode: 1,
1356
+ outcome: { class: "failed", phase: machineState(machine) },
1357
+ resume,
1358
+ presentation: { lines: ["The VM failed before SSH was ready."] },
1359
+ });
1360
+ },
1361
+ };
1362
+
1363
+ function reservationAllocationContext(value) {
1364
+ const reservation = record(value);
1365
+ return {
1366
+ term: {
1367
+ start: text(reservation.start_at) ?? text(reservation.start),
1368
+ end: text(reservation.end_at) ?? text(reservation.end),
1369
+ },
1370
+ machine_count: Number.isSafeInteger(reservation.node_count)
1371
+ ? reservation.node_count
1372
+ : Number.isSafeInteger(reservation.machine_count)
1373
+ ? reservation.machine_count
1374
+ : null,
1375
+ gpu_count: Number.isSafeInteger(reservation.gpu_count) ? reservation.gpu_count : null,
1376
+ };
1377
+ }
1378
+
1379
+ function projectOrganizationNetwork(value) {
1380
+ const network = record(record(value).network);
1381
+ return {
1382
+ scope: "organization",
1383
+ available: Boolean(text(network.status)),
1384
+ private_network: {
1385
+ id: "shared",
1386
+ name: "Organization private network",
1387
+ status: text(network.status),
1388
+ },
1389
+ };
1390
+ }
1391
+
1392
+ function projectVolumeMount(value, reservationId) {
1393
+ const attachment = record(record(value).attachment);
1394
+ const id = resourceId(attachment);
1395
+ const drive = record(attachment.drive);
1396
+ return {
1397
+ reservation_id: text(attachment.reservation_id) ?? reservationId,
1398
+ attached: Boolean(id),
1399
+ volume_mount: id
1400
+ ? {
1401
+ id,
1402
+ volume_id: text(attachment.drive_id),
1403
+ volume_name: text(drive.name),
1404
+ mount_path: resolvedMountPath(attachment.mount_path, drive.active_mounts),
1405
+ mount_mode: text(attachment.mount_mode),
1406
+ state: text(attachment.state),
1407
+ }
1408
+ : null,
1409
+ };
1410
+ }
1411
+
1412
+ async function bareMetalRelationships(context, reservationId) {
1413
+ const encoded = encodeURIComponent(reservationId);
1414
+ const missingAttachment = (error) => {
1415
+ if (!isReferenceMiss(error)) throw error;
1416
+ return null;
1417
+ };
1418
+ const [network, storage] = await Promise.all([
1419
+ context.fetch(`/v1/workflows/compute/networks/shared?overlay=true`),
1420
+ context
1421
+ .fetch(`/v1/workflows/compute/nodes/reservations/${encoded}/storage-attachment`)
1422
+ .catch(missingAttachment),
1423
+ ]);
1424
+ return {
1425
+ network: projectOrganizationNetwork(network),
1426
+ storage: projectVolumeMount(storage, reservationId),
1427
+ };
1428
+ }
1429
+
1430
+ function bareMetalIntentSignature(reservation, relationships) {
1431
+ return JSON.stringify({
1432
+ allocation: reservationAllocationContext(reservation),
1433
+ network: relationships.network,
1434
+ storage: relationships.storage,
1435
+ });
1436
+ }
1437
+
1438
+ function bareMetalLaunchData(reservation, machine, key, relationships, waitPolicy, replayed) {
1439
+ const reservationId = resourceId(reservation);
1440
+ const invocation = isSshReady(machine, key) ? sshInvocation(machine) : null;
1441
+ return {
1442
+ mode: "bare-metal",
1443
+ capacity_reservation: {
1444
+ id: reservationId,
1445
+ status: ACTIVE_RESERVATION,
1446
+ allocation_context: reservationAllocationContext(reservation),
1447
+ },
1448
+ machine: projectMachine(machine),
1449
+ ssh_key: projectKey(key),
1450
+ organization_network: relationships.network,
1451
+ volume_mount: relationships.storage,
1452
+ wait: waitPolicy,
1453
+ ssh_command: invocation ? commandText(invocation) : null,
1454
+ replayed,
1455
+ created_resources: { private_network: false, volume: false },
1456
+ };
1457
+ }
1458
+
1459
+ function relationshipLabel(relationships, kind) {
1460
+ if (kind === "network") {
1461
+ const network = relationships.network.private_network;
1462
+ return network
1463
+ ? `${network.name ?? "Private network"} (${network.id}); available to the organization`
1464
+ : "Organization networking is not ready";
1465
+ }
1466
+ const mount = relationships.storage.volume_mount;
1467
+ return mount
1468
+ ? `${mount.volume_name ?? "Volume"} (${mount.volume_id}) at ${mount.mount_path ?? "owner-provided mount path"}; existing mount retained`
1469
+ : "No existing volume mount; none will be created";
1470
+ }
1471
+
1472
+ function workspaceIntentRequested(input) {
1473
+ return [input.network, input.volume, input.mountAccess, input.resume].some(
1474
+ (value) => value !== undefined
1475
+ );
1476
+ }
1477
+
1478
+ function workspaceId(input, interaction) {
1479
+ const idempotencyKey = text(input.idempotencyKey);
1480
+ const resume = text(input.resume);
1481
+ if (idempotencyKey && resume && idempotencyKey !== resume) {
1482
+ interaction.usage("--resume must match --idempotency-key when both are supplied.", {
1483
+ code: "workspace_identity_conflict",
1484
+ field: "resume",
1485
+ });
1486
+ }
1487
+ const value = resume ?? idempotencyKey;
1488
+ if (!value) {
1489
+ interaction.usage(
1490
+ "A caller-stable --idempotency-key is required for a durable Bare Metal workspace.",
1491
+ {
1492
+ code: "idempotency_key_required",
1493
+ field: "idempotencyKey",
1494
+ }
1495
+ );
1496
+ }
1497
+ if (value.length > 115 || /\p{Cc}/u.test(value)) {
1498
+ interaction.usage("The workspace idempotency key is invalid.", {
1499
+ code: "invalid_idempotency_key",
1500
+ field: resume ? "resume" : "idempotencyKey",
1501
+ });
1502
+ }
1503
+ return value;
1504
+ }
1505
+
1506
+ function workspaceMountAccess(value, interaction) {
1507
+ const access = text(value)?.toLowerCase();
1508
+ if (access !== "read-only" && access !== "read-write") {
1509
+ interaction.usage("--mount-access must be read-only or read-write.", {
1510
+ code: "invalid_access_mode",
1511
+ field: "mountAccess",
1512
+ });
1513
+ }
1514
+ return access;
1515
+ }
1516
+
1517
+ function durableWorkspaceAllocation(reservation, interaction) {
1518
+ const allocation = reservationAllocationContext(reservation);
1519
+ const start = allocation.term.start;
1520
+ const end = allocation.term.end;
1521
+ const startMilliseconds = Date.parse(start ?? "");
1522
+ const endMilliseconds = Date.parse(end ?? "");
1523
+ const duration = endMilliseconds - startMilliseconds;
1524
+ const machineCount = allocation.machine_count;
1525
+ const gpusPerMachine = Number.isSafeInteger(reservation.gpus_per_node)
1526
+ ? reservation.gpus_per_node
1527
+ : Number.isSafeInteger(reservation.gpus_per_machine)
1528
+ ? reservation.gpus_per_machine
1529
+ : null;
1530
+ const gpuCount = Number.isSafeInteger(allocation.gpu_count)
1531
+ ? allocation.gpu_count
1532
+ : Number.isSafeInteger(machineCount) && Number.isSafeInteger(gpusPerMachine)
1533
+ ? machineCount * gpusPerMachine
1534
+ : null;
1535
+ if (
1536
+ !start ||
1537
+ !end ||
1538
+ !Number.isFinite(startMilliseconds) ||
1539
+ !Number.isFinite(endMilliseconds) ||
1540
+ duration <= 0 ||
1541
+ !Number.isSafeInteger(machineCount) ||
1542
+ machineCount < 1 ||
1543
+ !Number.isSafeInteger(gpuCount) ||
1544
+ gpuCount < 1
1545
+ ) {
1546
+ interaction.error("The active capacity reservation does not contain valid allocation facts.", {
1547
+ code: "reservation_allocation_incomplete",
1548
+ });
1549
+ }
1550
+ const hours = duration / 3_600_000;
1551
+ return {
1552
+ term: { start, end, hours },
1553
+ quantity: {
1554
+ machine_count: machineCount,
1555
+ gpu_count: gpuCount,
1556
+ gpus_per_machine: gpusPerMachine,
1557
+ },
1558
+ };
1559
+ }
1560
+
1561
+ function workspaceStepContext(context, interaction, signal) {
1562
+ const stepInteraction = Object.create(interaction);
1563
+ stepInteraction.review = async () => true;
1564
+ stepInteraction.options = { ...interaction.options, yes: true };
1565
+ return {
1566
+ ...context,
1567
+ fetch: (path, init = {}) =>
1568
+ context.fetch(path, init.signal === undefined && signal ? { ...init, signal } : init),
1569
+ cli: {
1570
+ ...context.cli,
1571
+ interaction: stepInteraction,
1572
+ },
1573
+ };
1574
+ }
1575
+
1576
+ async function executeWorkspaceCapability(
1577
+ context,
1578
+ interaction,
1579
+ capabilityId,
1580
+ input,
1581
+ { signal } = {}
1582
+ ) {
1583
+ const capability = context.cli?.catalog?.find((candidate) => candidate.id === capabilityId);
1584
+ if (!capability) {
1585
+ interaction.error("A required durable workspace capability is unavailable.", {
1586
+ code: "workspace_capability_unavailable",
1587
+ });
1588
+ }
1589
+ const execution = await capability.execute(
1590
+ workspaceStepContext(context, interaction, signal),
1591
+ input
1592
+ );
1593
+ if (execution?.exitCode && ![0, 3, 4].includes(execution.exitCode)) {
1594
+ interaction.error("A durable workspace step did not complete.", {
1595
+ code: "workspace_step_failed",
1596
+ retryable: execution.errors?.some((error) => error?.retryable === true) === true,
1597
+ });
1598
+ }
1599
+ return execution;
1600
+ }
1601
+
1602
+ async function workspaceResourceFacts(context, interaction, input, reservationId) {
1603
+ const [network, volume, volumeMount] = await Promise.all([
1604
+ context.fetch("/v1/workflows/compute/networks/shared?overlay=true"),
1605
+ executeWorkspaceCapability(context, interaction, "storage.volumes.show", {
1606
+ $positionals: [input.volume],
1607
+ }),
1608
+ executeWorkspaceCapability(context, interaction, "storage.volume-mounts.show", {
1609
+ $positionals: [reservationId],
1610
+ }),
1611
+ ]);
1612
+ return {
1613
+ network: {
1614
+ ...projectOrganizationNetwork(network).private_network,
1615
+ lifecycle: {
1616
+ class: ["ready", "active"].includes(record(record(network).network).status)
1617
+ ? "ready"
1618
+ : "working",
1619
+ phase: text(record(record(network).network).status) ?? "unknown",
1620
+ },
1621
+ },
1622
+ volume: volume.data.volume,
1623
+ volumeMount: volumeMount.data.volume_mount,
1624
+ };
1625
+ }
1626
+
1627
+ function workspaceRelationshipConflict(facts, goal, interaction) {
1628
+ if (
1629
+ facts.volumeMount &&
1630
+ (facts.volumeMount.volume?.id !== facts.volume.id ||
1631
+ facts.volumeMount.access !== goal.mountAccess)
1632
+ ) {
1633
+ interaction.error("The capacity reservation has a different managed-volume mount intent.", {
1634
+ action: {
1635
+ command: `ornn volume-mounts detach ${goal.reservationId}`,
1636
+ description: "Review and detach the existing volume relationship before resuming.",
1637
+ type: "command",
1638
+ },
1639
+ code: "workspace_volume_mount_conflict",
1640
+ });
1641
+ }
1642
+ }
1643
+
1644
+ function durableWorkspaceSignature(reservation, key, facts, goal, interaction) {
1645
+ return JSON.stringify({
1646
+ capacity_reservation: {
1647
+ id: resourceId(reservation),
1648
+ status: text(reservation.status),
1649
+ allocation: durableWorkspaceAllocation(reservation, interaction),
1650
+ },
1651
+ ssh_key: {
1652
+ id: resourceId(key),
1653
+ fingerprint: text(key.fingerprint),
1654
+ },
1655
+ ssh_user: goal.username,
1656
+ private_network: facts.network,
1657
+ managed_volume: facts.volume,
1658
+ mount: {
1659
+ access: goal.mountAccess,
1660
+ },
1661
+ });
1662
+ }
1663
+
1664
+ function workspaceResumeCommand(input, goal, facts, id, interaction, localResume = false) {
1665
+ if (localResume) return "ornn launch --mode bare-metal --resume";
1666
+ const parts = [
1667
+ "ornn",
1668
+ "launch",
1669
+ goal.reservationId,
1670
+ "--mode",
1671
+ "bare-metal",
1672
+ "--organization",
1673
+ goal.organizationId,
1674
+ "--key",
1675
+ goal.keyId,
1676
+ "--network",
1677
+ facts.network.id,
1678
+ "--volume",
1679
+ facts.volume.id,
1680
+ "--mount-access",
1681
+ goal.mountAccess,
1682
+ "--resume",
1683
+ id,
1684
+ ];
1685
+ if (goal.username) parts.push("--username", goal.username);
1686
+ if (input.noWait) parts.push("--no-wait");
1687
+ if (input.wait) parts.push("--wait", "--timeout", text(input.timeout));
1688
+ if (interaction.machine) parts.push("--yes", "--format", interaction.format);
1689
+ return parts.map(shellQuote).join(" ");
1690
+ }
1691
+
1692
+ async function clearWorkspaceResume(interaction, id) {
1693
+ try {
1694
+ await interaction.workspaceResume?.clear(id);
1695
+ } catch {
1696
+ // The remote workspace is ready even if local recovery cleanup fails.
1697
+ }
1698
+ }
1699
+
1700
+ function workspaceSteps(overrides = {}) {
1701
+ return [
1702
+ { name: "ssh_key_assignment", state: "pending", ref: null },
1703
+ { name: "organization_network", state: "pending", ref: null },
1704
+ { name: "managed_volume_mount", state: "pending", ref: null },
1705
+ { name: "bare_metal_machine", state: "pending", ref: null },
1706
+ ].map((step) => ({ ...step, ...(overrides[step.name] ?? {}) }));
1707
+ }
1708
+
1709
+ function workspaceData({ allocation, facts, goal, id, machine, steps }) {
1710
+ const mount = facts.volumeMount;
1711
+ return {
1712
+ mode: "bare-metal",
1713
+ workflow: { id, durable: true },
1714
+ capacity_reservation: {
1715
+ id: goal.reservationId,
1716
+ status: ACTIVE_RESERVATION,
1717
+ allocation_context: allocation,
1718
+ },
1719
+ ssh_key: projectKey(goal.key),
1720
+ private_network: facts.network,
1721
+ managed_volume: facts.volume,
1722
+ volume_mount: mount,
1723
+ machine: machine ? projectMachine(machine) : null,
1724
+ steps,
1725
+ source_writeback: false,
1726
+ created_resources: { private_network: false, volume: false },
1727
+ };
1728
+ }
1729
+
1730
+ function safeWorkspaceError(error, step) {
1731
+ const supplied = text(error?.code);
1732
+ const code = supplied && /^[a-z][a-z0-9_]*$/.test(supplied) ? supplied : `${step}_failed`;
1733
+ return {
1734
+ code,
1735
+ message: `The ${step.replaceAll("_", " ")} step did not reach a confirmed durable result.`,
1736
+ retryable: error?.retryable === true || error?.status === undefined || error?.status >= 500,
1737
+ };
1738
+ }
1739
+
1740
+ function partialWorkspaceResult({
1741
+ allocation,
1742
+ error,
1743
+ facts,
1744
+ goal,
1745
+ id,
1746
+ interaction,
1747
+ machine,
1748
+ resumeCommand,
1749
+ step,
1750
+ steps,
1751
+ }) {
1752
+ return interaction.result({
1753
+ action: {
1754
+ command: resumeCommand,
1755
+ description: "Re-read every owner fact and resume only unsatisfied workspace steps.",
1756
+ type: "command",
1757
+ },
1758
+ data: workspaceData({ allocation, facts, goal, id, machine, steps }),
1759
+ errors: [safeWorkspaceError(error, step)],
1760
+ exitCode: 4,
1761
+ outcome: { class: "partial", phase: step },
1762
+ resume: { kind: "bare_metal_workspace", ref: id },
1763
+ presentation: {
1764
+ lines: [
1765
+ `Bare Metal workspace ${id} is durable but incomplete.`,
1766
+ `Completed resources remain attached to capacity reservation ${goal.reservationId}.`,
1767
+ ],
1768
+ next: resumeCommand,
1769
+ },
1770
+ });
1771
+ }
1772
+
1773
+ function workspaceMountReady(mount) {
1774
+ return Boolean(
1775
+ mount && mount.readiness?.expected > 0 && mount.readiness.ready === mount.readiness.expected
1776
+ );
1777
+ }
1778
+
1779
+ function durableWorkspaceInputPreflight(input, interaction) {
1780
+ if (!text(input.network) || !text(input.volume) || !text(input.mountAccess)) {
1781
+ interaction.usage(
1782
+ "A durable Bare Metal workspace requires --network, --volume, and --mount-access.",
1783
+ { code: "workspace_input_incomplete" }
1784
+ );
1785
+ }
1786
+ if (input.network !== "shared")
1787
+ interaction.usage("Durable Bare Metal uses organization networking; pass --network shared.", {
1788
+ code: "invalid_network",
1789
+ field: "network",
1790
+ });
1791
+ if (interaction.machine) {
1792
+ requireCanonical(interaction, input.volume, "volume", "Managed volume");
1793
+ }
1794
+ return { mountAccess: workspaceMountAccess(input.mountAccess, interaction) };
1795
+ }
1796
+
1797
+ async function launchDurableBareMetalWorkspace({
1798
+ context,
1799
+ input,
1800
+ interaction,
1801
+ organization,
1802
+ reservation,
1803
+ keyPlan,
1804
+ }) {
1805
+ const id = workspaceId(input, interaction);
1806
+ const reservationId = resourceId(reservation);
1807
+ if (keyPlan.kind !== "existing") {
1808
+ interaction.usage("A durable Bare Metal workspace requires a declared existing SSH key.", {
1809
+ code: "workspace_ssh_key_required",
1810
+ field: "key",
1811
+ });
1812
+ }
1813
+ const mountAccess = input.mountAccess;
1814
+ const key = keyPlan.key;
1815
+ const goal = {
1816
+ key,
1817
+ keyId: resourceId(key),
1818
+ mountAccess,
1819
+ organizationId: organization.id,
1820
+ reservationId,
1821
+ username: text(input.username),
1822
+ };
1823
+ let facts = await workspaceResourceFacts(context, interaction, input, reservationId);
1824
+ workspaceRelationshipConflict(facts, goal, interaction);
1825
+ const allocation = durableWorkspaceAllocation(reservation, interaction);
1826
+ const shouldWait = input.noWait
1827
+ ? false
1828
+ : input.wait
1829
+ ? true
1830
+ : interaction.eligibleHuman && !interaction.machine;
1831
+ const accepted = await interaction.review({
1832
+ title: "Durable Bare Metal workspace review",
1833
+ terms: [
1834
+ { label: "Organization", value: organization.name },
1835
+ { label: "Active capacity", value: `${reservationLabel(reservation)} (${reservationId})` },
1836
+ { label: "SSH key", value: `${text(key.label) ?? "SSH key"} (${resourceId(key)})` },
1837
+ { label: "SSH user", value: goal.username ?? "Owner default" },
1838
+ { label: "Private network", value: `${facts.network.name} (${facts.network.id})` },
1839
+ { label: "Managed volume", value: `${facts.volume.name} (${facts.volume.id})` },
1840
+ {
1841
+ label: "Mount",
1842
+ value: `${mountAccess}; every machine in the reservation`,
1843
+ },
1844
+ {
1845
+ label: "Term",
1846
+ value: `${allocation.term.start} to ${allocation.term.end} (${allocation.term.hours} hours)`,
1847
+ },
1848
+ {
1849
+ label: "GPU quantity",
1850
+ value: `${allocation.quantity.gpu_count} GPUs across ${allocation.quantity.machine_count} machine${allocation.quantity.machine_count === 1 ? "" : "s"}`,
1851
+ },
1852
+ {
1853
+ label: "Creation",
1854
+ value: "Use the declared resources only; create no network, volume, or source writeback",
1855
+ },
1856
+ {
1857
+ label: "Wait",
1858
+ value: shouldWait
1859
+ ? "Until SSH and every requested mount are ready"
1860
+ : "Return after durable acceptance",
1861
+ },
1862
+ ],
1863
+ prompt: "Build this durable Bare Metal workspace?",
1864
+ });
1865
+ if (!accepted) {
1866
+ return interaction.result({
1867
+ errors: [
1868
+ { code: "declined", message: "Durable Bare Metal workspace declined.", retryable: false },
1869
+ ],
1870
+ exitCode: 1,
1871
+ outcome: { class: "ended", phase: "declined" },
1872
+ presentation: {
1873
+ lines: ["Durable Bare Metal workspace declined. No resource or relationship was created."],
1874
+ next: "ornn launch --mode bare-metal",
1875
+ },
1876
+ });
1877
+ }
1878
+
1879
+ const [currentReservationValue, currentKeyPlan, currentFacts] = await Promise.all([
1880
+ context.fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`),
1881
+ keyPlanFromInput(context, input, interaction),
1882
+ workspaceResourceFacts(context, interaction, input, reservationId),
1883
+ ]);
1884
+ const currentReservation = assertActiveReservation(
1885
+ currentReservationValue,
1886
+ reservationId,
1887
+ interaction,
1888
+ "bare-metal"
1889
+ );
1890
+ workspaceRelationshipConflict(currentFacts, goal, interaction);
1891
+ if (
1892
+ currentKeyPlan.kind !== "existing" ||
1893
+ durableWorkspaceSignature(
1894
+ currentReservation,
1895
+ currentKeyPlan.key,
1896
+ currentFacts,
1897
+ goal,
1898
+ interaction
1899
+ ) !== durableWorkspaceSignature(reservation, key, facts, goal, interaction)
1900
+ ) {
1901
+ interaction.error("The durable workspace intent changed after review.", {
1902
+ code: "workspace_intent_changed",
1903
+ });
1904
+ }
1905
+ facts = currentFacts;
1906
+ let localResume = false;
1907
+ if (interaction.eligibleHuman && !interaction.machine) {
1908
+ if (!interaction.workspaceResume) {
1909
+ interaction.error(
1910
+ "Local Bare Metal recovery could not be recorded; no owner mutation was sent.",
1911
+ {
1912
+ code: "local_resume_write_failed",
1913
+ exitCode: 6,
1914
+ retryable: true,
1915
+ }
1916
+ );
1917
+ }
1918
+ try {
1919
+ localResume = await interaction.workspaceResume.save({
1920
+ id,
1921
+ organizationId: goal.organizationId,
1922
+ reservationId: goal.reservationId,
1923
+ keyId: goal.keyId,
1924
+ networkId: facts.network.id,
1925
+ volumeId: facts.volume.id,
1926
+ mountAccess: goal.mountAccess,
1927
+ username: goal.username,
1928
+ });
1929
+ } catch {
1930
+ localResume = false;
1931
+ }
1932
+ if (!localResume) {
1933
+ interaction.error(
1934
+ "Local Bare Metal recovery could not be recorded; no owner mutation was sent.",
1935
+ {
1936
+ code: "local_resume_write_failed",
1937
+ exitCode: 6,
1938
+ retryable: true,
1939
+ }
1940
+ );
1941
+ }
1942
+ }
1943
+ const resumeCommand = workspaceResumeCommand(input, goal, facts, id, interaction, localResume);
1944
+ const states = {};
1945
+ let machine = null;
1946
+ let step = "ssh_key_assignment";
1947
+
1948
+ try {
1949
+ const assignment = await executeWorkspaceCapability(
1950
+ context,
1951
+ interaction,
1952
+ "access.ssh_key_assignments.assign",
1953
+ {
1954
+ $positionals: [reservationId],
1955
+ key: resourceId(key),
1956
+ idempotencyKey: `${id}:ssh-key`,
1957
+ yes: true,
1958
+ }
1959
+ );
1960
+ states.ssh_key_assignment = {
1961
+ state: assignment.data.replayed ? "replayed" : "satisfied",
1962
+ ref: resourceId(key),
1963
+ };
1964
+ } catch (error) {
1965
+ return partialWorkspaceResult({
1966
+ allocation,
1967
+ error,
1968
+ facts,
1969
+ goal,
1970
+ id,
1971
+ interaction,
1972
+ machine,
1973
+ resumeCommand,
1974
+ step,
1975
+ steps: workspaceSteps(states),
1976
+ });
1977
+ }
1978
+
1979
+ states.organization_network = { state: "satisfied", ref: "shared" };
1980
+
1981
+ step = "managed_volume_mount";
1982
+ try {
1983
+ const mount = await executeWorkspaceCapability(
1984
+ context,
1985
+ interaction,
1986
+ "storage.volume-mounts.attach",
1987
+ {
1988
+ $positionals: [reservationId],
1989
+ access: mountAccess,
1990
+ idempotencyKey: `${id}:volume-mount`,
1991
+ noWait: true,
1992
+ volume: facts.volume.id,
1993
+ yes: true,
1994
+ }
1995
+ );
1996
+ facts.volumeMount = mount.data.volume_mount;
1997
+ states.managed_volume_mount = {
1998
+ state: mount.data.replayed ? "replayed" : "satisfied",
1999
+ ref: facts.volumeMount.id,
2000
+ };
2001
+ if (mount.exitCode === 4) {
2002
+ return partialWorkspaceResult({
2003
+ allocation,
2004
+ error: { code: "volume_mount_partial", retryable: true },
2005
+ facts,
2006
+ goal,
2007
+ id,
2008
+ interaction,
2009
+ machine,
2010
+ resumeCommand,
2011
+ step,
2012
+ steps: workspaceSteps(states),
2013
+ });
2014
+ }
2015
+ } catch (error) {
2016
+ return partialWorkspaceResult({
2017
+ allocation,
2018
+ error,
2019
+ facts,
2020
+ goal,
2021
+ id,
2022
+ interaction,
2023
+ machine,
2024
+ resumeCommand,
2025
+ step,
2026
+ steps: workspaceSteps(states),
2027
+ });
2028
+ }
2029
+
2030
+ step = "bare_metal_machine";
2031
+ try {
2032
+ const existingMachines = await fetchMachines(context, reservationId);
2033
+ const replay = machineReplayState(existingMachines, key, input, interaction, "bare-metal");
2034
+ machine = replay.machine;
2035
+ const replayed = replay.replayed;
2036
+ if (!machine) {
2037
+ const launch = await context.fetch(
2038
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/launch`,
2039
+ {
2040
+ method: "POST",
2041
+ body: {
2042
+ access_mode: "bare-metal",
2043
+ image_id: null,
2044
+ machine_count: 1,
2045
+ network_mode: "private",
2046
+ request_id: `${id}:machine`,
2047
+ ssh_key_ids: [resourceId(key)],
2048
+ zone: null,
2049
+ },
2050
+ }
2051
+ );
2052
+ const launched = activeMachines(machinesFromPayload(launch));
2053
+ if (launched.length !== 1) {
2054
+ interaction.error("Launch did not return one durable Bare Metal machine reference.", {
2055
+ code: "machine_correlation_failed",
2056
+ retryable: true,
2057
+ });
2058
+ }
2059
+ machine = validatedMachine(launched, key, input, interaction, "bare-metal");
2060
+ }
2061
+ states.bare_metal_machine = {
2062
+ state: replayed ? "replayed" : "satisfied",
2063
+ ref: machineId(machine),
2064
+ };
2065
+ } catch (error) {
2066
+ return partialWorkspaceResult({
2067
+ allocation,
2068
+ error,
2069
+ facts,
2070
+ goal,
2071
+ id,
2072
+ interaction,
2073
+ machine,
2074
+ resumeCommand,
2075
+ step,
2076
+ steps: workspaceSteps(states),
2077
+ });
2078
+ }
2079
+
2080
+ const resume = { kind: "bare_metal_workspace", ref: id };
2081
+ if (!shouldWait) {
2082
+ const ready = isSshReady(machine, key) && workspaceMountReady(facts.volumeMount);
2083
+ if (ready) await clearWorkspaceResume(interaction, id);
2084
+ return interaction.result({
2085
+ data: workspaceData({
2086
+ allocation,
2087
+ facts,
2088
+ goal,
2089
+ id,
2090
+ machine,
2091
+ steps: workspaceSteps(states),
2092
+ }),
2093
+ outcome: ready
2094
+ ? { class: "ready", phase: "workspace_ready" }
2095
+ : { class: "working", phase: "workspace_accepted" },
2096
+ resume,
2097
+ presentation: {
2098
+ lines: [
2099
+ ready
2100
+ ? `Durable Bare Metal workspace is ready: ${machineHostname(machine) || machineId(machine)}`
2101
+ : `Durable Bare Metal workspace accepted: ${id}`,
2102
+ ],
2103
+ next: ready ? "ornn connect" : resumeCommand,
2104
+ },
2105
+ });
2106
+ }
2107
+
2108
+ let waitOutcome;
2109
+ try {
2110
+ waitOutcome = await interaction.waitForGoal({
2111
+ command: "launch",
2112
+ initial: { machine, mount: facts.volumeMount },
2113
+ timeoutMs: timeoutMilliseconds(input.timeout, interaction),
2114
+ isReady: (current) => isSshReady(current.machine, key) && workspaceMountReady(current.mount),
2115
+ isTerminal: (current) =>
2116
+ TERMINAL_MACHINE_STATES.has(machineState(current.machine)) ||
2117
+ current.mount?.readiness?.failed > 0 ||
2118
+ current.mount?.lifecycle?.class === "ended",
2119
+ message: (current, elapsedMs) =>
2120
+ `Bare Metal is ${machineState(current.machine)}; ${current.mount?.readiness?.ready ?? 0} of ${current.mount?.readiness?.expected ?? 0} mounts ready (${Math.floor(elapsedMs / 1000)}s).`,
2121
+ projectProgress: (current) => ({
2122
+ machine: projectMachine(current.machine),
2123
+ volume_mount: current.mount,
2124
+ workflow_id: id,
2125
+ }),
2126
+ poll: async ({ signal }) => {
2127
+ const [currentMachine, currentMount] = await Promise.all([
2128
+ context.fetch(`/v1/workflows/compute/nodes/${encodeURIComponent(machineId(machine))}`, {
2129
+ signal,
2130
+ }),
2131
+ executeWorkspaceCapability(
2132
+ context,
2133
+ interaction,
2134
+ "storage.volume-mounts.show",
2135
+ {
2136
+ $positionals: [reservationId],
2137
+ },
2138
+ { signal }
2139
+ ),
2140
+ ]);
2141
+ return { machine: currentMachine, mount: currentMount.data.volume_mount };
2142
+ },
2143
+ });
2144
+ } catch (error) {
2145
+ return acceptedWaitFailure(interaction, error, {
2146
+ data: workspaceData({
2147
+ allocation,
2148
+ facts,
2149
+ goal,
2150
+ id,
2151
+ machine,
2152
+ steps: workspaceSteps(states),
2153
+ }),
2154
+ label: "durable Bare Metal workspace",
2155
+ phase: "workspace_accepted",
2156
+ resume,
2157
+ retryCommand: resumeCommand,
2158
+ });
2159
+ }
2160
+ machine = waitOutcome.value.machine;
2161
+ facts.volumeMount = waitOutcome.value.mount;
2162
+ const data = workspaceData({
2163
+ allocation,
2164
+ facts,
2165
+ goal,
2166
+ id,
2167
+ machine,
2168
+ steps: workspaceSteps(states),
2169
+ });
2170
+ if (waitOutcome.kind === "ready") {
2171
+ await clearWorkspaceResume(interaction, id);
2172
+ return interaction.result({
2173
+ data,
2174
+ outcome: { class: "ready", phase: "workspace_ready" },
2175
+ resume,
2176
+ presentation: {
2177
+ lines: [
2178
+ `Durable Bare Metal workspace is ready: ${machineHostname(machine) || machineId(machine)}`,
2179
+ ],
2180
+ next: "ornn connect",
2181
+ },
2182
+ });
2183
+ }
2184
+ if (waitOutcome.kind === "timeout" || waitOutcome.kind === "interrupted") {
2185
+ const interrupted = waitOutcome.kind === "interrupted";
2186
+ return interaction.result({
2187
+ action: {
2188
+ command: resumeCommand,
2189
+ description: "Resume the exact durable workspace from current owner facts.",
2190
+ type: "command",
2191
+ },
2192
+ data,
2193
+ errors: [
2194
+ {
2195
+ code: interrupted ? "interrupted" : "wait_timeout",
2196
+ message: interrupted
2197
+ ? "Local waiting stopped; the remote workspace was not changed."
2198
+ : "Local waiting timed out; the remote workspace continues.",
2199
+ retryable: true,
2200
+ },
2201
+ ],
2202
+ exitCode: interrupted ? 130 : 124,
2203
+ outcome: { class: "working", phase: "workspace_accepted" },
2204
+ resume,
2205
+ presentation: {
2206
+ lines: [
2207
+ interrupted
2208
+ ? "Local waiting stopped; no remote cancellation was sent."
2209
+ : "Local waiting timed out; no remote cancellation was sent.",
2210
+ ],
2211
+ next: resumeCommand,
2212
+ },
2213
+ });
2214
+ }
2215
+ return partialWorkspaceResult({
2216
+ allocation,
2217
+ error: { code: "workspace_readiness_partial", retryable: true },
2218
+ facts,
2219
+ goal,
2220
+ id,
2221
+ interaction,
2222
+ machine,
2223
+ resumeCommand,
2224
+ step: "workspace_readiness",
2225
+ steps: workspaceSteps(states),
2226
+ });
2227
+ }
2228
+
2229
+ export const bareMetalLaunchModeHandler = {
2230
+ mode: "bare-metal",
2231
+ async launch({ context, input, interaction }) {
2232
+ const durableWorkspace = workspaceIntentRequested(input);
2233
+ if (durableWorkspace && input.resume === true) {
2234
+ if (!interaction.eligibleHuman || interaction.machine) {
2235
+ interaction.usage("--resume without a value requires an eligible interactive terminal.", {
2236
+ code: "workspace_resume_reference_required",
2237
+ field: "resume",
2238
+ });
2239
+ }
2240
+ const hasConflictingIntent =
2241
+ (input.$positionals?.length ?? 0) > 0 ||
2242
+ [
2243
+ input.organization,
2244
+ input.reservation,
2245
+ input.reservationId,
2246
+ input.key,
2247
+ input.keyId,
2248
+ input.label,
2249
+ input.publicKey,
2250
+ input.publicKeyFile,
2251
+ input.network,
2252
+ input.volume,
2253
+ input.mountAccess,
2254
+ input.username,
2255
+ input.machine,
2256
+ input.machines,
2257
+ input.nodes,
2258
+ input.idempotencyKey,
2259
+ ].some((value) => value !== undefined);
2260
+ if (hasConflictingIntent) {
2261
+ interaction.usage("--resume without a value cannot be combined with workspace intent.", {
2262
+ code: "workspace_resume_conflict",
2263
+ field: "resume",
2264
+ });
2265
+ }
2266
+ let remembered;
2267
+ try {
2268
+ remembered = await interaction.workspaceResume?.load();
2269
+ } catch {
2270
+ interaction.error("The locally remembered Bare Metal workspace could not be read.", {
2271
+ code: "workspace_resume_unavailable",
2272
+ exitCode: 6,
2273
+ retryable: true,
2274
+ });
2275
+ }
2276
+ if (!remembered) {
2277
+ interaction.usage("No locally remembered Bare Metal workspace is available to resume.", {
2278
+ code: "workspace_resume_not_found",
2279
+ field: "resume",
2280
+ });
2281
+ }
2282
+ input = {
2283
+ ...input,
2284
+ $positionals: [remembered.reservationId],
2285
+ idempotencyKey: remembered.id,
2286
+ key: remembered.keyId,
2287
+ mountAccess: remembered.mountAccess,
2288
+ network: remembered.networkId,
2289
+ organization: remembered.organizationId,
2290
+ resume: remembered.id,
2291
+ username: remembered.username ?? undefined,
2292
+ volume: remembered.volumeId,
2293
+ };
2294
+ } else if (
2295
+ durableWorkspace &&
2296
+ interaction.eligibleHuman &&
2297
+ !interaction.machine &&
2298
+ !text(input.idempotencyKey) &&
2299
+ !text(input.resume)
2300
+ ) {
2301
+ input = { ...input, idempotencyKey: interaction.idempotencyKey() };
2302
+ } else if (durableWorkspace && !text(input.idempotencyKey) && text(input.resume)) {
2303
+ input = { ...input, idempotencyKey: text(input.resume) };
2304
+ }
2305
+ if (durableWorkspace) {
2306
+ input = { ...input, ...durableWorkspaceInputPreflight(input, interaction) };
2307
+ }
2308
+ launchInputPreflight(input, interaction);
2309
+ input = await normalizePublicKeyInput(input);
2310
+ const organization = await selectedOrganization(context, input, interaction);
2311
+ const reservation = await resolveReservation(context, input, interaction, "bare-metal");
2312
+ const reservationId = resourceId(reservation);
2313
+ const keyPlan = await keyPlanFromInput(context, input, interaction);
2314
+ if (durableWorkspace) {
2315
+ return launchDurableBareMetalWorkspace({
2316
+ context,
2317
+ input,
2318
+ interaction,
2319
+ keyPlan,
2320
+ organization,
2321
+ reservation,
2322
+ });
2323
+ }
2324
+ const [relationships, reviewedMachines] = await Promise.all([
2325
+ bareMetalRelationships(context, reservationId),
2326
+ fetchMachines(context, reservationId),
2327
+ ]);
2328
+ input = withAuthoritativeUsername(input, reviewedMachines, interaction);
2329
+ const reviewedOwner = launchOwnerSnapshot(reviewedMachines);
2330
+ const shouldWait = input.noWait
2331
+ ? false
2332
+ : input.wait
2333
+ ? true
2334
+ : interaction.eligibleHuman && !interaction.machine;
2335
+ const allocation = reservationAllocationContext(reservation);
2336
+ const reviewAccepted = await interaction.review({
2337
+ title: "Bare Metal launch review",
2338
+ terms: [
2339
+ { label: "Organization", value: organization.name },
2340
+ {
2341
+ label: "Capacity reservation",
2342
+ value: `${reservationLabel(reservation)} (${reservationId})`,
2343
+ },
2344
+ { label: "Access mode", value: "Bare Metal" },
2345
+ {
2346
+ label: "SSH key",
2347
+ value: `${text(keyPlan.key.label) ?? "SSH key"} (${text(keyPlan.key.fingerprint) ?? resourceId(keyPlan.key)})`,
2348
+ },
2349
+ { label: "SSH user", value: text(input.username) ?? "Owner default" },
2350
+ { label: "Private network", value: relationshipLabel(relationships, "network") },
2351
+ { label: "Volume mount", value: relationshipLabel(relationships, "storage") },
2352
+ {
2353
+ label: "Term",
2354
+ value: `${allocation.term.start ?? "unreported"} to ${allocation.term.end ?? "unreported"}`,
2355
+ },
2356
+ {
2357
+ label: "Wait",
2358
+ value: shouldWait
2359
+ ? "Until SSH and the selected key are usable"
2360
+ : "Return after durable acceptance",
2361
+ },
2362
+ ],
2363
+ prompt: "Launch this Bare Metal machine?",
2364
+ });
2365
+ if (!reviewAccepted) {
2366
+ return interaction.result({
2367
+ errors: [{ code: "declined", message: "Bare Metal launch declined.", retryable: false }],
2368
+ exitCode: 1,
2369
+ outcome: { class: "ended", phase: "declined" },
2370
+ presentation: {
2371
+ lines: ["Bare Metal launch declined. No machine or relationship was created."],
2372
+ next: "ornn launch --mode bare-metal",
2373
+ },
2374
+ });
2375
+ }
2376
+
2377
+ const [currentReservationValue, currentRelationships, existingAssigned, projectedMachines] =
2378
+ await Promise.all([
2379
+ context.fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`),
2380
+ bareMetalRelationships(context, reservationId),
2381
+ fetchAssignedKeys(context, reservationId),
2382
+ fetchMachines(context, reservationId),
2383
+ ]);
2384
+ const currentReservation = assertActiveReservation(
2385
+ currentReservationValue,
2386
+ reservationId,
2387
+ interaction,
2388
+ "bare-metal"
2389
+ );
2390
+ if (
2391
+ reservationLabel(currentReservation) !== reservationLabel(reservation) ||
2392
+ bareMetalIntentSignature(currentReservation, currentRelationships) !==
2393
+ bareMetalIntentSignature(reservation, relationships)
2394
+ ) {
2395
+ interaction.error("Bare Metal eligibility or a declared relationship changed after review.", {
2396
+ code: "reservation_eligibility_changed",
2397
+ });
2398
+ }
2399
+ const currentOwner = launchOwnerSnapshot(projectedMachines);
2400
+ if (JSON.stringify(currentOwner) !== JSON.stringify(reviewedOwner)) {
2401
+ interaction.error(
2402
+ "The reservation owner assignment changed after review; launch stopped before mutation.",
2403
+ { code: "reservation_eligibility_changed" }
2404
+ );
2405
+ }
2406
+ const existingMachines = isPreassignedUnlaunchedCapacity(
2407
+ projectedMachines,
2408
+ existingAssigned,
2409
+ keyPlan.key
2410
+ )
2411
+ ? []
2412
+ : projectedMachines;
2413
+ const idempotencyKey = text(input.idempotencyKey) ?? interaction.idempotencyKey();
2414
+ if (existingMachines.length && keyPlan.kind === "create") {
2415
+ interaction.error(
2416
+ "The existing machine has a different SSH-key intent than the reviewed launch.",
2417
+ {
2418
+ code: "launch_input_changed",
2419
+ }
2420
+ );
2421
+ }
2422
+ const key = await materializeKey(context, keyPlan, interaction, idempotencyKey);
2423
+ const replay = machineReplayState(existingMachines, key, input, interaction, "bare-metal");
2424
+ let machine = replay.machine;
2425
+ if (
2426
+ replay.existing &&
2427
+ !existingAssigned.some((candidate) => resourceId(candidate) === resourceId(key))
2428
+ ) {
2429
+ interaction.error("The existing machine does not use the reviewed SSH-key assignment.", {
2430
+ code: "launch_input_changed",
2431
+ });
2432
+ }
2433
+ const replayed = replay.replayed;
2434
+
2435
+ if (!machine) {
2436
+ let launch;
2437
+ try {
2438
+ await ensureAssignment(context, reservationId, key, idempotencyKey, interaction);
2439
+ const launchBody = {
2440
+ access_mode: "bare-metal",
2441
+ network_mode: "public",
2442
+ image_id: null,
2443
+ machine_count: 1,
2444
+ request_id: idempotencyKey,
2445
+ ssh_key_ids: [resourceId(key)],
2446
+ zone: null,
2447
+ };
2448
+ launch = await context.fetch(
2449
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/launch`,
2450
+ { method: "POST", body: launchBody }
2451
+ );
2452
+ } catch (error) {
2453
+ if (!isRecoverableWaitFailure(error)) throw error;
2454
+ const discovered = await fetchMachines(context, reservationId).catch(() => []);
2455
+ const candidate =
2456
+ activeMachines(discovered).find((row) => machineAccessMode(row) === "bare-metal") ?? null;
2457
+ const retryCommand = [
2458
+ "ornn",
2459
+ "launch",
2460
+ reservationId,
2461
+ "--mode",
2462
+ "bare-metal",
2463
+ "--key",
2464
+ resourceId(key),
2465
+ "--idempotency-key",
2466
+ idempotencyKey,
2467
+ ...(interaction.machine
2468
+ ? ["--organization", resourceId(organization), "--yes", "--no-wait", "--format", "json"]
2469
+ : []),
2470
+ ].join(" ");
2471
+ return uncertainMutationFailure(interaction, error, {
2472
+ data: {
2473
+ capacity_reservation: {
2474
+ id: reservationId,
2475
+ label: reservationLabel(currentReservation),
2476
+ },
2477
+ machine: candidate ? projectMachine(candidate) : null,
2478
+ relationships: currentRelationships,
2479
+ ssh_key: projectKey(key),
2480
+ wait: { enabled: shouldWait, timeout: text(input.timeout) ?? "10m" },
2481
+ },
2482
+ label: "Bare Metal launch",
2483
+ phase: "launch_reconciliation",
2484
+ resume: candidate
2485
+ ? { kind: "machine", ref: machineId(candidate) }
2486
+ : { kind: "reservation", ref: reservationId },
2487
+ retryCommand,
2488
+ });
2489
+ }
2490
+ const launched = activeMachines(machinesFromPayload(launch));
2491
+ if (launched.length !== 1) {
2492
+ interaction.error("Launch did not return one durable Bare Metal machine reference.", {
2493
+ code: "machine_correlation_failed",
2494
+ retryable: true,
2495
+ });
2496
+ }
2497
+ machine = validatedMachine(launched, key, input, interaction, "bare-metal");
2498
+ }
2499
+
2500
+ const waitPolicy = shouldWait
2501
+ ? { enabled: true, timeout: text(input.timeout) ?? "10m" }
2502
+ : { enabled: false, timeout: null };
2503
+ const resume = { kind: "machine", ref: machineId(machine) };
2504
+ if (!shouldWait) {
2505
+ const ready = isSshReady(machine, key);
2506
+ return interaction.result({
2507
+ data: bareMetalLaunchData(
2508
+ currentReservation,
2509
+ machine,
2510
+ key,
2511
+ currentRelationships,
2512
+ waitPolicy,
2513
+ replayed
2514
+ ),
2515
+ outcome: ready
2516
+ ? { class: "ready", phase: "ssh_ready" }
2517
+ : { class: "working", phase: "provisioning" },
2518
+ resume,
2519
+ presentation: {
2520
+ lines: [
2521
+ ready
2522
+ ? `Bare Metal is SSH-ready: ${machineHostname(machine) || "selected machine"}`
2523
+ : "Bare Metal launch accepted; provisioning continues.",
2524
+ ],
2525
+ next: "ornn connect",
2526
+ },
2527
+ });
2528
+ }
2529
+
2530
+ let waitOutcome;
2531
+ try {
2532
+ waitOutcome = await interaction.waitForGoal({
2533
+ command: "launch",
2534
+ initial: machine,
2535
+ timeoutMs: timeoutMilliseconds(input.timeout, interaction),
2536
+ isReady: (current) => isSshReady(current, key),
2537
+ isTerminal: (current) => TERMINAL_MACHINE_STATES.has(machineState(current)),
2538
+ message: (current, elapsedMs) =>
2539
+ isSshReady(current, key)
2540
+ ? "The selected Bare Metal machine is SSH-ready."
2541
+ : `The selected Bare Metal machine is ${machineState(current)}; waiting for SSH and key delivery (${Math.floor(elapsedMs / 1000)}s).`,
2542
+ projectProgress: (current) => ({ machine: projectMachine(current) }),
2543
+ poll: async ({ signal }) =>
2544
+ context.fetch(`/v1/workflows/compute/nodes/${encodeURIComponent(machineId(machine))}`, {
2545
+ signal,
2546
+ }),
2547
+ });
2548
+ } catch (error) {
2549
+ return acceptedWaitFailure(interaction, error, {
2550
+ data: bareMetalLaunchData(
2551
+ currentReservation,
2552
+ machine,
2553
+ key,
2554
+ currentRelationships,
2555
+ waitPolicy,
2556
+ replayed
2557
+ ),
2558
+ label: "Bare Metal launch",
2559
+ phase: "provisioning",
2560
+ resume,
2561
+ retryCommand: `ornn machines wait ${machineId(machine)} --timeout ${waitPolicy.timeout}`,
2562
+ });
2563
+ }
2564
+ machine = waitOutcome.value;
2565
+ const data = bareMetalLaunchData(
2566
+ currentReservation,
2567
+ machine,
2568
+ key,
2569
+ currentRelationships,
2570
+ waitPolicy,
2571
+ replayed
2572
+ );
2573
+ if (waitOutcome.kind === "ready") {
2574
+ return interaction.result({
2575
+ data,
2576
+ outcome: { class: "ready", phase: "ssh_ready" },
2577
+ resume,
2578
+ presentation: {
2579
+ lines: [`Bare Metal is SSH-ready: ${machineHostname(machine) || "selected machine"}`],
2580
+ next: "ornn connect",
2581
+ },
2582
+ });
2583
+ }
2584
+ if (waitOutcome.kind === "timeout") {
2585
+ return interaction.result({
2586
+ action: {
2587
+ command: `ornn machines wait ${machineId(machine)} --timeout ${waitPolicy.timeout}`,
2588
+ description: "Resume the exact Bare Metal readiness wait",
2589
+ type: "command",
2590
+ },
2591
+ data,
2592
+ errors: [
2593
+ {
2594
+ code: "wait_timeout",
2595
+ message: "Local wait ended before SSH was ready; the remote launch continues.",
2596
+ retryable: true,
2597
+ },
2598
+ ],
2599
+ exitCode: 124,
2600
+ outcome: { class: "working", phase: "provisioning" },
2601
+ resume,
2602
+ presentation: {
2603
+ lines: [
2604
+ `Bare Metal remains ${machineState(machine)}. Local wait timed out; remote launch continues.`,
2605
+ ],
2606
+ next: "ornn connect",
2607
+ },
2608
+ });
2609
+ }
2610
+ if (waitOutcome.kind === "interrupted") {
2611
+ return interaction.result({
2612
+ action: {
2613
+ command: `ornn machines wait ${machineId(machine)} --timeout ${waitPolicy.timeout}`,
2614
+ description: "Resume from the exact Bare Metal machine",
2615
+ type: "command",
2616
+ },
2617
+ data,
2618
+ errors: [
2619
+ {
2620
+ code: "interrupted",
2621
+ message: "Local wait was interrupted; the remote launch continues.",
2622
+ retryable: true,
2623
+ },
2624
+ ],
2625
+ exitCode: 130,
2626
+ outcome: { class: "working", phase: "provisioning" },
2627
+ resume,
2628
+ presentation: {
2629
+ lines: [
2630
+ `Bare Metal remains ${machineState(machine)}. Local wait stopped; remote launch continues.`,
2631
+ ],
2632
+ next: "ornn connect",
2633
+ },
2634
+ });
2635
+ }
2636
+ return interaction.result({
2637
+ data,
2638
+ errors: [
2639
+ {
2640
+ code: "machine_failed",
2641
+ message: "The Bare Metal machine entered a terminal state before SSH was ready.",
2642
+ retryable: false,
2643
+ },
2644
+ ],
2645
+ exitCode: 1,
2646
+ outcome: { class: "failed", phase: machineState(machine) },
2647
+ resume,
2648
+ presentation: { lines: ["Bare Metal failed before SSH was ready."] },
2649
+ });
2650
+ },
2651
+ };
2652
+
2653
+ async function spawnSsh(invocation, spawnProcess) {
2654
+ if (invocation.raw) return { code: 0, rendered: true, signal: null };
2655
+ return new Promise((resolve, reject) => {
2656
+ const child = spawnProcess(invocation.command, invocation.args, { stdio: "inherit" });
2657
+ child.on("error", reject);
2658
+ child.on("exit", (code, signal) =>
2659
+ resolve({
2660
+ code: signal ? 130 : (code ?? 0),
2661
+ rendered: false,
2662
+ signal: signal ?? null,
2663
+ })
2664
+ );
2665
+ });
2666
+ }
2667
+
2668
+ const directConnectResolutionCache = new WeakMap();
2669
+ const directConnectCandidatesCache = new WeakMap();
2670
+
2671
+ async function resolveDirectConnectMachine(reference, context, interaction) {
2672
+ const machine = await resolveMachineReference(reference, context, interaction, {
2673
+ allowMiss: true,
2674
+ });
2675
+ if (machine || !CANONICAL_ID.test(text(reference) ?? "")) return machine;
2676
+
2677
+ let machines = directConnectCandidatesCache.get(context);
2678
+ if (!machines) {
2679
+ machines = listOwnedActiveMachines(context, interaction);
2680
+ directConnectCandidatesCache.set(context, machines);
2681
+ }
2682
+ const matches = (await machines).filter(
2683
+ (candidate) =>
2684
+ (text(candidate.reservation_id) ?? text(candidate.commerce_reservation_id)) === reference
2685
+ );
2686
+ if (matches.length <= 1) return matches[0] ?? null;
2687
+ if (!interaction.eligibleHuman) {
2688
+ interaction.usage(
2689
+ "This capacity reservation has multiple active machines; supply one canonical machine ID.",
2690
+ {
2691
+ code: "machine_reservation_ambiguous",
2692
+ field: "machine",
2693
+ }
2694
+ );
2695
+ }
2696
+ const answer = await interaction.ask({
2697
+ label: "Machine",
2698
+ choices: matches.map((candidate) => ({
2699
+ label: `${machineHostname(candidate) ?? "Unnamed machine"} · ${machineAccessMode(candidate) ?? "direct"} · ${machineState(candidate)}`,
2700
+ value: machineId(candidate),
2701
+ })),
2702
+ });
2703
+ const selected = matches.find((candidate) => machineId(candidate) === answer);
2704
+ if (!selected) {
2705
+ interaction.usage("Choose one exact machine from the capacity reservation.", {
2706
+ code: "machine_reservation_ambiguous",
2707
+ field: "machine",
2708
+ });
2709
+ }
2710
+ return selected;
2711
+ }
2712
+
2713
+ function cachedDirectConnectMachine(reference, context, interaction) {
2714
+ const cached = directConnectResolutionCache.get(context);
2715
+ if (cached?.reference === reference) return cached.machine;
2716
+ const machine = resolveDirectConnectMachine(reference, context, interaction);
2717
+ directConnectResolutionCache.set(context, { reference, machine });
2718
+ return machine;
2719
+ }
2720
+
2721
+ async function directConnectCandidates(context, interaction, acceptedModes, label) {
2722
+ let machines = directConnectCandidatesCache.get(context);
2723
+ if (!machines) {
2724
+ machines = listOwnedActiveMachines(context, interaction);
2725
+ directConnectCandidatesCache.set(context, machines);
2726
+ }
2727
+ return (await machines)
2728
+ .filter((machine) =>
2729
+ acceptedModes.includes(
2730
+ machineAccessMode(machine) === "vm" ? "virtual-machine" : machineAccessMode(machine)
2731
+ )
2732
+ )
2733
+ .map((machine) => ({
2734
+ label: `${machineHostname(machine) ?? "Unnamed machine"} · ${label} · ${machineState(machine)}`,
2735
+ reference: machineId(machine),
2736
+ }));
2737
+ }
2738
+
2739
+ function createDirectMachineConnectModeHandler({
2740
+ acceptedModes,
2741
+ label,
2742
+ mode,
2743
+ spawnProcess = spawnProcessDefault,
2744
+ }) {
2745
+ return {
2746
+ mode,
2747
+ candidates({ context, interaction }) {
2748
+ return directConnectCandidates(context, interaction, acceptedModes, label);
2749
+ },
2750
+ async resolve({ context, input, interaction, reference }) {
2751
+ const machine = await cachedDirectConnectMachine(reference, context, interaction);
2752
+ if (!machine) return null;
2753
+ const accessMode =
2754
+ machineAccessMode(machine) === "vm" ? "virtual-machine" : machineAccessMode(machine);
2755
+ if (!acceptedModes.includes(accessMode)) return null;
2756
+ const reservationId = text(machine.reservation_id) ?? text(machine.commerce_reservation_id);
2757
+ if (!reservationId) {
2758
+ interaction.error("The machine is missing its capacity-reservation reference.", {
2759
+ code: "machine_correlation_failed",
2760
+ });
2761
+ }
2762
+ const reservation = await context.fetch(
2763
+ `/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`
2764
+ );
2765
+ if (!isDirectReservation(reservation)) return null;
2766
+ const privateTransportRequired = text(machine.network_mode) === "private";
2767
+ return {
2768
+ context,
2769
+ input,
2770
+ interaction,
2771
+ machine,
2772
+ mode,
2773
+ privateTransportRequired,
2774
+ reservationId,
2775
+ targetReference: reference,
2776
+ };
2777
+ },
2778
+ matches(state) {
2779
+ return state.mode === mode || acceptedModes.includes(text(state.machine?.access_mode));
2780
+ },
2781
+ async connect(state) {
2782
+ const { input, interaction, machine } = state;
2783
+ if (input.output || input.overwrite) {
2784
+ interaction.usage(
2785
+ `${label} connect uses native SSH and does not write a kubeconfig or other output file.`,
2786
+ {
2787
+ code: "direct_machine_output_unsupported",
2788
+ field: "output",
2789
+ }
2790
+ );
2791
+ }
2792
+ const reachability = machineReachability(machine);
2793
+ const invocation = reachability.class === "ready" ? sshInvocation(machine, input) : null;
2794
+ const resume = { kind: "machine", ref: machineId(machine) };
2795
+ const displayName = machineHostname(machine) || "the selected machine";
2796
+ const waitTarget = machineHostname(machine)
2797
+ ? shellQuote(machineHostname(machine))
2798
+ : "--guided";
2799
+ const privateTransport = state.privateNetwork
2800
+ ? {
2801
+ private_network: {
2802
+ id: resourceId(state.privateNetwork),
2803
+ name: text(state.privateNetwork.name) ?? text(state.privateNetwork.label),
2804
+ },
2805
+ vpn_connection: state.vpnConnection
2806
+ ? {
2807
+ id: resourceId(state.vpnConnection),
2808
+ name: text(state.vpnConnection.name) ?? text(state.vpnConnection.label),
2809
+ status:
2810
+ text(state.vpnConnection.lifecycle?.class) ?? text(state.vpnConnection.status),
2811
+ }
2812
+ : null,
2813
+ }
2814
+ : null;
2815
+ if (!invocation) {
2816
+ const keyPending = reachability.phase === "key_delivery_pending";
2817
+ return {
2818
+ action: {
2819
+ command: `ornn machines wait ${machineId(machine)} --timeout 10m`,
2820
+ description: "Wait for the exact machine to become SSH-ready",
2821
+ type: "command",
2822
+ },
2823
+ data: {
2824
+ machine: projectMachine(machine),
2825
+ private_transport: privateTransport,
2826
+ ssh_command: null,
2827
+ },
2828
+ errors: [
2829
+ {
2830
+ code: keyPending ? "ssh_key_delivery_pending" : "ssh_not_ready",
2831
+ message: keyPending
2832
+ ? "The machine lifecycle is ready, but SSH-key delivery is still pending."
2833
+ : "The machine exists, but SSH is not usable yet.",
2834
+ retryable: true,
2835
+ },
2836
+ ],
2837
+ exitCode: 4,
2838
+ outcome: {
2839
+ class: "working",
2840
+ phase: keyPending ? "key_delivery_pending" : "ssh_pending",
2841
+ },
2842
+ resume,
2843
+ presentation: {
2844
+ lines: [
2845
+ keyPending
2846
+ ? `${label} ${displayName} is running; SSH-key delivery is pending.`
2847
+ : `${label} ${displayName} is not SSH-ready yet.`,
2848
+ ],
2849
+ next: `ornn machines wait ${waitTarget}`,
2850
+ },
2851
+ };
2852
+ }
2853
+ const command = commandText(invocation);
2854
+ const data = {
2855
+ machine: projectMachine(machine),
2856
+ private_transport: privateTransport,
2857
+ ssh_command: command,
2858
+ };
2859
+ if (interaction.eligibleHuman && !interaction.machine && input.print !== true) {
2860
+ interaction.stderr?.write(
2861
+ `Opening SSH to ${displayName}.\nRun: ${command}\nWaiting for SSH to exit. Press Ctrl-C to stop only local SSH; the remote machine keeps running.\n`
2862
+ );
2863
+ const launched = await spawnSsh(invocation, spawnProcess);
2864
+ if (launched.rendered) {
2865
+ return {
2866
+ data,
2867
+ outcome: { class: "ready", phase: "ssh_ready" },
2868
+ presentation: { lines: [`Run: ${command}`] },
2869
+ resume,
2870
+ };
2871
+ }
2872
+ if (launched.code === 130) {
2873
+ return {
2874
+ data,
2875
+ errors: [
2876
+ {
2877
+ code: "interrupted",
2878
+ message:
2879
+ "The local SSH process was interrupted; the remote machine was not changed.",
2880
+ retryable: true,
2881
+ },
2882
+ ],
2883
+ exitCode: 130,
2884
+ outcome: { class: "ended", phase: "interrupted" },
2885
+ resume,
2886
+ presentation: { lines: [`SSH session interrupted for ${displayName}.`] },
2887
+ };
2888
+ }
2889
+ if (launched.code !== 0) {
2890
+ return {
2891
+ data,
2892
+ errors: [
2893
+ {
2894
+ code: "ssh_failed",
2895
+ message: `SSH exited with status ${launched.code}.`,
2896
+ retryable: false,
2897
+ },
2898
+ ],
2899
+ exitCode: 1,
2900
+ outcome: { class: "failed", phase: "ssh_failed" },
2901
+ resume,
2902
+ presentation: {
2903
+ lines: [`SSH session for ${displayName} exited with status ${launched.code}.`],
2904
+ },
2905
+ };
2906
+ }
2907
+ }
2908
+ return {
2909
+ data,
2910
+ outcome: { class: "ready", phase: "ssh_ready" },
2911
+ presentation: {
2912
+ lines: [
2913
+ input.print === true || !interaction.eligibleHuman
2914
+ ? `Run: ${command}`
2915
+ : `SSH session ended for ${displayName}.`,
2916
+ ],
2917
+ },
2918
+ resume,
2919
+ };
2920
+ },
2921
+ };
2922
+ }
2923
+
2924
+ export function createVmConnectModeHandler(options = {}) {
2925
+ return createDirectMachineConnectModeHandler({
2926
+ ...options,
2927
+ acceptedModes: ["virtual-machine", "vm"],
2928
+ label: "VM",
2929
+ mode: "vm",
2930
+ });
2931
+ }
2932
+
2933
+ export function createBareMetalConnectModeHandler(options = {}) {
2934
+ return createDirectMachineConnectModeHandler({
2935
+ ...options,
2936
+ acceptedModes: ["bare-metal"],
2937
+ label: "Bare Metal",
2938
+ mode: "bare-metal",
2939
+ });
2940
+ }
2941
+
2942
+ function directMode(value, interaction, { optional = false } = {}) {
2943
+ const mode = text(value)?.toLowerCase() ?? null;
2944
+ if (!mode && optional) return null;
2945
+ if (!mode || !["vm", "bare-metal"].includes(mode)) {
2946
+ interaction.usage("--mode must be vm or bare-metal.", {
2947
+ code: "invalid_access_mode",
2948
+ field: "mode",
2949
+ });
2950
+ }
2951
+ return mode;
2952
+ }
2953
+
2954
+ function networkMode(value, interaction, { optional = false } = {}) {
2955
+ const network = text(value)?.toLowerCase() ?? null;
2956
+ if (!network && optional) return null;
2957
+ if (!network || !["public", "private"].includes(network)) {
2958
+ interaction.usage("--network must be public or private.", {
2959
+ code: "invalid_network_mode",
2960
+ field: "network",
2961
+ });
2962
+ }
2963
+ return network;
2964
+ }
2965
+
2966
+ function validateWaitInput(input, interaction, { waitCommand = false } = {}) {
2967
+ if (input.wait && input.noWait) {
2968
+ interaction.usage("--wait and --no-wait cannot be combined.", {
2969
+ code: "wait_conflict",
2970
+ field: "wait",
2971
+ });
2972
+ }
2973
+ if (input.noWait && text(input.timeout)) {
2974
+ interaction.usage("--timeout cannot be combined with --no-wait.", {
2975
+ code: "wait_conflict",
2976
+ field: "timeout",
2977
+ });
2978
+ }
2979
+ if (text(input.timeout)) timeoutMilliseconds(input.timeout, interaction);
2980
+ const noninteractive = interaction.machine || !interaction.eligibleHuman;
2981
+ if (!waitCommand && noninteractive && input.wait && !text(input.timeout)) {
2982
+ interaction.usage("--wait requires a bounded --timeout outside an eligible terminal.", {
2983
+ code: "timeout_required",
2984
+ field: "timeout",
2985
+ });
2986
+ }
2987
+ if (waitCommand && interaction.machine && !text(input.timeout)) {
2988
+ interaction.usage("Automation must bound machines wait with --timeout <duration>.", {
2989
+ code: "timeout_required",
2990
+ field: "timeout",
2991
+ });
2992
+ }
2993
+ }
2994
+
2995
+ function requireMachineMutationSafety(input, interaction, { destructive = false } = {}) {
2996
+ validateWaitInput(input, interaction);
2997
+ if (destructive && input.eraseLocalData !== true) {
2998
+ interaction.usage(
2999
+ "machines reset requires --erase-local-data; --yes cannot infer destructive scope.",
3000
+ {
3001
+ code: "erase_local_data_required",
3002
+ field: "eraseLocalData",
3003
+ }
3004
+ );
3005
+ }
3006
+ if (interaction.machine || !interaction.eligibleHuman) {
3007
+ if (input.yes !== true) {
3008
+ interaction.usage(
3009
+ "--yes is required outside an eligible terminal after reviewing the exact intent.",
3010
+ {
3011
+ code: "consent_required",
3012
+ field: "yes",
3013
+ }
3014
+ );
3015
+ }
3016
+ if (!text(input.idempotencyKey)) {
3017
+ interaction.usage("--idempotency-key is required outside an eligible terminal.", {
3018
+ code: "idempotency_key_required",
3019
+ field: "idempotencyKey",
3020
+ });
3021
+ }
3022
+ }
3023
+ }
3024
+
3025
+ function targetPosition(input, interaction, usage) {
3026
+ const values = Array.isArray(input.$positionals) ? input.$positionals : [];
3027
+ if (input.guided && !interaction.eligibleHuman) {
3028
+ interaction.usage("--guided requires an eligible interactive terminal.", {
3029
+ code: "guided_unavailable",
3030
+ field: "guided",
3031
+ });
3032
+ }
3033
+ if (values.length > 1 || (values.length === 0 && input.guided !== true)) {
3034
+ interaction.usage(`Usage: ${usage}`, {
3035
+ code: values.length > 1 ? "target_conflict" : "missing_input",
3036
+ field: "machine",
3037
+ });
3038
+ }
3039
+ const reference = text(values[0]);
3040
+ return reference;
3041
+ }
3042
+
3043
+ function machineCursorPage(items, input, interaction) {
3044
+ const limit = Number.isSafeInteger(input.limit) ? input.limit : 50;
3045
+ const cursor = text(input.cursor);
3046
+ let offset = 0;
3047
+ if (cursor) {
3048
+ let after;
3049
+ try {
3050
+ if (!/^[A-Za-z0-9_-]+$/.test(cursor)) throw new Error("invalid cursor");
3051
+ const decoded = record(JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")));
3052
+ if (decoded.version !== 1 || decoded.kind !== "machines") throw new Error("invalid cursor");
3053
+ after = text(decoded.after);
3054
+ } catch {
3055
+ interaction.usage("The machine cursor is invalid or stale.", {
3056
+ code: "invalid_cursor",
3057
+ field: "cursor",
3058
+ });
3059
+ }
3060
+ const index = items.findIndex((item) => machineId(item) === after);
3061
+ if (index === -1) {
3062
+ interaction.usage("The machine cursor is invalid or stale.", {
3063
+ code: "invalid_cursor",
3064
+ field: "cursor",
3065
+ });
3066
+ }
3067
+ offset = index + 1;
3068
+ }
3069
+ const selected = items.slice(offset, offset + limit);
3070
+ const last = selected.at(-1);
3071
+ return {
3072
+ items: selected,
3073
+ page: {
3074
+ limit,
3075
+ returned: selected.length,
3076
+ next_cursor:
3077
+ offset + selected.length < items.length && last
3078
+ ? Buffer.from(
3079
+ JSON.stringify({
3080
+ version: 1,
3081
+ kind: "machines",
3082
+ after: machineId(last),
3083
+ })
3084
+ ).toString("base64url")
3085
+ : null,
3086
+ },
3087
+ };
3088
+ }
3089
+
3090
+ async function resolveMachineCommandTarget(input, context, interaction, usage) {
3091
+ const reference = targetPosition(input, interaction, usage);
3092
+ const machine = await resolveMachineReference(reference, context, interaction);
3093
+ const reservationId = text(machine.reservation_id) ?? text(machine.commerce_reservation_id);
3094
+ if (!reservationId) {
3095
+ interaction.error("The machine is missing its capacity-reservation reference.", {
3096
+ code: "machine_correlation_failed",
3097
+ });
3098
+ }
3099
+ const reservation = await context.fetch(
3100
+ `/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`
3101
+ );
3102
+ if (!isDirectReservation(reservation)) {
3103
+ interaction.error(
3104
+ "The selected machine belongs to a cluster rather than the direct-machine lifecycle.",
3105
+ {
3106
+ code: "machine_not_direct",
3107
+ }
3108
+ );
3109
+ }
3110
+ return machine;
3111
+ }
3112
+
3113
+ function machineLines(machine) {
3114
+ const projected = projectMachine(machine);
3115
+ return [
3116
+ `${projected.access_mode === "bare-metal" ? "Bare Metal" : "VM"} ${projected.hostname ?? projected.id}`,
3117
+ ` ID: ${projected.id}`,
3118
+ ` Capacity reservation: ${projected.reservation_id ?? "unreported"}`,
3119
+ ` Lifecycle: ${projected.lifecycle.class} / ${projected.lifecycle.phase}`,
3120
+ ` Reachability: ${projected.reachability.class} / ${projected.reachability.phase}`,
3121
+ ];
3122
+ }
3123
+
3124
+ function shouldWaitForMutation(input, interaction) {
3125
+ if (input.noWait) return false;
3126
+ if (input.wait) return true;
3127
+ return !interaction.machine && interaction.eligibleHuman;
3128
+ }
3129
+
3130
+ async function waitForMachine(context, interaction, machine, timeout, command) {
3131
+ return interaction.waitForGoal({
3132
+ command,
3133
+ initial: machine,
3134
+ timeoutMs: timeoutMilliseconds(timeout, interaction),
3135
+ isReady: (current) => machineReachability(current).class === "ready",
3136
+ isTerminal: (current) => machineReachability(current).class === "failed",
3137
+ message: (current, elapsedMs) => {
3138
+ const reachability = machineReachability(current);
3139
+ return reachability.class === "ready"
3140
+ ? `Machine ${machineId(current)} is SSH-ready.`
3141
+ : `Machine ${machineId(current)} is ${machineState(current)}; ${reachability.phase.replaceAll("_", " ")} (${Math.floor(elapsedMs / 1000)}s).`;
3142
+ },
3143
+ projectProgress: (current) => ({ machine: projectMachine(current) }),
3144
+ poll: async ({ signal }) =>
3145
+ context.fetch(`/v1/workflows/compute/nodes/${encodeURIComponent(machineId(machine))}`, {
3146
+ signal,
3147
+ }),
3148
+ });
3149
+ }
3150
+
3151
+ function waitExecution(
3152
+ interaction,
3153
+ machine,
3154
+ waitOutcome,
3155
+ { acceptedPhase = "accepted", data = {}, recoveryCommand, successLines }
3156
+ ) {
3157
+ const id = machineId(machine);
3158
+ const resume = { kind: "machine", ref: id };
3159
+ const resultData = { ...data, machine: projectMachine(machine) };
3160
+ if (waitOutcome.kind === "ready") {
3161
+ return interaction.result({
3162
+ data: resultData,
3163
+ outcome: { class: "ready", phase: "ssh_ready" },
3164
+ resume,
3165
+ presentation: {
3166
+ lines: [
3167
+ ...(successLines ?? [
3168
+ `Machine is SSH-ready: ${machineHostname(machine) || "selected machine"}`,
3169
+ ]),
3170
+ ],
3171
+ next: "ornn connect",
3172
+ },
3173
+ });
3174
+ }
3175
+ if (waitOutcome.kind === "timeout") {
3176
+ return interaction.result({
3177
+ action: {
3178
+ command: `ornn machines wait ${id} --timeout 10m`,
3179
+ description: "Resume the exact machine readiness wait",
3180
+ type: "command",
3181
+ },
3182
+ data: resultData,
3183
+ errors: [
3184
+ {
3185
+ code: "wait_timeout",
3186
+ message: "The local wait timed out; remote work was not canceled.",
3187
+ retryable: true,
3188
+ },
3189
+ ],
3190
+ exitCode: 124,
3191
+ outcome: { class: "working", phase: acceptedPhase },
3192
+ resume,
3193
+ presentation: {
3194
+ lines: ["The selected machine is not SSH-ready yet. Remote work continues."],
3195
+ next: "ornn machines wait --guided",
3196
+ },
3197
+ });
3198
+ }
3199
+ if (waitOutcome.kind === "interrupted") {
3200
+ return interaction.result({
3201
+ action: {
3202
+ command: `ornn machines wait ${id} --timeout 10m`,
3203
+ description: "Resume the exact machine readiness wait",
3204
+ type: "command",
3205
+ },
3206
+ data: resultData,
3207
+ errors: [
3208
+ {
3209
+ code: "interrupted",
3210
+ message: "The local wait was interrupted; remote work was not canceled.",
3211
+ retryable: true,
3212
+ },
3213
+ ],
3214
+ exitCode: 130,
3215
+ outcome: { class: "working", phase: acceptedPhase },
3216
+ resume,
3217
+ presentation: {
3218
+ lines: ["Stopped waiting for the selected machine. Remote work continues."],
3219
+ next: "ornn machines wait --guided",
3220
+ },
3221
+ });
3222
+ }
3223
+ return interaction.result({
3224
+ ...(recoveryCommand
3225
+ ? {
3226
+ action: {
3227
+ command: recoveryCommand,
3228
+ description: "Retry the failed machine operation with a new idempotency key",
3229
+ type: "command",
3230
+ },
3231
+ }
3232
+ : {}),
3233
+ data: resultData,
3234
+ errors: [
3235
+ {
3236
+ code: "machine_failed",
3237
+ message:
3238
+ text(machine.customer_lifecycle_error) ??
3239
+ "The machine operation failed before SSH became ready.",
3240
+ retryable: Boolean(recoveryCommand),
3241
+ },
3242
+ ],
3243
+ exitCode: 1,
3244
+ outcome: { class: "failed", phase: "operation_failed" },
3245
+ resume,
3246
+ presentation: {
3247
+ lines: [
3248
+ text(machine.customer_lifecycle_error)
3249
+ ? `Machine operation failed: ${text(machine.customer_lifecycle_error)}.`
3250
+ : `The selected machine entered ${machineState(machine)}.`,
3251
+ ],
3252
+ ...(recoveryCommand ? { next: recoveryCommand } : {}),
3253
+ },
3254
+ });
3255
+ }
3256
+
3257
+ async function listMachines(input, context, interaction) {
3258
+ positional(
3259
+ input,
3260
+ 0,
3261
+ "ornn machines list [--limit <1-500>] [--cursor <opaque-cursor>]",
3262
+ interaction
3263
+ );
3264
+ const rows = (await listOwnedActiveMachines(context, interaction))
3265
+ .filter((machine) => machineId(machine))
3266
+ .sort((left, right) => machineId(left).localeCompare(machineId(right)));
3267
+ const selected = machineCursorPage(rows, input, interaction);
3268
+ return interaction.result({
3269
+ data: { machines: selected.items.map(projectMachine) },
3270
+ outcome: { class: "ready", phase: selected.items.length ? "listed" : "empty" },
3271
+ page: selected.page,
3272
+ presentation: {
3273
+ lines: selected.items.length
3274
+ ? selected.items.flatMap(machineLines)
3275
+ : ["No direct machines found."],
3276
+ ...(selected.page.next_cursor
3277
+ ? { next: `ornn machines list --cursor ${selected.page.next_cursor}` }
3278
+ : {}),
3279
+ },
3280
+ });
3281
+ }
3282
+
3283
+ async function showMachine(input, context, interaction) {
3284
+ const machine = await resolveMachineCommandTarget(
3285
+ input,
3286
+ context,
3287
+ interaction,
3288
+ "ornn machines show <canonical-machine-id|exact-hostname> [--guided]"
3289
+ );
3290
+ return interaction.result({
3291
+ data: { machine: projectMachine(machine) },
3292
+ outcome: machineLifecycle(machine),
3293
+ resume: { kind: "machine", ref: machineId(machine) },
3294
+ presentation: {
3295
+ lines: machineLines(machine),
3296
+ ...(machineReachability(machine).class === "ready"
3297
+ ? { next: "ornn connect" }
3298
+ : { next: "ornn machines wait --guided" }),
3299
+ },
3300
+ });
3301
+ }
3302
+
3303
+ async function waitMachine(input, context, interaction) {
3304
+ validateWaitInput(input, interaction, { waitCommand: true });
3305
+ const machine = await resolveMachineCommandTarget(
3306
+ input,
3307
+ context,
3308
+ interaction,
3309
+ "ornn machines wait <canonical-machine-id|exact-hostname> [--guided] [--timeout <duration>]"
3310
+ );
3311
+ const timeout = text(input.timeout) ?? "10m";
3312
+ let waitOutcome;
3313
+ try {
3314
+ waitOutcome = await waitForMachine(context, interaction, machine, timeout, "machines wait");
3315
+ } catch (error) {
3316
+ return acceptedWaitFailure(interaction, error, {
3317
+ data: {
3318
+ machine: projectMachine(machine),
3319
+ wait: { enabled: true, timeout },
3320
+ },
3321
+ label: "machine readiness",
3322
+ phase: "ssh_pending",
3323
+ resume: { kind: "machine", ref: machineId(machine) },
3324
+ retryCommand: `ornn machines wait ${machineId(machine)} --timeout ${timeout}`,
3325
+ });
3326
+ }
3327
+ return waitExecution(interaction, waitOutcome.value, waitOutcome, {
3328
+ acceptedPhase: "ssh_pending",
3329
+ recoveryCommand: failedOperationRecoveryCommand(waitOutcome.value, interaction),
3330
+ data: { wait: { enabled: true, timeout } },
3331
+ });
3332
+ }
3333
+
3334
+ function directMachineSnapshot(machines, relationships, keys) {
3335
+ return JSON.stringify({
3336
+ keys: keys.map(resourceId).sort(),
3337
+ machines: machines
3338
+ .map((machine) => ({
3339
+ id: machineId(machine),
3340
+ mode: machineAccessMode(machine),
3341
+ network: text(machine.network_mode),
3342
+ username: machineUsername(machine),
3343
+ }))
3344
+ .sort((left, right) => left.id.localeCompare(right.id)),
3345
+ relationships,
3346
+ });
3347
+ }
3348
+
3349
+ async function guidedSwitchReservation(context, interaction) {
3350
+ const reservations = await listActiveDirectReservations(context, interaction);
3351
+ if (!reservations.length) {
3352
+ interaction.error("No active direct-machine reservation is available to switch.", {
3353
+ code: "no_active_reservation",
3354
+ });
3355
+ }
3356
+ let reference = resourceId(reservations[0]);
3357
+ if (reservations.length > 1) {
3358
+ reference = await interaction.ask({
3359
+ label: "Capacity reservation",
3360
+ choices: reservations.map((reservation) => ({
3361
+ label: reservationLabel(reservation),
3362
+ value: resourceId(reservation),
3363
+ })),
3364
+ });
3365
+ }
3366
+ return assertActiveReservation(
3367
+ await context.fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(reference)}`),
3368
+ reference,
3369
+ interaction
3370
+ );
3371
+ }
3372
+
3373
+ async function switchMachine(input, context, interaction) {
3374
+ requireMachineMutationSafety(input, interaction);
3375
+ const values = Array.isArray(input.$positionals) ? input.$positionals : [];
3376
+ if (input.guided && !interaction.eligibleHuman) {
3377
+ interaction.usage("--guided requires an eligible interactive terminal.", {
3378
+ code: "guided_unavailable",
3379
+ field: "guided",
3380
+ });
3381
+ }
3382
+ if (values.length > 1 || (values.length === 0 && input.guided !== true)) {
3383
+ interaction.usage(
3384
+ "Usage: ornn machines switch <canonical-reservation-id> [--mode vm|bare-metal] [--network public|private] [--erase-local-data]",
3385
+ { code: values.length > 1 ? "target_conflict" : "missing_input", field: "reservation" }
3386
+ );
3387
+ }
3388
+ const suppliedReservation = text(values[0]);
3389
+ if (interaction.machine && suppliedReservation && !CANONICAL_ID.test(suppliedReservation)) {
3390
+ interaction.usage("Automation must use a canonical capacity-reservation identifier.", {
3391
+ code: "invalid_reference",
3392
+ field: "reservation",
3393
+ });
3394
+ }
3395
+ const requestedMode = directMode(input.mode, interaction, { optional: true });
3396
+ const requestedNetwork = networkMode(input.network, interaction, { optional: true });
3397
+ if (!requestedMode && !requestedNetwork) {
3398
+ interaction.usage("machines switch requires --mode, --network, or both.", {
3399
+ code: "switch_goal_required",
3400
+ });
3401
+ }
3402
+ const reservation = suppliedReservation
3403
+ ? assertActiveReservation(
3404
+ await context.fetch(
3405
+ `/v1/workflows/commerce/reservations/${encodeURIComponent(suppliedReservation)}`
3406
+ ),
3407
+ suppliedReservation,
3408
+ interaction
3409
+ )
3410
+ : await guidedSwitchReservation(context, interaction);
3411
+ const reservationId = resourceId(reservation);
3412
+ if (!isDirectReservation(reservation)) {
3413
+ interaction.error("machines switch accepts only a direct VM or Bare Metal reservation.", {
3414
+ code: "reservation_not_direct",
3415
+ });
3416
+ }
3417
+ const [machines, relationships, keys] = await Promise.all([
3418
+ fetchMachines(context, reservationId),
3419
+ bareMetalRelationships(context, reservationId),
3420
+ fetchAssignedKeys(context, reservationId),
3421
+ ]);
3422
+ if (machines.length !== 1) {
3423
+ interaction.error(
3424
+ "machines switch requires exactly one active direct machine on the capacity reservation.",
3425
+ {
3426
+ code: machines.length ? "machine_ambiguous" : "machine_not_found",
3427
+ }
3428
+ );
3429
+ }
3430
+ if (!keys.length) {
3431
+ interaction.error("The capacity reservation has no SSH-key assignment to preserve.", {
3432
+ code: "ssh_key_assignment_missing",
3433
+ });
3434
+ }
3435
+ const currentMode = machineAccessMode(machines[0]);
3436
+ if (!["vm", "bare-metal"].includes(currentMode)) {
3437
+ interaction.error("The selected resource is not a direct VM or Bare Metal machine.", {
3438
+ code: "invalid_access_mode",
3439
+ });
3440
+ }
3441
+ const currentNetwork = text(machines[0].network_mode) ?? "public";
3442
+ const targetMode = requestedMode ?? currentMode;
3443
+ const targetNetwork = requestedNetwork ?? currentNetwork;
3444
+ const erasesLocalData = targetMode !== currentMode;
3445
+ if (erasesLocalData && input.eraseLocalData !== true) {
3446
+ interaction.usage(
3447
+ "Changing between VM and Bare Metal permanently erases source-local data and requires --erase-local-data; --yes cannot infer destructive scope.",
3448
+ { code: "erase_local_data_required", field: "eraseLocalData" }
3449
+ );
3450
+ }
3451
+ const wait = {
3452
+ enabled: shouldWaitForMutation(input, interaction),
3453
+ timeout: shouldWaitForMutation(input, interaction) ? (text(input.timeout) ?? "10m") : null,
3454
+ };
3455
+ if (targetMode === currentMode && targetNetwork === currentNetwork) {
3456
+ return interaction.result({
3457
+ data: {
3458
+ machine: projectMachine(machines[0]),
3459
+ mode: targetMode,
3460
+ network_mode: targetNetwork,
3461
+ retained_ssh_keys: keys.map(projectKey),
3462
+ relationships,
3463
+ replayed: true,
3464
+ wait,
3465
+ },
3466
+ outcome:
3467
+ machineReachability(machines[0]).class === "ready"
3468
+ ? { class: "ready", phase: "ssh_ready" }
3469
+ : { class: "working", phase: "switch_accepted" },
3470
+ resume: { kind: "machine", ref: machineId(machines[0]) },
3471
+ presentation: {
3472
+ lines: ["The machine already satisfies the requested switch goal."],
3473
+ next: "ornn connect",
3474
+ },
3475
+ });
3476
+ }
3477
+ const snapshot = directMachineSnapshot(machines, relationships, keys);
3478
+ const accepted = await interaction.review({
3479
+ title: "Machine switch review",
3480
+ terms: [
3481
+ { label: "Capacity reservation", value: reservationId },
3482
+ {
3483
+ label: "Access mode",
3484
+ value: requestedMode ? `${currentMode} to ${targetMode}` : `${currentMode} (unchanged)`,
3485
+ },
3486
+ {
3487
+ label: "Network",
3488
+ value: requestedNetwork
3489
+ ? `${currentNetwork} to ${targetNetwork}`
3490
+ : `${currentNetwork} (unchanged)`,
3491
+ },
3492
+ {
3493
+ label: "SSH keys",
3494
+ value: `${keys.length} assigned key${keys.length === 1 ? "" : "s"} retained`,
3495
+ },
3496
+ { label: "Volume mount", value: relationshipLabel(relationships, "storage") },
3497
+ {
3498
+ label: "Local data",
3499
+ value: erasesLocalData
3500
+ ? "ERASED permanently under explicit --erase-local-data scope"
3501
+ : "Preserved",
3502
+ },
3503
+ {
3504
+ label: "Consequence",
3505
+ value: "SSH is interrupted until the requested goal is ready",
3506
+ },
3507
+ ],
3508
+ prompt: "Apply this machine switch?",
3509
+ });
3510
+ if (!accepted) {
3511
+ return interaction.result({
3512
+ errors: [{ code: "declined", message: "Machine switch declined.", retryable: false }],
3513
+ exitCode: 1,
3514
+ outcome: { class: "ended", phase: "declined" },
3515
+ presentation: { lines: ["Machine switch declined. No change was submitted."] },
3516
+ });
3517
+ }
3518
+ const [currentReservationValue, currentMachines, currentRelationships, currentKeys] =
3519
+ await Promise.all([
3520
+ context.fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`),
3521
+ fetchMachines(context, reservationId),
3522
+ bareMetalRelationships(context, reservationId),
3523
+ fetchAssignedKeys(context, reservationId),
3524
+ ]);
3525
+ const currentReservation = assertActiveReservation(
3526
+ currentReservationValue,
3527
+ reservationId,
3528
+ interaction
3529
+ );
3530
+ if (
3531
+ reservationLabel(currentReservation) !== reservationLabel(reservation) ||
3532
+ directMachineSnapshot(currentMachines, currentRelationships, currentKeys) !== snapshot
3533
+ ) {
3534
+ interaction.error("Machine access, network, volume, or SSH-key intent changed after review.", {
3535
+ code: "machine_switch_stale",
3536
+ });
3537
+ }
3538
+ const idempotencyKey = text(input.idempotencyKey) ?? interaction.idempotencyKey();
3539
+ const body = {
3540
+ ...(requestedMode
3541
+ ? { access_mode: requestedMode === "vm" ? "virtual-machine" : "bare-metal" }
3542
+ : {}),
3543
+ ...(requestedNetwork ? { network_mode: requestedNetwork } : {}),
3544
+ ...(erasesLocalData ? { erase_local_data: true } : {}),
3545
+ machine_count: machines.length,
3546
+ request_id: idempotencyKey,
3547
+ ssh_key_ids: keys.map(resourceId),
3548
+ };
3549
+ const retryCommand = [
3550
+ "ornn machines switch",
3551
+ shellQuote(reservationId),
3552
+ requestedMode ? `--mode ${requestedMode}` : null,
3553
+ requestedNetwork ? `--network ${requestedNetwork}` : null,
3554
+ erasesLocalData ? "--erase-local-data" : null,
3555
+ "--yes",
3556
+ "--no-wait",
3557
+ `--idempotency-key ${shellQuote(idempotencyKey)}`,
3558
+ ]
3559
+ .filter(Boolean)
3560
+ .join(" ");
3561
+ let response;
3562
+ try {
3563
+ response = await context.fetch(
3564
+ `/v1/workflows/compute/nodes/reservations/${encodeURIComponent(reservationId)}/switch-access`,
3565
+ { method: "POST", body }
3566
+ );
3567
+ } catch (error) {
3568
+ return uncertainMutationFailure(interaction, error, {
3569
+ data: {
3570
+ mode: targetMode,
3571
+ network_mode: targetNetwork,
3572
+ erased_local_data: erasesLocalData,
3573
+ request_id: idempotencyKey,
3574
+ },
3575
+ label: "machine switch",
3576
+ phase: "switch_submission_unknown",
3577
+ resume: { kind: "machine", ref: machineId(machines[0]) },
3578
+ retryCommand,
3579
+ });
3580
+ }
3581
+ const responseSource = record(response);
3582
+ const switched = activeMachines(
3583
+ Array.isArray(responseSource.machines)
3584
+ ? responseSource.machines.map(record)
3585
+ : await fetchMachines(context, reservationId)
3586
+ );
3587
+ if (switched.length !== 1) {
3588
+ interaction.error("The switch response did not preserve one exact machine.", {
3589
+ code: "machine_correlation_failed",
3590
+ retryable: true,
3591
+ });
3592
+ }
3593
+ let machine = switched[0];
3594
+ const operation = record(
3595
+ Array.isArray(responseSource.operations) ? responseSource.operations[0] : null
3596
+ );
3597
+ const targetReference = text(operation.node_id) ?? text(machine.node_id) ?? machineId(machine);
3598
+ const data = {
3599
+ mode: targetMode,
3600
+ network_mode: targetNetwork,
3601
+ retained_ssh_keys: keys.map(projectKey),
3602
+ relationships,
3603
+ erased_local_data: erasesLocalData,
3604
+ replayed: false,
3605
+ request_id: idempotencyKey,
3606
+ wait,
3607
+ };
3608
+ if (!wait.enabled) {
3609
+ return interaction.result({
3610
+ data: { ...data, machine: projectMachine(machine) },
3611
+ outcome:
3612
+ machineReachability(machine).class === "ready"
3613
+ ? { class: "ready", phase: "ssh_ready" }
3614
+ : { class: "working", phase: "switch_accepted" },
3615
+ resume: { kind: "machine", ref: targetReference },
3616
+ presentation: {
3617
+ lines: ["Machine switch accepted."],
3618
+ next: "ornn machines wait --guided",
3619
+ },
3620
+ });
3621
+ }
3622
+ const waiting = {
3623
+ ...record(machine),
3624
+ id: targetReference,
3625
+ instance_id: targetReference,
3626
+ node_id: text(operation.node_id) ?? text(machine.node_id),
3627
+ state: "switching",
3628
+ status: "switching",
3629
+ ssh_endpoint: null,
3630
+ };
3631
+ let waitOutcome;
3632
+ try {
3633
+ waitOutcome = await waitForMachine(
3634
+ context,
3635
+ interaction,
3636
+ waiting,
3637
+ wait.timeout,
3638
+ "machines switch"
3639
+ );
3640
+ } catch (error) {
3641
+ return acceptedWaitFailure(interaction, error, {
3642
+ data: { ...data, machine: projectMachine(waiting) },
3643
+ label: "machine switch",
3644
+ phase: "switch_accepted",
3645
+ resume: { kind: "machine", ref: machineId(waiting) },
3646
+ retryCommand: `ornn machines wait ${machineId(waiting)} --timeout ${wait.timeout}`,
3647
+ });
3648
+ }
3649
+ machine = waitOutcome.value;
3650
+ const finalRelationships = await bareMetalRelationships(context, reservationId);
3651
+ const failedRecoveryCommand = [
3652
+ "ornn machines switch",
3653
+ shellQuote(reservationId),
3654
+ `--mode ${targetMode}`,
3655
+ `--network ${targetNetwork}`,
3656
+ erasesLocalData ? "--erase-local-data" : null,
3657
+ "--yes",
3658
+ `--idempotency-key ${shellQuote(interaction.idempotencyKey())}`,
3659
+ ]
3660
+ .filter(Boolean)
3661
+ .join(" ");
3662
+ if (
3663
+ machineAccessMode(machine) !== targetMode ||
3664
+ text(machine.network_mode)?.toLowerCase() !== targetNetwork
3665
+ ) {
3666
+ return interaction.result({
3667
+ action: {
3668
+ command: failedRecoveryCommand,
3669
+ description: "Retry the switch with a new idempotency key.",
3670
+ type: "command",
3671
+ },
3672
+ data: { ...data, machine: projectMachine(machine), relationships: finalRelationships },
3673
+ errors: [
3674
+ {
3675
+ code: "switch_goal_not_applied",
3676
+ message: "The machine became reachable without the reviewed access and network goal.",
3677
+ retryable: true,
3678
+ resource_ref: reservationId,
3679
+ },
3680
+ ],
3681
+ exitCode: 4,
3682
+ outcome: { class: "failed", phase: "switch_goal_not_applied" },
3683
+ presentation: {
3684
+ lines: ["Machine is reachable, but the reviewed switch goal is not applied."],
3685
+ next: failedRecoveryCommand,
3686
+ },
3687
+ resume: { kind: "machine", ref: machineId(machine) },
3688
+ });
3689
+ }
3690
+ return waitExecution(interaction, machine, waitOutcome, {
3691
+ acceptedPhase: "switch_accepted",
3692
+ data: { ...data, relationships: finalRelationships },
3693
+ recoveryCommand: failedRecoveryCommand,
3694
+ successLines: [
3695
+ `Machine switch is SSH-ready: ${machineHostname(machine) || machineId(machine)}`,
3696
+ ],
3697
+ });
3698
+ }
3699
+
3700
+ async function maintainMachine(command, input, context, interaction) {
3701
+ const destructive = command === "reset";
3702
+ requireMachineMutationSafety(input, interaction, { destructive });
3703
+ const machine = await resolveMachineCommandTarget(
3704
+ input,
3705
+ context,
3706
+ interaction,
3707
+ `ornn machines ${command} <canonical-machine-id|exact-hostname>${destructive ? " --erase-local-data" : ""} [--no-wait]`
3708
+ );
3709
+ if (TERMINAL_MACHINE_STATES.has(machineState(machine))) {
3710
+ interaction.error("A terminal machine cannot be rebooted or reset.", {
3711
+ code: "machine_terminal",
3712
+ });
3713
+ }
3714
+ const accepted = await interaction.review({
3715
+ title: destructive ? "Machine reset review" : "Machine reboot review",
3716
+ terms: [
3717
+ {
3718
+ label: "Machine",
3719
+ value: `${machineHostname(machine) || "unnamed"} (${machineId(machine)})`,
3720
+ },
3721
+ { label: "Access mode", value: machineAccessMode(machine) ?? "unreported" },
3722
+ { label: "Access", value: "SSH will be interrupted until the machine is ready again" },
3723
+ {
3724
+ label: "Local data",
3725
+ value: destructive
3726
+ ? "ERASED permanently under explicit --erase-local-data scope"
3727
+ : "Preserved",
3728
+ },
3729
+ {
3730
+ label: "SSH keys",
3731
+ value: destructive ? "Restored after the machine reconnects" : "Preserved",
3732
+ },
3733
+ {
3734
+ label: "External volume",
3735
+ value: destructive
3736
+ ? "Relationship preserved; local data is still erased"
3737
+ : "Relationship preserved",
3738
+ },
3739
+ ],
3740
+ prompt: destructive ? "Erase local data and reset this machine?" : "Reboot this machine?",
3741
+ });
3742
+ if (!accepted) {
3743
+ return interaction.result({
3744
+ errors: [
3745
+ {
3746
+ code: "declined",
3747
+ message: destructive ? "Machine reset declined." : "Machine reboot declined.",
3748
+ retryable: false,
3749
+ },
3750
+ ],
3751
+ exitCode: 1,
3752
+ outcome: { class: "ended", phase: "declined" },
3753
+ presentation: {
3754
+ lines: [`Machine ${destructive ? "reset" : "reboot"} declined. No change was submitted.`],
3755
+ },
3756
+ });
3757
+ }
3758
+ const current = await context.fetch(
3759
+ `/v1/workflows/compute/nodes/${encodeURIComponent(machineId(machine))}`
3760
+ );
3761
+ if (
3762
+ machineId(current) !== machineId(machine) ||
3763
+ machineAccessMode(current) !== machineAccessMode(machine) ||
3764
+ machineState(current) !== machineState(machine)
3765
+ ) {
3766
+ interaction.error("The machine changed after review; maintenance was not submitted.", {
3767
+ code: "machine_maintenance_stale",
3768
+ });
3769
+ }
3770
+ const idempotencyKey = text(input.idempotencyKey) ?? interaction.idempotencyKey();
3771
+ const maintenanceRetryCommand = [
3772
+ `ornn machines ${command}`,
3773
+ shellQuote(machineId(machine)),
3774
+ destructive ? "--erase-local-data" : null,
3775
+ "--yes",
3776
+ "--no-wait",
3777
+ `--idempotency-key ${shellQuote(idempotencyKey)}`,
3778
+ ]
3779
+ .filter(Boolean)
3780
+ .join(" ");
3781
+ let response;
3782
+ try {
3783
+ response = record(
3784
+ await context.fetch(
3785
+ `/v1/workflows/compute/nodes/${encodeURIComponent(machineId(machine))}/reboot`,
3786
+ {
3787
+ method: "POST",
3788
+ body: {
3789
+ mode: destructive ? "hard_reset" : "reboot",
3790
+ request_id: idempotencyKey,
3791
+ erase_local_data: destructive,
3792
+ },
3793
+ }
3794
+ )
3795
+ );
3796
+ } catch (error) {
3797
+ return uncertainMutationFailure(interaction, error, {
3798
+ data: {
3799
+ operation: destructive ? "reset" : "reboot",
3800
+ erased_local_data: destructive,
3801
+ request_id: idempotencyKey,
3802
+ },
3803
+ label: `machine ${destructive ? "reset" : "reboot"}`,
3804
+ phase: destructive ? "reset_submission_unknown" : "reboot_submission_unknown",
3805
+ resume: { kind: "machine", ref: machineId(machine) },
3806
+ retryCommand: maintenanceRetryCommand,
3807
+ });
3808
+ }
3809
+ const wait = {
3810
+ enabled: shouldWaitForMutation(input, interaction),
3811
+ timeout: shouldWaitForMutation(input, interaction) ? (text(input.timeout) ?? "10m") : null,
3812
+ };
3813
+ const data = {
3814
+ operation: destructive ? "reset" : "reboot",
3815
+ request_id: idempotencyKey,
3816
+ packet_id: text(response.packet_id),
3817
+ erased_local_data: destructive,
3818
+ preserved: {
3819
+ external_volume_relationship: true,
3820
+ network_relationship: true,
3821
+ ssh_keys: true,
3822
+ },
3823
+ wait,
3824
+ };
3825
+ const resume = { kind: "machine", ref: machineId(machine) };
3826
+ if (!wait.enabled) {
3827
+ return interaction.result({
3828
+ data: { ...data, machine: projectMachine(machine) },
3829
+ outcome: {
3830
+ class: "working",
3831
+ phase: destructive ? "reset_accepted" : "reboot_accepted",
3832
+ },
3833
+ resume,
3834
+ presentation: {
3835
+ lines: [`Machine ${destructive ? "reset" : "reboot"} accepted.`],
3836
+ next: "ornn machines wait --guided",
3837
+ },
3838
+ });
3839
+ }
3840
+ const waiting = {
3841
+ ...record(machine),
3842
+ state: destructive ? "resetting" : "rebooting",
3843
+ status: destructive ? "resetting" : "rebooting",
3844
+ ssh_endpoint: null,
3845
+ };
3846
+ let waitOutcome;
3847
+ try {
3848
+ waitOutcome = await waitForMachine(
3849
+ context,
3850
+ interaction,
3851
+ waiting,
3852
+ wait.timeout,
3853
+ `machines ${command}`
3854
+ );
3855
+ } catch (error) {
3856
+ return acceptedWaitFailure(interaction, error, {
3857
+ data: { ...data, machine: projectMachine(waiting) },
3858
+ label: `machine ${destructive ? "reset" : "reboot"}`,
3859
+ phase: destructive ? "reset_accepted" : "reboot_accepted",
3860
+ resume,
3861
+ retryCommand: `ornn machines wait ${machineId(waiting)} --timeout ${wait.timeout}`,
3862
+ });
3863
+ }
3864
+ const failedRecoveryCommand = [
3865
+ `ornn machines ${command}`,
3866
+ shellQuote(machineId(waitOutcome.value)),
3867
+ destructive ? "--erase-local-data" : null,
3868
+ "--yes",
3869
+ `--idempotency-key ${shellQuote(interaction.idempotencyKey())}`,
3870
+ ]
3871
+ .filter(Boolean)
3872
+ .join(" ");
3873
+ return waitExecution(interaction, waitOutcome.value, waitOutcome, {
3874
+ acceptedPhase: destructive ? "reset_accepted" : "reboot_accepted",
3875
+ data,
3876
+ recoveryCommand: failedRecoveryCommand,
3877
+ successLines: [
3878
+ `Machine ${destructive ? "reset" : "reboot"} completed; SSH is ready: ${machineHostname(waitOutcome.value) || machineId(waitOutcome.value)}`,
3879
+ ],
3880
+ });
3881
+ }
3882
+
3883
+ export async function executeMachineCommand(command, input, context, interaction) {
3884
+ if (command === "list") return listMachines(input, context, interaction);
3885
+ if (command === "show") return showMachine(input, context, interaction);
3886
+ if (command === "wait") return waitMachine(input, context, interaction);
3887
+ if (command === "switch") return switchMachine(input, context, interaction);
3888
+ if (command === "reboot" || command === "reset") {
3889
+ return maintainMachine(command, input, context, interaction);
3890
+ }
3891
+ throw new Error(`Unsupported machine command: ${command}`);
3892
+ }
3893
+
3894
+ function cursorPage(items, input, interaction) {
3895
+ const limit = Number.isSafeInteger(input.limit) ? input.limit : 50;
3896
+ const cursor = text(input.cursor);
3897
+ let offset = 0;
3898
+ if (cursor) {
3899
+ let after;
3900
+ try {
3901
+ if (!/^[A-Za-z0-9_-]+$/.test(cursor)) throw new Error("invalid cursor");
3902
+ const decoded = record(JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")));
3903
+ if (decoded.version !== 1) throw new Error("unsupported cursor");
3904
+ after = text(decoded.after);
3905
+ } catch {
3906
+ interaction.usage("The SSH-key cursor is invalid or stale.", {
3907
+ code: "invalid_cursor",
3908
+ field: "cursor",
3909
+ });
3910
+ }
3911
+ const index = items.findIndex((item) => resourceId(item) === after);
3912
+ if (index === -1)
3913
+ interaction.usage("The SSH-key cursor is invalid or stale.", {
3914
+ code: "invalid_cursor",
3915
+ field: "cursor",
3916
+ });
3917
+ offset = index + 1;
3918
+ }
3919
+ const pageItems = items.slice(offset, offset + limit);
3920
+ const last = pageItems.at(-1);
3921
+ return {
3922
+ items: pageItems,
3923
+ page: {
3924
+ limit,
3925
+ returned: pageItems.length,
3926
+ next_cursor:
3927
+ offset + limit < items.length && last
3928
+ ? Buffer.from(JSON.stringify({ version: 1, after: resourceId(last) })).toString(
3929
+ "base64url"
3930
+ )
3931
+ : null,
3932
+ },
3933
+ };
3934
+ }
3935
+
3936
+ function keyLines(keys, heading) {
3937
+ return [
3938
+ heading,
3939
+ ...(keys.length
3940
+ ? keys.map((key) =>
3941
+ ` ${text(key.label) ?? "SSH key"} (${resourceId(key)}) ${text(key.fingerprint) ?? ""}`.trimEnd()
3942
+ )
3943
+ : [" none"]),
3944
+ ];
3945
+ }
3946
+
3947
+ async function resolveExistingKey(
3948
+ context,
3949
+ input,
3950
+ interaction,
3951
+ { absentCanonicalIsSuccess = false } = {}
3952
+ ) {
3953
+ const reference = text(input.keyId) ?? text(input.key) ?? text(input.$positionals?.[0]);
3954
+ if (!reference)
3955
+ interaction.usage("An SSH-key reference is required.", { code: "missing_input", field: "key" });
3956
+ if (interaction.machine && !CANONICAL_ID.test(reference)) {
3957
+ interaction.usage("Automation must use a canonical SSH-key identifier.", {
3958
+ code: "invalid_reference",
3959
+ field: "key",
3960
+ });
3961
+ }
3962
+ const matches = (await fetchOrganizationKeys(context)).filter(
3963
+ (key) =>
3964
+ resourceId(key) === reference || (!interaction.machine && text(key.label) === reference)
3965
+ );
3966
+ if (!matches.length && absentCanonicalIsSuccess && CANONICAL_ID.test(reference)) return null;
3967
+ if (matches.length !== 1) {
3968
+ interaction.error(
3969
+ matches.length
3970
+ ? `More than one SSH key has the exact label "${reference}".`
3971
+ : `SSH key not found: ${reference}.`,
3972
+ { code: matches.length ? "ssh_key_ambiguous" : "ssh_key_not_found" }
3973
+ );
3974
+ }
3975
+ return matches[0];
3976
+ }
3977
+
3978
+ function reservationPosition(input, interaction) {
3979
+ const [reservationId] = positional(
3980
+ input,
3981
+ 1,
3982
+ "ornn ssh-keys assignments <list|assign|sync|status> <canonical-reservation-id>",
3983
+ interaction
3984
+ );
3985
+ return requireCanonical(interaction, reservationId, "reservation", "Capacity reservation");
3986
+ }
3987
+
3988
+ export async function executeSshKeyCommand(command, input, context, interaction) {
3989
+ if (command === "list") {
3990
+ positional(input, 0, "ornn ssh-keys list [--limit <1-500>] [--cursor <cursor>]", interaction);
3991
+ const keys = fetchOrganizationKeys(context).then((rows) =>
3992
+ rows.sort((left, right) => String(resourceId(left)).localeCompare(String(resourceId(right))))
3993
+ );
3994
+ const page = cursorPage(await keys, input, interaction);
3995
+ return interaction.result({
3996
+ data: { ssh_keys: page.items.map(projectKey) },
3997
+ page: page.page,
3998
+ presentation: { lines: keyLines(page.items, "Organization SSH keys") },
3999
+ });
4000
+ }
4001
+
4002
+ if (command === "add") {
4003
+ positional(input, 0, "ornn ssh-keys add --public-key-file <path> --label <label>", interaction);
4004
+ machineMutationPreflight(input, interaction, { requireYes: false });
4005
+ if (interaction.machine && !text(input.publicKey) && !text(input.publicKeyFile)) {
4006
+ interaction.usage("--public-key or --public-key-file is required for automation.", {
4007
+ code: "missing_input",
4008
+ field: "publicKey",
4009
+ });
4010
+ }
4011
+ if (interaction.machine && !text(input.label)) {
4012
+ interaction.usage("--label is required when adding an SSH key in automation.", {
4013
+ code: "missing_input",
4014
+ field: "label",
4015
+ });
4016
+ }
4017
+ input = await normalizePublicKeyInput(input);
4018
+ const plan = await keyPlanFromInput(context, input, interaction);
4019
+ if (plan.kind === "existing") {
4020
+ return interaction.result({
4021
+ data: { ssh_key: projectKey(plan.key), replayed: true },
4022
+ presentation: {
4023
+ lines: [`SSH key already exists: ${text(plan.key.label) ?? resourceId(plan.key)}`],
4024
+ },
4025
+ });
4026
+ }
4027
+ const key = await materializeKey(
4028
+ context,
4029
+ plan,
4030
+ interaction,
4031
+ text(input.idempotencyKey) ?? interaction.idempotencyKey()
4032
+ );
4033
+ return interaction.result({
4034
+ data: { ssh_key: projectKey(key), replayed: false },
4035
+ presentation: { lines: [`SSH key added: ${text(key.label) ?? resourceId(key)}`] },
4036
+ });
4037
+ }
4038
+
4039
+ if (command === "delete") {
4040
+ const values = positional(
4041
+ input,
4042
+ 1,
4043
+ "ornn ssh-keys delete <exact-key-id-or-label>",
4044
+ interaction
4045
+ );
4046
+ machineMutationPreflight(input, interaction);
4047
+ const key = await resolveExistingKey(context, { ...input, $positionals: values }, interaction, {
4048
+ absentCanonicalIsSuccess: interaction.machine,
4049
+ });
4050
+ if (!key) {
4051
+ return interaction.result({
4052
+ data: {
4053
+ replayed: true,
4054
+ ssh_key: { fingerprint: null, id: values[0], label: null, status: "revoked" },
4055
+ },
4056
+ outcome: { class: "ended", phase: "deleted" },
4057
+ presentation: { lines: [`SSH key is already absent: ${values[0]}`] },
4058
+ });
4059
+ }
4060
+ const accepted = await interaction.review({
4061
+ title: "Delete SSH key",
4062
+ terms: [
4063
+ { label: "SSH key", value: `${text(key.label) ?? "SSH key"} (${resourceId(key)})` },
4064
+ {
4065
+ label: "Consequence",
4066
+ value: "Future machine access using this organization key may end",
4067
+ },
4068
+ ],
4069
+ prompt: "Delete this SSH key?",
4070
+ });
4071
+ if (!accepted) {
4072
+ return interaction.result({
4073
+ errors: [{ code: "declined", message: "SSH-key deletion declined.", retryable: false }],
4074
+ exitCode: 1,
4075
+ outcome: { class: "ended", phase: "declined" },
4076
+ presentation: { lines: ["SSH-key deletion declined."] },
4077
+ });
4078
+ }
4079
+ await context.fetch(
4080
+ `/v1/workflows/compute/nodes/organizations/me/ssh-keys/${encodeURIComponent(resourceId(key))}`,
4081
+ {
4082
+ method: "DELETE",
4083
+ headers: { "Idempotency-Key": text(input.idempotencyKey) ?? interaction.idempotencyKey() },
4084
+ }
4085
+ );
4086
+ return interaction.result({
4087
+ data: { ssh_key: projectKey(key) },
4088
+ outcome: { class: "ended", phase: "deleted" },
4089
+ presentation: { lines: [`SSH key deleted: ${text(key.label) ?? resourceId(key)}`] },
4090
+ });
4091
+ }
4092
+
4093
+ const reservationId = reservationPosition(input, interaction);
4094
+ if (command === "assignments.list") {
4095
+ const keys = (await fetchAssignedKeys(context, reservationId)).sort((left, right) =>
4096
+ String(resourceId(left)).localeCompare(String(resourceId(right)))
4097
+ );
4098
+ const page = cursorPage(keys, input, interaction);
4099
+ return interaction.result({
4100
+ data: { reservation_id: reservationId, ssh_keys: page.items.map(projectKey) },
4101
+ page: page.page,
4102
+ presentation: { lines: keyLines(page.items, `SSH keys assigned to ${reservationId}`) },
4103
+ });
4104
+ }
4105
+
4106
+ if (command === "assignments.status") {
4107
+ const [keys, machines] = await Promise.all([
4108
+ fetchAssignedKeys(context, reservationId),
4109
+ fetchMachines(context, reservationId),
4110
+ ]);
4111
+ keys.sort((left, right) => String(resourceId(left)).localeCompare(String(resourceId(right))));
4112
+ const page = cursorPage(keys, input, interaction);
4113
+ const status = assignmentStatus(reservationId, page.items, machines);
4114
+ const lines = [`SSH-key delivery for ${reservationId}`];
4115
+ for (const key of status.keys) {
4116
+ lines.push(` ${key.label ?? key.id}`);
4117
+ for (const machine of key.machines)
4118
+ lines.push(` ${machine.hostname ?? machine.machine_id}: ${machine.status}`);
4119
+ }
4120
+ if (!status.keys.length) lines.push(" no assigned SSH keys");
4121
+ return interaction.result({ data: status, page: page.page, presentation: { lines } });
4122
+ }
4123
+
4124
+ machineMutationPreflight(input, interaction);
4125
+ const idempotencyKey = text(input.idempotencyKey) ?? interaction.idempotencyKey();
4126
+ if (command === "assignments.assign") {
4127
+ const key = await resolveExistingKey(context, { ...input, $positionals: [] }, interaction);
4128
+ const accepted = await interaction.review({
4129
+ title: "Assign SSH key",
4130
+ terms: [
4131
+ { label: "Capacity reservation", value: reservationId },
4132
+ { label: "SSH key", value: `${text(key.label) ?? "SSH key"} (${resourceId(key)})` },
4133
+ {
4134
+ label: "Delivery",
4135
+ value: "Assignment is durable; installation on each machine may be delayed",
4136
+ },
4137
+ ],
4138
+ prompt: "Assign this SSH key?",
4139
+ });
4140
+ if (!accepted) {
4141
+ return interaction.result({
4142
+ errors: [{ code: "declined", message: "SSH-key assignment declined.", retryable: false }],
4143
+ exitCode: 1,
4144
+ outcome: { class: "ended", phase: "declined" },
4145
+ presentation: { lines: ["SSH-key assignment declined."] },
4146
+ });
4147
+ }
4148
+ const assignment = await ensureAssignment(
4149
+ context,
4150
+ reservationId,
4151
+ key,
4152
+ idempotencyKey,
4153
+ interaction
4154
+ );
4155
+ const machines = await fetchMachines(context, reservationId);
4156
+ return interaction.result({
4157
+ data: {
4158
+ ...assignmentStatus(reservationId, [assignment.key], machines),
4159
+ replayed: !assignment.created,
4160
+ },
4161
+ outcome: {
4162
+ class: machines.length ? "working" : "ready",
4163
+ phase: machines.length ? "delivery_pending" : "associated",
4164
+ },
4165
+ presentation: {
4166
+ lines: [
4167
+ `SSH key ${assignment.created ? "assigned" : "already assigned"}: ${text(key.label) ?? resourceId(key)}`,
4168
+ ],
4169
+ next: `ornn ssh-keys assignments status ${reservationId}`,
4170
+ },
4171
+ });
4172
+ }
4173
+
4174
+ if (command === "assignments.sync") {
4175
+ const [keys, machines] = await Promise.all([
4176
+ fetchAssignedKeys(context, reservationId),
4177
+ fetchMachines(context, reservationId),
4178
+ ]);
4179
+ if (!keys.length) {
4180
+ interaction.error("No SSH keys are assigned to this capacity reservation.", {
4181
+ code: "ssh_key_assignment_missing",
4182
+ });
4183
+ }
4184
+ const accepted = await interaction.review({
4185
+ title: "Synchronize SSH keys",
4186
+ terms: [
4187
+ { label: "Capacity reservation", value: reservationId },
4188
+ { label: "SSH keys", value: String(keys.length) },
4189
+ { label: "Active machines", value: String(machines.length) },
4190
+ { label: "Delivery", value: "Queued delivery remains distinct from installed state" },
4191
+ ],
4192
+ prompt: "Queue SSH-key synchronization?",
4193
+ });
4194
+ if (!accepted) {
4195
+ return interaction.result({
4196
+ errors: [
4197
+ { code: "declined", message: "SSH-key synchronization declined.", retryable: false },
4198
+ ],
4199
+ exitCode: 1,
4200
+ outcome: { class: "ended", phase: "declined" },
4201
+ presentation: { lines: ["SSH-key synchronization declined."] },
4202
+ });
4203
+ }
4204
+ const deliveries = new Map();
4205
+ for (const machine of machines) {
4206
+ const id = machineId(machine);
4207
+ const requestId = `${idempotencyKey}:${id}`;
4208
+ try {
4209
+ const queued = record(
4210
+ await context.fetch(`/v1/workflows/compute/nodes/${encodeURIComponent(id)}/push-keys`, {
4211
+ method: "POST",
4212
+ body: {
4213
+ request_id: requestId,
4214
+ ssh_key_ids: keys.map(resourceId),
4215
+ },
4216
+ })
4217
+ );
4218
+ deliveries.set(id, {
4219
+ failure_reason: null,
4220
+ packet_id: text(queued.packet_id),
4221
+ request_id: requestId,
4222
+ status: "queued",
4223
+ });
4224
+ } catch {
4225
+ deliveries.set(id, {
4226
+ failure_reason: "SSH-key delivery could not be queued.",
4227
+ packet_id: null,
4228
+ request_id: requestId,
4229
+ status: "failed",
4230
+ });
4231
+ }
4232
+ }
4233
+ const status = assignmentStatus(reservationId, keys, machines);
4234
+ for (const key of status.keys) {
4235
+ for (const machine of key.machines)
4236
+ Object.assign(machine, deliveries.get(machine.machine_id));
4237
+ }
4238
+ const failed = [...deliveries.values()].filter(
4239
+ (delivery) => delivery.status === "failed"
4240
+ ).length;
4241
+ const queued = deliveries.size - failed;
4242
+ return interaction.result({
4243
+ data: status,
4244
+ errors: failed
4245
+ ? [
4246
+ {
4247
+ code: "ssh_key_delivery_partial",
4248
+ message: `SSH-key delivery could not be queued for ${failed} machine${failed === 1 ? "" : "s"}.`,
4249
+ retryable: true,
4250
+ },
4251
+ ]
4252
+ : [],
4253
+ exitCode: failed ? 4 : 0,
4254
+ outcome: failed
4255
+ ? {
4256
+ class: queued ? "partial" : "failed",
4257
+ phase: queued ? "delivery_partial" : "delivery_failed",
4258
+ }
4259
+ : {
4260
+ class: machines.length ? "working" : "ready",
4261
+ phase: machines.length ? "delivery_queued" : "associated",
4262
+ },
4263
+ presentation: {
4264
+ lines: [
4265
+ `SSH-key synchronization queued for ${queued} machine${queued === 1 ? "" : "s"}${failed ? `; ${failed} failed` : ""}.`,
4266
+ ],
4267
+ next: `ornn ssh-keys assignments status ${reservationId}`,
4268
+ },
4269
+ });
4270
+ }
4271
+
4272
+ throw new Error(`Unsupported SSH-key command: ${command}`);
4273
+ }