@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,1146 @@
1
+ import { CliApiError, computeEndpoint } from "./api-client.mjs";
2
+ import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
3
+ import { resolveVolume, safeVolume } from "./storage-commands.mjs";
4
+
5
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
+ const DEFAULT_LIMIT = 50;
7
+ const MAX_LIMIT = 500;
8
+ const DEFAULT_MOUNT_TIMEOUT = "30m";
9
+ const TERMINAL_ATTACHMENT_PHASES = new Set(["detached", "ended", "failed"]);
10
+
11
+ function record(value) {
12
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
13
+ }
14
+
15
+ function text(value) {
16
+ return typeof value === "string" && value.trim() ? value.trim() : null;
17
+ }
18
+
19
+ function integer(value) {
20
+ const parsed = typeof value === "number" ? value : Number(value);
21
+ return Number.isSafeInteger(parsed) ? parsed : null;
22
+ }
23
+
24
+ function canonical(value, field, interaction) {
25
+ const candidate = text(value);
26
+ if (!candidate || !UUID.test(candidate)) {
27
+ interaction.usage(`--${field} must be one canonical identifier.`, {
28
+ code: "invalid_reference",
29
+ field,
30
+ });
31
+ }
32
+ return candidate;
33
+ }
34
+
35
+ function positionals(input, expected, usage, interaction) {
36
+ const values = Array.isArray(input.$positionals) ? input.$positionals : [];
37
+ if (values.length !== expected) interaction.usage(`Usage: ${usage}`, { code: "usage_error" });
38
+ return values;
39
+ }
40
+
41
+ function required(value, field, interaction) {
42
+ const candidate = text(value);
43
+ if (!candidate) interaction.usage(`--${field} is required.`, { code: "missing_input", field });
44
+ return candidate;
45
+ }
46
+
47
+ function accessMode(value, interaction) {
48
+ const candidate = required(value, "access", interaction).toLowerCase();
49
+ if (candidate !== "read-only" && candidate !== "read-write") {
50
+ interaction.usage("--access must be read-only or read-write.", {
51
+ code: "invalid_access_mode",
52
+ field: "access",
53
+ });
54
+ }
55
+ return candidate;
56
+ }
57
+
58
+ function positiveInteger(value, field, interaction) {
59
+ const parsed = integer(value);
60
+ if (parsed === null || parsed < 1) {
61
+ interaction.usage(`--${field} must be a positive integer.`, {
62
+ code: "invalid_input",
63
+ field,
64
+ });
65
+ }
66
+ return parsed;
67
+ }
68
+
69
+ function timeoutMilliseconds(value, interaction, fallback = DEFAULT_MOUNT_TIMEOUT) {
70
+ const candidate = text(value) ?? fallback;
71
+ const match = /^(\d+)(ms|s|m|h)$/.exec(candidate);
72
+ if (!match) {
73
+ interaction.usage("--timeout must be a positive duration such as 30s, 10m, or 1h.", {
74
+ code: "invalid_timeout",
75
+ field: "timeout",
76
+ });
77
+ }
78
+ const count = Number(match[1]);
79
+ const scale = { ms: 1, s: 1_000, m: 60_000, h: 3_600_000 }[match[2]];
80
+ if (!Number.isSafeInteger(count) || count < 1 || count * scale > 2_147_483_647) {
81
+ interaction.usage("--timeout is outside the supported range.", {
82
+ code: "invalid_timeout",
83
+ field: "timeout",
84
+ });
85
+ }
86
+ return count * scale;
87
+ }
88
+
89
+ function requireMachineMutation(input, interaction) {
90
+ if (!interaction.machine) return;
91
+ if (!text(input.idempotencyKey)) {
92
+ interaction.usage("Machine mutations require --idempotency-key <caller-stable-key>.", {
93
+ code: "missing_idempotency_key",
94
+ field: "idempotency-key",
95
+ });
96
+ }
97
+ }
98
+
99
+ function idempotencyHeaders(input) {
100
+ const key = text(input.idempotencyKey);
101
+ return key ? { "Idempotency-Key": key } : undefined;
102
+ }
103
+
104
+ function ownerCode(error) {
105
+ const top = record(error?.detail);
106
+ const detail = record(top.detail ?? top.error ?? top);
107
+ const code = text(detail.code);
108
+ return code && /^[a-z][a-z0-9_]*$/.test(code) ? code : null;
109
+ }
110
+
111
+ function safeFailure(
112
+ interaction,
113
+ error,
114
+ message,
115
+ { action, fallbackCode = "mount_operation_failed" } = {}
116
+ ) {
117
+ if (error?.name === "InteractionError" || error?.name === "StorageCommandError") throw error;
118
+ if (error instanceof CliApiError && (error.status === 401 || error.status === 403)) throw error;
119
+ const code =
120
+ ownerCode(error) ??
121
+ (error instanceof CliApiError && (error.status === undefined || error.status >= 500)
122
+ ? "service_unavailable"
123
+ : fallbackCode);
124
+ interaction.error(message, {
125
+ action,
126
+ code,
127
+ exitCode: code === "service_unavailable" ? 6 : 1,
128
+ retryable: code === "service_unavailable",
129
+ });
130
+ }
131
+
132
+ function reservationAttachmentPath(reservationId) {
133
+ return computeEndpoint(
134
+ `/nodes/reservations/${encodeURIComponent(reservationId)}/storage-attachment`
135
+ );
136
+ }
137
+
138
+ function reservationDeploymentPath(reservationId) {
139
+ return computeEndpoint(
140
+ `/nodes/reservations/${encodeURIComponent(reservationId)}/storage-deployment`
141
+ );
142
+ }
143
+
144
+ function reservationFilesystemPath(reservationId) {
145
+ return computeEndpoint(
146
+ `/nodes/reservations/${encodeURIComponent(reservationId)}/filesystem-deployment`
147
+ );
148
+ }
149
+
150
+ function stableAttachmentPhase(value) {
151
+ const state = text(value)?.toLowerCase() ?? "pending";
152
+ if (["attached", "ready"].includes(state)) return state;
153
+ if (["failed", "detached", "detaching"].includes(state)) return state;
154
+ if (["pre_positioning", "provisioning", "mounting", "accepted"].includes(state)) return state;
155
+ return "pending";
156
+ }
157
+
158
+ function stableMachineMountPhase(value) {
159
+ const state = text(value)?.toLowerCase() ?? "pending";
160
+ if (state === "mounted") return "mounted";
161
+ if (["mount_failed", "mount_failed_unverified", "failed", "error"].includes(state))
162
+ return "failed";
163
+ if (["release_pending", "releasing", "detached"].includes(state)) return "detaching";
164
+ if (["reserved", "provisioning", "mount_pending", "mounting", "pending"].includes(state))
165
+ return "pending";
166
+ return "unknown";
167
+ }
168
+
169
+ function safeMountPath(value) {
170
+ const candidate = text(value);
171
+ return candidate && candidate.startsWith("/") && !/[\p{Cc}]/u.test(candidate) ? candidate : null;
172
+ }
173
+
174
+ export function projectVolumeMount(payload, requestedReservationId) {
175
+ const source = record(payload);
176
+ const attachment = record(
177
+ source.attachment ??
178
+ source.filesystem_deployment ??
179
+ source.deployment ??
180
+ (source.id ? source : null)
181
+ );
182
+ if (!text(attachment.id)) return null;
183
+ const drive = record(attachment.drive ?? attachment.volume);
184
+ const filesystem = record(drive.nfs_backend ?? attachment.filesystem);
185
+ const rawMounts = Array.isArray(drive.active_mounts)
186
+ ? drive.active_mounts
187
+ : drive.active_mount
188
+ ? [drive.active_mount]
189
+ : [];
190
+ const machines = rawMounts.map((value) => {
191
+ const mount = record(value);
192
+ const phase = stableMachineMountPhase(mount.state);
193
+ return {
194
+ machine_id: text(mount.node_id ?? mount.instance_id),
195
+ machine_label: text(mount.node_label),
196
+ reservation_id: text(mount.reservation_id) ?? requestedReservationId,
197
+ phase,
198
+ ready: phase === "mounted",
199
+ mount_path: safeMountPath(mount.mount_path ?? attachment.mount_path),
200
+ issue: phase === "failed" ? "The machine did not confirm this volume mount." : null,
201
+ };
202
+ });
203
+ const expected = Math.max(integer(record(attachment.scope).node_count) ?? 0, machines.length);
204
+ const ready = machines.filter((machine) => machine.ready).length;
205
+ const failed = machines.filter((machine) => machine.phase === "failed").length;
206
+ const phase = stableAttachmentPhase(attachment.state);
207
+ const lifecycleClass =
208
+ ready === expected && expected > 0 && ["attached", "ready"].includes(phase)
209
+ ? "ready"
210
+ : failed > 0 || phase === "failed"
211
+ ? "partial"
212
+ : phase === "detached"
213
+ ? "ended"
214
+ : "working";
215
+ return {
216
+ id: text(attachment.id),
217
+ reservation_id: requestedReservationId ?? text(attachment.reservation_id),
218
+ volume: {
219
+ id: text(attachment.drive_id ?? drive.id),
220
+ name: text(drive.name),
221
+ },
222
+ mount_path: safeMountPath(attachment.mount_path),
223
+ access: ["read-only", "read-write"].includes(text(attachment.access_mode)?.toLowerCase())
224
+ ? text(attachment.access_mode).toLowerCase()
225
+ : null,
226
+ scope: {
227
+ all_nodes:
228
+ record(attachment.scope).all_nodes === true ||
229
+ record(attachment.scope).origin === "cluster",
230
+ machine_count: expected,
231
+ },
232
+ lifecycle: { class: lifecycleClass, phase },
233
+ filesystem: {
234
+ performance_tier: text(filesystem.performance_tier ?? attachment.performance_tier),
235
+ capacity_gib: integer(filesystem.capacity_gib ?? attachment.capacity_gib),
236
+ phase: text(filesystem.state)?.toLowerCase() ?? null,
237
+ mount_ready: filesystem.mount_ready === true,
238
+ },
239
+ readiness: {
240
+ expected,
241
+ ready,
242
+ failed,
243
+ pending: Math.max(0, expected - ready - failed),
244
+ },
245
+ machines,
246
+ };
247
+ }
248
+
249
+ function mountSatisfied(mount, goal) {
250
+ return Boolean(
251
+ mount &&
252
+ !["detached", "detaching", "ended"].includes(mount.lifecycle.phase) &&
253
+ mount.volume.id === goal.volumeId &&
254
+ mount.access === goal.access
255
+ );
256
+ }
257
+
258
+ function filesystemSatisfied(mount, performanceTier, capacityGib) {
259
+ return Boolean(
260
+ mount &&
261
+ mount.filesystem.performance_tier === performanceTier &&
262
+ mount.filesystem.capacity_gib === capacityGib
263
+ );
264
+ }
265
+
266
+ function mountReady(mount) {
267
+ return Boolean(
268
+ mount && mount.readiness.expected > 0 && mount.readiness.ready === mount.readiness.expected
269
+ );
270
+ }
271
+
272
+ function mountTerminal(mount) {
273
+ return Boolean(
274
+ mount && (mount.readiness.failed > 0 || TERMINAL_ATTACHMENT_PHASES.has(mount.lifecycle.phase))
275
+ );
276
+ }
277
+
278
+ export function resolvedMountPath(requestedPath, machines) {
279
+ const paths = new Set(
280
+ (Array.isArray(machines) ? machines : [])
281
+ .map((machine) => text(record(machine).mount_path))
282
+ .filter(Boolean)
283
+ );
284
+ if (paths.size === 1) return [...paths][0];
285
+ if (paths.size === 0) return text(requestedPath);
286
+ return null;
287
+ }
288
+
289
+ function mountLines(mount, reservationId) {
290
+ if (!mount) return [`Capacity reservation ${reservationId} has no volume mount.`];
291
+ const lines = [
292
+ `Volume mount ${mount.id}`,
293
+ ` Capacity reservation: ${mount.reservation_id}`,
294
+ ` Volume: ${mount.volume.name ?? "Managed volume"} (${mount.volume.id})`,
295
+ ` Path: ${resolvedMountPath(mount.mount_path, mount.machines) ?? "per machine"}`,
296
+ ` Access: ${mount.access ?? "pending"}`,
297
+ ` Ready machines: ${mount.readiness.ready} of ${mount.readiness.expected}`,
298
+ ];
299
+ for (const machine of mount.machines) {
300
+ lines.push(
301
+ ` ${machine.machine_label ?? machine.machine_id ?? "machine"}: ${machine.phase}${machine.mount_path ? ` at ${machine.mount_path}` : ""}`
302
+ );
303
+ }
304
+ return lines;
305
+ }
306
+
307
+ function mountResult(interaction, mount, reservationId, { replayed = false } = {}) {
308
+ if (!mount) {
309
+ return interaction.result({
310
+ data: { reservation_id: reservationId, volume_mount: null, replayed },
311
+ outcome: { class: "ended", phase: "not_attached" },
312
+ presentation: { lines: mountLines(null, reservationId) },
313
+ });
314
+ }
315
+ const partial = mount.readiness.failed > 0 || mount.lifecycle.class === "partial";
316
+ return interaction.result({
317
+ data: { reservation_id: reservationId, volume_mount: mount, replayed },
318
+ errors: partial
319
+ ? [
320
+ {
321
+ code: "volume_mount_partial",
322
+ message: "One or more machines did not confirm the requested volume mount.",
323
+ retryable: true,
324
+ },
325
+ ]
326
+ : [],
327
+ exitCode: partial ? 4 : 0,
328
+ outcome: partial ? { class: "partial", phase: "machine_mounts" } : mount.lifecycle,
329
+ resume: { kind: "volume_mount", ref: reservationId },
330
+ action: partial
331
+ ? {
332
+ command: `ornn volume-mounts wait ${reservationId} --timeout ${DEFAULT_MOUNT_TIMEOUT}`,
333
+ description: "Resume the exact per-machine mount wait.",
334
+ type: "command",
335
+ }
336
+ : null,
337
+ presentation: {
338
+ lines: mountLines(mount, reservationId),
339
+ ...(partial
340
+ ? { next: `ornn volume-mounts wait ${reservationId} --timeout ${DEFAULT_MOUNT_TIMEOUT}` }
341
+ : {}),
342
+ },
343
+ });
344
+ }
345
+
346
+ // The attachment payload carries no reservation machine count, so a mount
347
+ // still short of its per-machine rows has no denominator of its own.
348
+ async function reservationMachineCount(runtime, reservationId, signal) {
349
+ const cache = (runtime.machineCounts ??= new Map());
350
+ if (cache.has(reservationId)) return cache.get(reservationId);
351
+ let count = 0;
352
+ try {
353
+ const rows = candidateRows(
354
+ await runtime.fetch("/v1/cli/storage-targets", {
355
+ method: "GET",
356
+ ...(signal ? { signal } : {}),
357
+ })
358
+ );
359
+ const row = rows.find(
360
+ (candidate) =>
361
+ candidate.id === reservationId || candidate.reservation_ids.includes(reservationId)
362
+ );
363
+ count = row?.machine_count ?? 0;
364
+ } catch {
365
+ // A missing denominator must never fail the mount it describes.
366
+ }
367
+ cache.set(reservationId, count);
368
+ return count;
369
+ }
370
+
371
+ async function withMachineCount(runtime, mount, reservationId, signal) {
372
+ if (!mount || mount.readiness.expected > 0) return mount;
373
+ const expected = await reservationMachineCount(runtime, reservationId, signal);
374
+ if (expected < 1) return mount;
375
+ mount.readiness.expected = expected;
376
+ mount.readiness.pending = Math.max(0, expected - mount.readiness.ready - mount.readiness.failed);
377
+ mount.scope.machine_count = expected;
378
+ return mount;
379
+ }
380
+
381
+ async function fetchMount(runtime, reservationId, { preserveFailure = false, signal } = {}) {
382
+ try {
383
+ const payload = await runtime.fetch(reservationAttachmentPath(reservationId), {
384
+ method: "GET",
385
+ ...(signal ? { signal } : {}),
386
+ });
387
+ return withMachineCount(
388
+ runtime,
389
+ projectVolumeMount(payload, reservationId),
390
+ reservationId,
391
+ signal
392
+ );
393
+ } catch (error) {
394
+ if (error instanceof CliApiError && error.status === 404) return null;
395
+ if (preserveFailure) throw error;
396
+ safeFailure(
397
+ runtime.interaction,
398
+ error,
399
+ "The volume-mount relationship could not be loaded safely."
400
+ );
401
+ }
402
+ }
403
+
404
+ function shouldWait(input, interaction) {
405
+ if (input.noWait && input.wait) {
406
+ interaction.usage("Choose only one of --wait or --no-wait.", { code: "usage_error" });
407
+ }
408
+ if (interaction.machine && input.wait && !text(input.timeout)) {
409
+ interaction.usage("Machine waiting requires --timeout <duration>.", {
410
+ code: "missing_timeout",
411
+ field: "timeout",
412
+ });
413
+ }
414
+ if (!input.wait && text(input.timeout)) {
415
+ interaction.usage("--timeout requires --wait for a mutation command.", {
416
+ code: "usage_error",
417
+ field: "timeout",
418
+ });
419
+ }
420
+ return input.noWait
421
+ ? false
422
+ : input.wait
423
+ ? true
424
+ : interaction.eligibleHuman && !interaction.machine;
425
+ }
426
+
427
+ async function waitForMount(input, runtime, reservationId, initial, command) {
428
+ const timeout = text(input.timeout) ?? DEFAULT_MOUNT_TIMEOUT;
429
+ const next = `ornn volume-mounts wait ${reservationId} --timeout ${timeout}`;
430
+ let outcome;
431
+ try {
432
+ outcome = await runtime.interaction.waitForGoal({
433
+ command,
434
+ initial,
435
+ timeoutMs: timeoutMilliseconds(timeout, runtime.interaction),
436
+ isReady: mountReady,
437
+ isTerminal: mountTerminal,
438
+ message: (mount, elapsedMs) =>
439
+ mountReady(mount)
440
+ ? `Every requested machine has mounted the volume.`
441
+ : `Volume mount has ${mount?.readiness.ready ?? 0} of ${mount?.readiness.expected ?? 0} machines ready (${Math.floor(elapsedMs / 1000)}s).`,
442
+ projectProgress: (mount) => ({ reservation_id: reservationId, volume_mount: mount }),
443
+ poll: ({ signal }) => fetchMount(runtime, reservationId, { preserveFailure: true, signal }),
444
+ });
445
+ } catch (error) {
446
+ const status = Number(error?.status);
447
+ if (!isRecoverableWaitFailure(error)) throw error;
448
+ const authorization = status === 401 || status === 403;
449
+ const authentication = status === 401;
450
+ const action = authorization
451
+ ? {
452
+ command: "ornn login",
453
+ description: "Restore an authorized session, then resume the exact accepted mount.",
454
+ type: "command",
455
+ }
456
+ : { command: next, description: "Retry the exact accepted mount wait.", type: "command" };
457
+ return runtime.interaction.result({
458
+ action,
459
+ data: {
460
+ remote_work_cancelled: false,
461
+ replayed: false,
462
+ reservation_id: reservationId,
463
+ volume_mount: initial,
464
+ },
465
+ errors: [
466
+ {
467
+ code: authentication
468
+ ? "authentication_required"
469
+ : authorization
470
+ ? "authorization_denied"
471
+ : "service_unavailable",
472
+ message: authorization
473
+ ? "The session could not continue waiting; the accepted remote mount continues."
474
+ : "The local wait lost contact with the service; the accepted remote mount continues.",
475
+ retryable: status !== 403,
476
+ resource_ref: reservationId,
477
+ },
478
+ ],
479
+ exitCode: authorization ? 5 : 6,
480
+ outcome: initial?.lifecycle ?? { class: "working", phase: "mounting" },
481
+ resume: { kind: "volume_mount", ref: reservationId },
482
+ presentation: {
483
+ lines: ["The accepted remote mount continues; no remote cancellation was sent."],
484
+ next: action.command,
485
+ },
486
+ });
487
+ }
488
+ if (outcome.kind === "ready" || outcome.kind === "terminal") {
489
+ return mountResult(runtime.interaction, outcome.value, reservationId);
490
+ }
491
+ const interrupted = outcome.kind === "interrupted";
492
+ const mount = outcome.value;
493
+ return runtime.interaction.result({
494
+ action: { command: next, description: "Resume the exact local mount wait.", type: "command" },
495
+ data: { reservation_id: reservationId, volume_mount: mount, replayed: false },
496
+ errors: [
497
+ {
498
+ code: interrupted ? "interrupted" : "wait_timeout",
499
+ message: interrupted
500
+ ? "Local waiting stopped; the remote mount continues."
501
+ : "Local waiting timed out; the remote mount continues.",
502
+ retryable: true,
503
+ },
504
+ ],
505
+ exitCode: interrupted ? 130 : 124,
506
+ outcome: mount?.lifecycle ?? { class: "working", phase: "mounting" },
507
+ resume: { kind: "volume_mount", ref: reservationId },
508
+ presentation: {
509
+ lines: [
510
+ ...mountLines(mount, reservationId),
511
+ interrupted
512
+ ? "Local waiting stopped; no remote cancellation was sent."
513
+ : "Local waiting timed out; no remote cancellation was sent.",
514
+ ],
515
+ next,
516
+ },
517
+ });
518
+ }
519
+
520
+ function candidateRows(payload) {
521
+ const source = record(payload);
522
+ const rows = Array.isArray(payload)
523
+ ? payload
524
+ : Array.isArray(source.targets)
525
+ ? source.targets
526
+ : [];
527
+ return rows
528
+ .flatMap((value) => {
529
+ const target = record(value);
530
+ const id = text(target.id);
531
+ if (!id) return [];
532
+ const reservationIds = Array.isArray(target.reservationIds ?? target.reservation_ids)
533
+ ? (target.reservationIds ?? target.reservation_ids).filter(
534
+ (item) => typeof item === "string"
535
+ )
536
+ : [];
537
+ return [
538
+ {
539
+ id,
540
+ label: machineWording(text(target.label)),
541
+ detail: text(target.detail),
542
+ facility: facilityLabel(target.facilityLabel ?? target.facility_label),
543
+ all_nodes: target.allNodes === true || target.all_nodes === true,
544
+ machine_count: integer(target.nodeCount ?? target.node_count) ?? 0,
545
+ reservation_ids: reservationIds,
546
+ },
547
+ ];
548
+ })
549
+ .sort((left, right) => (left.label ?? left.id).localeCompare(right.label ?? right.id));
550
+ }
551
+
552
+ function machineWording(value) {
553
+ return value?.replace(/\bnodes\b/g, "machines").replace(/\bnode\b/g, "machine") ?? null;
554
+ }
555
+
556
+ function facilityLabel(value) {
557
+ const candidate = text(value);
558
+ return candidate === "\u2014" ? null : candidate;
559
+ }
560
+
561
+ function cursorOffset(cursor, interaction) {
562
+ if (!text(cursor)) return 0;
563
+ try {
564
+ const decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
565
+ if (
566
+ decoded.kind === "volume-mount-candidates" &&
567
+ Number.isSafeInteger(decoded.offset) &&
568
+ decoded.offset >= 0
569
+ ) {
570
+ return decoded.offset;
571
+ }
572
+ } catch {}
573
+ interaction.usage("The volume-mount candidate cursor is invalid or stale.", {
574
+ code: "invalid_cursor",
575
+ field: "cursor",
576
+ });
577
+ }
578
+
579
+ function pageLimit(value, interaction) {
580
+ if (value === undefined) return DEFAULT_LIMIT;
581
+ const limit = integer(value);
582
+ if (limit === null || limit < 1 || limit > MAX_LIMIT) {
583
+ interaction.usage("--limit must be an integer from 1 through 500.", {
584
+ code: "invalid_limit",
585
+ field: "limit",
586
+ });
587
+ }
588
+ return limit;
589
+ }
590
+
591
+ async function candidates(input, runtime) {
592
+ positionals(
593
+ input,
594
+ 0,
595
+ "ornn volume-mounts candidates [--limit <1-500>] [--cursor <cursor>]",
596
+ runtime.interaction
597
+ );
598
+ const rows = candidateRows(await runtime.fetch("/v1/cli/storage-targets", { method: "GET" }));
599
+ const offset = cursorOffset(input.cursor, runtime.interaction);
600
+ const limit = pageLimit(input.limit, runtime.interaction);
601
+ if (offset > rows.length) {
602
+ runtime.interaction.usage("The volume-mount candidate cursor is invalid or stale.", {
603
+ code: "invalid_cursor",
604
+ field: "cursor",
605
+ });
606
+ }
607
+ const selected = rows.slice(offset, offset + limit);
608
+ const next =
609
+ offset + limit < rows.length
610
+ ? Buffer.from(
611
+ JSON.stringify({ kind: "volume-mount-candidates", offset: offset + limit })
612
+ ).toString("base64url")
613
+ : null;
614
+ return runtime.interaction.result({
615
+ data: { candidates: selected },
616
+ page: { limit, returned: selected.length, next_cursor: next },
617
+ outcome: { class: "ready", phase: selected.length ? "listed" : "empty" },
618
+ presentation: {
619
+ lines: selected.length
620
+ ? selected.map(
621
+ (target) =>
622
+ `${target.label ?? `${target.machine_count} machines`} (${target.id})${target.facility ? ` in ${target.facility}` : ""}`
623
+ )
624
+ : ["No volume-mount candidates found."],
625
+ ...(next ? { next: `ornn volume-mounts candidates --cursor ${next}` } : {}),
626
+ },
627
+ });
628
+ }
629
+
630
+ async function show(input, runtime, command) {
631
+ const [rawReservation] = positionals(
632
+ input,
633
+ 1,
634
+ `ornn ${command} <canonical-reservation-id>`,
635
+ runtime.interaction
636
+ );
637
+ const reservationId = canonical(rawReservation, "reservation", runtime.interaction);
638
+ return mountResult(runtime.interaction, await fetchMount(runtime, reservationId), reservationId);
639
+ }
640
+
641
+ export async function attachVolumeMount(input, runtime, { skipReview = false } = {}) {
642
+ const [rawReservation] = positionals(
643
+ input,
644
+ 1,
645
+ "ornn volume-mounts attach <canonical-reservation-id> --volume <exact-name-or-id> --access read-only|read-write",
646
+ runtime.interaction
647
+ );
648
+ requireMachineMutation(input, runtime.interaction);
649
+ const reservationId = canonical(rawReservation, "reservation", runtime.interaction);
650
+ if (
651
+ runtime.interaction.machine &&
652
+ !UUID.test(required(input.volume, "volume", runtime.interaction))
653
+ ) {
654
+ runtime.interaction.usage("Automation must use a canonical managed-volume identifier.", {
655
+ code: "invalid_reference",
656
+ field: "volume",
657
+ });
658
+ }
659
+ const access = accessMode(input.access, runtime.interaction);
660
+ const wait = shouldWait(input, runtime.interaction);
661
+ const volume = await resolveVolume(runtime, input.volume);
662
+ const projectedVolume = safeVolume(volume);
663
+ const goal = { volumeId: projectedVolume.id, access };
664
+ let current = await fetchMount(runtime, reservationId);
665
+ if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
666
+ if (mountSatisfied(current, goal)) {
667
+ return wait && !mountReady(current)
668
+ ? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
669
+ : mountResult(runtime.interaction, current, reservationId, { replayed: true });
670
+ }
671
+ if (current) {
672
+ runtime.interaction.error(
673
+ "This capacity reservation already has a different volume-mount relationship.",
674
+ {
675
+ action: {
676
+ command: `ornn volume-mounts detach ${reservationId}`,
677
+ description:
678
+ "Review and detach the existing relationship before choosing a different mount.",
679
+ type: "command",
680
+ },
681
+ code: "volume_mount_conflict",
682
+ }
683
+ );
684
+ }
685
+ if (!skipReview) {
686
+ const accepted = await runtime.interaction.review({
687
+ title: "Attach managed volume",
688
+ terms: [
689
+ { label: "Capacity reservation", value: reservationId },
690
+ { label: "Managed volume", value: `${projectedVolume.name} (${projectedVolume.id})` },
691
+ { label: "Access", value: access },
692
+ { label: "Machine scope", value: "Every machine in the reservation" },
693
+ {
694
+ label: "Volume lifecycle",
695
+ value: "The volume remains independent from this relationship",
696
+ },
697
+ ],
698
+ prompt: "Attach this volume?",
699
+ });
700
+ if (!accepted) {
701
+ return runtime.interaction.result({
702
+ errors: [{ code: "declined", message: "Volume attachment declined.", retryable: false }],
703
+ exitCode: 1,
704
+ outcome: { class: "ended", phase: "declined" },
705
+ presentation: { lines: ["Volume attachment declined. No relationship was created."] },
706
+ });
707
+ }
708
+ const refreshedVolume = safeVolume(await resolveVolume(runtime, projectedVolume.id));
709
+ if (
710
+ refreshedVolume.id !== projectedVolume.id ||
711
+ refreshedVolume.name !== projectedVolume.name
712
+ ) {
713
+ runtime.interaction.error("The selected managed volume changed after review.", {
714
+ code: "volume_selection_changed",
715
+ });
716
+ }
717
+ current = await fetchMount(runtime, reservationId);
718
+ if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
719
+ if (mountSatisfied(current, goal)) {
720
+ return wait && !mountReady(current)
721
+ ? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
722
+ : mountResult(runtime.interaction, current, reservationId, { replayed: true });
723
+ }
724
+ if (current) {
725
+ runtime.interaction.error("The volume-mount relationship changed after review.", {
726
+ code: "volume_mount_conflict",
727
+ });
728
+ }
729
+ }
730
+
731
+ try {
732
+ const payload = await runtime.fetch(reservationDeploymentPath(reservationId), {
733
+ method: "POST",
734
+ headers: idempotencyHeaders(input),
735
+ body: {
736
+ drive_id: projectedVolume.id,
737
+ mount_mode: "fuse",
738
+ access_mode: access,
739
+ },
740
+ });
741
+ current = await withMachineCount(
742
+ runtime,
743
+ projectVolumeMount(payload, reservationId),
744
+ reservationId
745
+ );
746
+ } catch (error) {
747
+ if (error instanceof CliApiError && (error.status === undefined || error.status >= 500)) {
748
+ try {
749
+ current = await fetchMount(runtime, reservationId);
750
+ if (!mountSatisfied(current, goal)) throw error;
751
+ } catch {
752
+ throw error;
753
+ }
754
+ } else {
755
+ safeFailure(runtime.interaction, error, "The volume could not be attached safely.", {
756
+ action: {
757
+ command: `ornn volume-mounts show ${reservationId}`,
758
+ description: "Inspect the exact relationship before retrying.",
759
+ type: "command",
760
+ },
761
+ });
762
+ }
763
+ }
764
+ if (!mountSatisfied(current, goal)) {
765
+ runtime.interaction.error("The accepted volume mount did not match the reviewed goal.", {
766
+ code: "volume_mount_goal_mismatch",
767
+ });
768
+ }
769
+ return wait && !mountReady(current)
770
+ ? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
771
+ : mountResult(runtime.interaction, current, reservationId);
772
+ }
773
+
774
+ async function waitCommand(input, runtime, command) {
775
+ const [rawReservation] = positionals(
776
+ input,
777
+ 1,
778
+ `ornn ${command} <canonical-reservation-id> --timeout <duration>`,
779
+ runtime.interaction
780
+ );
781
+ const reservationId = canonical(rawReservation, "reservation", runtime.interaction);
782
+ if (runtime.interaction.machine && !text(input.timeout)) {
783
+ runtime.interaction.usage("Machine waiting requires --timeout <duration>.", {
784
+ code: "missing_timeout",
785
+ field: "timeout",
786
+ });
787
+ }
788
+ const initial = await fetchMount(runtime, reservationId);
789
+ if (!initial) {
790
+ runtime.interaction.error(
791
+ "No volume-mount relationship exists for this capacity reservation.",
792
+ {
793
+ code: "volume_mount_not_found",
794
+ }
795
+ );
796
+ }
797
+ return waitForMount(
798
+ { ...input, timeout: text(input.timeout) ?? DEFAULT_MOUNT_TIMEOUT },
799
+ runtime,
800
+ reservationId,
801
+ initial,
802
+ command
803
+ );
804
+ }
805
+
806
+ export async function detachVolumeMount(input, runtime, { skipReview = false } = {}) {
807
+ const [rawReservation] = positionals(
808
+ input,
809
+ 1,
810
+ "ornn volume-mounts detach <canonical-reservation-id>",
811
+ runtime.interaction
812
+ );
813
+ requireMachineMutation(input, runtime.interaction);
814
+ const reservationId = canonical(rawReservation, "reservation", runtime.interaction);
815
+ let current = await fetchMount(runtime, reservationId);
816
+ if (!current || current.lifecycle.phase === "detached") {
817
+ return runtime.interaction.result({
818
+ data: {
819
+ reservation_id: reservationId,
820
+ volume_mount: current,
821
+ replayed: true,
822
+ volume_preserved: current?.volume ?? null,
823
+ },
824
+ outcome: { class: "ended", phase: "detached" },
825
+ presentation: {
826
+ lines: [
827
+ `Capacity reservation ${reservationId} is already detached; no volume was deleted.`,
828
+ ],
829
+ },
830
+ });
831
+ }
832
+ if (!skipReview) {
833
+ const accepted = await runtime.interaction.review({
834
+ title: "Detach managed volume",
835
+ terms: [
836
+ { label: "Capacity reservation", value: reservationId },
837
+ {
838
+ label: "Managed volume",
839
+ value: `${current.volume.name ?? "Managed volume"} (${current.volume.id})`,
840
+ },
841
+ {
842
+ label: "Mounted machines",
843
+ value: `${current.readiness.ready} of ${current.readiness.expected}`,
844
+ },
845
+ {
846
+ label: "Consequence",
847
+ value: "Mounted access ends; the managed volume and its data remain",
848
+ },
849
+ ],
850
+ prompt: "Detach this volume?",
851
+ });
852
+ if (!accepted) {
853
+ return runtime.interaction.result({
854
+ errors: [{ code: "declined", message: "Volume detachment declined.", retryable: false }],
855
+ exitCode: 1,
856
+ outcome: { class: "ended", phase: "declined" },
857
+ presentation: { lines: ["Volume detachment declined. No relationship was changed."] },
858
+ });
859
+ }
860
+ const refreshed = await fetchMount(runtime, reservationId);
861
+ if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
862
+ runtime.interaction.error("The volume-mount relationship changed after review.", {
863
+ code: "volume_mount_changed",
864
+ });
865
+ }
866
+ current = refreshed;
867
+ }
868
+ try {
869
+ await runtime.fetch(reservationAttachmentPath(reservationId), {
870
+ method: "DELETE",
871
+ headers: idempotencyHeaders(input),
872
+ });
873
+ } catch (error) {
874
+ if (error instanceof CliApiError && (error.status === undefined || error.status >= 500)) {
875
+ const reconciled = await fetchMount(runtime, reservationId);
876
+ if (reconciled && reconciled.lifecycle.phase !== "detached") throw error;
877
+ } else {
878
+ safeFailure(runtime.interaction, error, "The volume could not be detached safely.");
879
+ }
880
+ }
881
+ return runtime.interaction.result({
882
+ data: {
883
+ reservation_id: reservationId,
884
+ volume_mount: { ...current, lifecycle: { class: "ended", phase: "detaching" } },
885
+ replayed: false,
886
+ volume_preserved: current.volume,
887
+ },
888
+ outcome: { class: "working", phase: "detaching" },
889
+ resume: { kind: "volume_mount", ref: reservationId },
890
+ presentation: {
891
+ lines: [
892
+ `Volume detachment accepted for ${reservationId}.`,
893
+ `Managed volume ${current.volume.name ?? current.volume.id} was preserved.`,
894
+ ],
895
+ next: `ornn volume-mounts wait ${reservationId} --timeout ${DEFAULT_MOUNT_TIMEOUT}`,
896
+ },
897
+ });
898
+ }
899
+
900
+ async function deployFilesystem(input, runtime) {
901
+ const [rawReservation] = positionals(
902
+ input,
903
+ 1,
904
+ "ornn filesystems deploy <canonical-reservation-id> --performance-tier <tier> --capacity-gib <gib>",
905
+ runtime.interaction
906
+ );
907
+ requireMachineMutation(input, runtime.interaction);
908
+ const reservationId = canonical(rawReservation, "reservation", runtime.interaction);
909
+ const performanceTier = required(input.performanceTier, "performance-tier", runtime.interaction);
910
+ const capacityGib = positiveInteger(input.capacityGib, "capacity-gib", runtime.interaction);
911
+ const wait = shouldWait(input, runtime.interaction);
912
+ let current = await fetchMount(runtime, reservationId);
913
+ if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
914
+ if (current) {
915
+ const sameGoal = filesystemSatisfied(current, performanceTier, capacityGib);
916
+ if (!sameGoal) {
917
+ runtime.interaction.error(
918
+ "This capacity reservation already has a different managed filesystem relationship.",
919
+ {
920
+ action: {
921
+ command: `ornn filesystems show ${reservationId}`,
922
+ description:
923
+ "Inspect the current deployment before changing its exact capacity or performance goal.",
924
+ type: "command",
925
+ },
926
+ code: "filesystem_deployment_conflict",
927
+ }
928
+ );
929
+ }
930
+ return wait && !mountReady(current)
931
+ ? waitForMount(input, runtime, reservationId, current, "filesystems deploy")
932
+ : mountResult(runtime.interaction, current, reservationId, { replayed: true });
933
+ }
934
+ const accepted = await runtime.interaction.review({
935
+ title: "Deploy managed filesystem",
936
+ terms: [
937
+ { label: "Capacity reservation", value: reservationId },
938
+ { label: "Performance tier", value: performanceTier },
939
+ { label: "Capacity", value: `${capacityGib} GiB` },
940
+ { label: "Readiness", value: "Reported independently for every machine" },
941
+ ],
942
+ prompt: "Deploy this filesystem?",
943
+ });
944
+ if (!accepted) {
945
+ return runtime.interaction.result({
946
+ errors: [{ code: "declined", message: "Filesystem deployment declined.", retryable: false }],
947
+ exitCode: 1,
948
+ outcome: { class: "ended", phase: "declined" },
949
+ presentation: { lines: ["Filesystem deployment declined. No deployment was created."] },
950
+ });
951
+ }
952
+ current = await fetchMount(runtime, reservationId);
953
+ if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
954
+ if (current && !filesystemSatisfied(current, performanceTier, capacityGib)) {
955
+ runtime.interaction.error("The managed filesystem relationship changed after review.", {
956
+ code: "filesystem_deployment_conflict",
957
+ });
958
+ }
959
+ if (!current) {
960
+ try {
961
+ current = await withMachineCount(
962
+ runtime,
963
+ projectVolumeMount(
964
+ await runtime.fetch(reservationFilesystemPath(reservationId), {
965
+ method: "POST",
966
+ headers: idempotencyHeaders(input),
967
+ body: { performance_tier: performanceTier, capacity_gib: capacityGib },
968
+ }),
969
+ reservationId
970
+ ),
971
+ reservationId
972
+ );
973
+ } catch (error) {
974
+ if (error instanceof CliApiError && (error.status === undefined || error.status >= 500)) {
975
+ current = await fetchMount(runtime, reservationId);
976
+ if (!filesystemSatisfied(current, performanceTier, capacityGib)) throw error;
977
+ } else {
978
+ safeFailure(runtime.interaction, error, "The filesystem could not be deployed safely.");
979
+ }
980
+ }
981
+ }
982
+ if (!filesystemSatisfied(current, performanceTier, capacityGib)) {
983
+ runtime.interaction.error(
984
+ "The accepted filesystem deployment did not match the reviewed goal.",
985
+ {
986
+ code: "filesystem_deployment_goal_mismatch",
987
+ }
988
+ );
989
+ }
990
+ return wait && !mountReady(current)
991
+ ? waitForMount(input, runtime, reservationId, current, "filesystems deploy")
992
+ : mountResult(runtime.interaction, current, reservationId);
993
+ }
994
+
995
+ function filesystemExecution(result) {
996
+ if (!result?.data || !Object.hasOwn(result.data, "volume_mount")) return result;
997
+ const { volume_mount: filesystemDeployment, ...data } = result.data;
998
+ return {
999
+ ...result,
1000
+ data: {
1001
+ ...data,
1002
+ filesystem_deployment: filesystemDeployment,
1003
+ },
1004
+ };
1005
+ }
1006
+
1007
+ async function deleteFilesystem(input, runtime) {
1008
+ const [rawReservation] = positionals(
1009
+ input,
1010
+ 1,
1011
+ "ornn filesystems delete <canonical-reservation-id>",
1012
+ runtime.interaction
1013
+ );
1014
+ requireMachineMutation(input, runtime.interaction);
1015
+ const reservationId = canonical(rawReservation, "reservation", runtime.interaction);
1016
+ const current = await fetchMount(runtime, reservationId);
1017
+ if (!current || ["detached", "ended"].includes(current.lifecycle.phase)) {
1018
+ return runtime.interaction.result({
1019
+ data: {
1020
+ filesystem_deployment: current,
1021
+ replayed: true,
1022
+ reservation_id: reservationId,
1023
+ volume_preserved: current?.volume ?? null,
1024
+ },
1025
+ outcome: { class: "ended", phase: "deleted" },
1026
+ presentation: {
1027
+ lines: [
1028
+ `Capacity reservation ${reservationId} already has no managed filesystem deployment.`,
1029
+ ],
1030
+ },
1031
+ });
1032
+ }
1033
+ const accepted = await runtime.interaction.review({
1034
+ title: "Delete managed filesystem deployment",
1035
+ terms: [
1036
+ { label: "Capacity reservation", value: reservationId },
1037
+ {
1038
+ label: "Managed volume",
1039
+ value: `${current.volume.name ?? "Managed volume"} (${current.volume.id})`,
1040
+ },
1041
+ {
1042
+ label: "Mounted machines",
1043
+ value: `${current.readiness.ready} of ${current.readiness.expected}`,
1044
+ },
1045
+ {
1046
+ label: "Consequence",
1047
+ value: "Filesystem access ends; the managed volume and its data remain",
1048
+ },
1049
+ ],
1050
+ prompt: "Delete this filesystem deployment?",
1051
+ });
1052
+ if (!accepted) {
1053
+ return runtime.interaction.result({
1054
+ data: {
1055
+ filesystem_deployment: current,
1056
+ reservation_id: reservationId,
1057
+ volume_preserved: current.volume,
1058
+ },
1059
+ errors: [{ code: "declined", message: "Filesystem deletion declined.", retryable: false }],
1060
+ exitCode: 1,
1061
+ outcome: { class: "ended", phase: "declined" },
1062
+ presentation: { lines: ["Filesystem deletion declined. No relationship was changed."] },
1063
+ });
1064
+ }
1065
+ const refreshed = await fetchMount(runtime, reservationId);
1066
+ if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
1067
+ runtime.interaction.error("The managed filesystem relationship changed after review.", {
1068
+ code: "filesystem_deployment_changed",
1069
+ });
1070
+ }
1071
+ let deleting;
1072
+ try {
1073
+ deleting = await withMachineCount(
1074
+ runtime,
1075
+ projectVolumeMount(
1076
+ await runtime.fetch(reservationFilesystemPath(reservationId), {
1077
+ headers: idempotencyHeaders(input),
1078
+ method: "DELETE",
1079
+ }),
1080
+ reservationId
1081
+ ),
1082
+ reservationId
1083
+ );
1084
+ } catch (error) {
1085
+ if (error instanceof CliApiError && (error.status === undefined || error.status >= 500)) {
1086
+ const reconciled = await fetchMount(runtime, reservationId);
1087
+ const deletingState = reconciled?.filesystem?.phase;
1088
+ if (
1089
+ reconciled &&
1090
+ !["deleting", "deleted", "ended"].includes(deletingState) &&
1091
+ !["detached", "ended"].includes(reconciled.lifecycle.phase)
1092
+ ) {
1093
+ throw error;
1094
+ }
1095
+ deleting = reconciled;
1096
+ } else {
1097
+ safeFailure(
1098
+ runtime.interaction,
1099
+ error,
1100
+ "The filesystem deployment could not be deleted safely."
1101
+ );
1102
+ }
1103
+ }
1104
+ return runtime.interaction.result({
1105
+ data: {
1106
+ filesystem_deployment: deleting,
1107
+ replayed: false,
1108
+ reservation_id: reservationId,
1109
+ volume_preserved: current.volume,
1110
+ },
1111
+ outcome: { class: "working", phase: "deleting" },
1112
+ resume: { kind: "filesystem_deployment", ref: reservationId },
1113
+ presentation: {
1114
+ lines: [
1115
+ `Filesystem deployment removal accepted for ${reservationId}.`,
1116
+ `Managed volume ${current.volume.name ?? current.volume.id} was preserved.`,
1117
+ ],
1118
+ next: `ornn filesystems show ${reservationId}`,
1119
+ },
1120
+ });
1121
+ }
1122
+
1123
+ export async function executeMountCommand(command, input, runtime) {
1124
+ switch (command) {
1125
+ case "storage.volume-mounts.candidates":
1126
+ return candidates(input, runtime);
1127
+ case "storage.volume-mounts.show":
1128
+ return show(input, runtime, "volume-mounts show");
1129
+ case "storage.volume-mounts.attach":
1130
+ return attachVolumeMount(input, runtime);
1131
+ case "storage.volume-mounts.wait":
1132
+ return waitCommand(input, runtime, "volume-mounts wait");
1133
+ case "storage.volume-mounts.detach":
1134
+ return detachVolumeMount(input, runtime);
1135
+ case "storage.filesystems.deploy":
1136
+ return filesystemExecution(await deployFilesystem(input, runtime));
1137
+ case "storage.filesystems.show":
1138
+ return filesystemExecution(await show(input, runtime, "filesystems show"));
1139
+ case "storage.filesystems.wait":
1140
+ return filesystemExecution(await waitCommand(input, runtime, "filesystems wait"));
1141
+ case "storage.filesystems.delete":
1142
+ return deleteFilesystem(input, runtime);
1143
+ default:
1144
+ runtime.interaction.error("Unknown mount command.", { code: "unknown_command" });
1145
+ }
1146
+ }