@ornncompute/cli 1.0.72 → 1.0.97

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 (59) hide show
  1. package/README.md +55 -24
  2. package/package.json +4 -2
  3. package/src/admin-capacity-reservations.mjs +6 -13
  4. package/src/admin-command-handlers.mjs +2 -1
  5. package/src/admin-credits.mjs +1 -2
  6. package/src/admin-enrollment.mjs +22 -30
  7. package/src/admin-mutations.mjs +6 -56
  8. package/src/admin-node-diagnostics.mjs +3 -41
  9. package/src/admin-node-lifecycle.mjs +40 -160
  10. package/src/admin-node-sanitize.mjs +243 -0
  11. package/src/admin-shell.mjs +39 -32
  12. package/src/catalog-shell.mjs +6 -6
  13. package/src/evidence-commands.mjs +4 -6
  14. package/src/external-bucket-commands.mjs +76 -96
  15. package/src/interaction-runtime.mjs +5 -37
  16. package/src/kubernetes-commands.mjs +25 -183
  17. package/src/launch-mode-handlers.mjs +0 -1
  18. package/src/machine-reference.mjs +33 -56
  19. package/src/mount-commands.mjs +25 -131
  20. package/src/slurm-commands.mjs +13 -2
  21. package/src/storage-commands.mjs +2 -87
  22. package/src/vm-commands.mjs +48 -101
  23. package/vendor/api-client/commerce.js +743 -0
  24. package/vendor/capabilities/cli.js +7688 -7
  25. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
  26. package/vendor/capabilities/capabilities/admin-enrollment.js +0 -321
  27. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
  28. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -235
  29. package/vendor/capabilities/capabilities/admin.js +0 -91
  30. package/vendor/capabilities/capabilities/cli.js +0 -156
  31. package/vendor/capabilities/capabilities/clusters.js +0 -248
  32. package/vendor/capabilities/capabilities/commerce-market.js +0 -405
  33. package/vendor/capabilities/capabilities/compute.js +0 -523
  34. package/vendor/capabilities/capabilities/credits.js +0 -78
  35. package/vendor/capabilities/capabilities/evidence.js +0 -145
  36. package/vendor/capabilities/capabilities/external-buckets.js +0 -191
  37. package/vendor/capabilities/capabilities/fleet.js +0 -306
  38. package/vendor/capabilities/capabilities/identity.js +0 -129
  39. package/vendor/capabilities/capabilities/listings.js +0 -398
  40. package/vendor/capabilities/capabilities/locality.js +0 -42
  41. package/vendor/capabilities/capabilities/market-shared.js +0 -46
  42. package/vendor/capabilities/capabilities/markets.js +0 -482
  43. package/vendor/capabilities/capabilities/mounts.js +0 -176
  44. package/vendor/capabilities/capabilities/native-listing.js +0 -99
  45. package/vendor/capabilities/capabilities/networks.js +0 -1264
  46. package/vendor/capabilities/capabilities/observability.js +0 -218
  47. package/vendor/capabilities/capabilities/organization-invites.js +0 -53
  48. package/vendor/capabilities/capabilities/reservation-list.js +0 -68
  49. package/vendor/capabilities/capabilities/staff-shared.js +0 -64
  50. package/vendor/capabilities/capabilities/tokens.js +0 -165
  51. package/vendor/capabilities/capabilities/users.js +0 -159
  52. package/vendor/capabilities/capabilities/volumes.js +0 -178
  53. package/vendor/capabilities/capability.js +0 -99
  54. package/vendor/capabilities/cli-catalog.js +0 -73
  55. package/vendor/capabilities/cli-migrations.js +0 -264
  56. package/vendor/capabilities/names.js +0 -6
  57. package/vendor/capabilities/software-catalog.js +0 -82
  58. package/vendor/capabilities/spec-catalog.js +0 -151
  59. package/vendor/capabilities/supplier-directory.js +0 -20
@@ -36,11 +36,6 @@ function parseOptions(argv, allowed, usage) {
36
36
  if (options[name] !== undefined) {
37
37
  throw usageError(`${usage}\n--${name} may be supplied only once.`, name);
38
38
  }
39
- if (name === "yes") {
40
- if (separator !== -1) throw usageError(`${usage}\n--yes does not take a value.`, "yes");
41
- options.yes = true;
42
- continue;
43
- }
44
39
  const parsed = optionValue(argv, index, name, usage);
45
40
  options[name] = parsed.value;
46
41
  index += parsed.consumed;
@@ -64,18 +59,13 @@ function timeoutSeconds(value) {
64
59
  }
65
60
 
66
61
  export function parseAdminNodeDiagnoseArgs(argv) {
67
- const { options, positionals } = parseOptions(
68
- argv,
69
- new Set(["yes", "idempotency-key"]),
70
- DIAGNOSE_USAGE
71
- );
62
+ const { options, positionals } = parseOptions(argv, new Set(["idempotency-key"]), DIAGNOSE_USAGE);
72
63
  if (positionals.length !== 1 || !positionals[0]) throw usageError(DIAGNOSE_USAGE, "node-id");
73
64
  return {
74
65
  input: { node_id: positionals[0] },
75
66
  mutation: {
76
67
  idempotencyKey: options["idempotency-key"],
77
68
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
78
- yes: options.yes === true,
79
69
  },
80
70
  };
81
71
  }
@@ -83,7 +73,7 @@ export function parseAdminNodeDiagnoseArgs(argv) {
83
73
  export function parseAdminNodeExecArgs(argv) {
84
74
  const { options, positionals } = parseOptions(
85
75
  argv,
86
- new Set(["command", "timeout", "yes", "idempotency-key"]),
76
+ new Set(["command", "timeout", "idempotency-key"]),
87
77
  EXEC_USAGE
88
78
  );
89
79
  if (positionals.length !== 1 || !positionals[0]) throw usageError(EXEC_USAGE, "node-id");
@@ -99,7 +89,6 @@ export function parseAdminNodeExecArgs(argv) {
99
89
  mutation: {
100
90
  idempotencyKey: options["idempotency-key"],
101
91
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
102
- yes: options.yes === true,
103
92
  },
104
93
  };
105
94
  }
@@ -123,7 +112,6 @@ function nodeContext(payload, expectedNodeId) {
123
112
  }
124
113
  const explicitReachability =
125
114
  presentString(payload.reachability?.status) ??
126
- presentString(payload.off_grid_reachability?.status) ??
127
115
  (typeof payload.reachable === "boolean"
128
116
  ? payload.reachable
129
117
  ? "reachable"
@@ -138,10 +126,7 @@ function nodeContext(payload, expectedNodeId) {
138
126
  presentString(payload.status) ??
139
127
  "unknown",
140
128
  reachability: explicitReachability ?? "unknown",
141
- observed_at:
142
- presentString(payload.observed_at) ??
143
- presentString(payload.last_heartbeat_at) ??
144
- presentString(payload.off_grid_reachability?.last_checked_at),
129
+ observed_at: presentString(payload.observed_at) ?? presentString(payload.last_heartbeat_at),
145
130
  };
146
131
  }
147
132
 
@@ -272,27 +257,6 @@ function executionResult(raw, input, target) {
272
257
  };
273
258
  }
274
259
 
275
- function declinedResult(input) {
276
- return {
277
- data: {
278
- declined: true,
279
- node: { node_id: input.node_id },
280
- ...(input.command ? { request: requestCorrelation(input) } : {}),
281
- },
282
- exitCode: 1,
283
- outcome: { class: "failed", phase: "consent_declined" },
284
- action: null,
285
- resume: null,
286
- errors: [
287
- {
288
- code: "consent_declined",
289
- message: "The action was declined; no mutation was submitted.",
290
- retryable: false,
291
- },
292
- ],
293
- };
294
- }
295
-
296
260
  function interruptedResult(input) {
297
261
  return {
298
262
  data: {
@@ -343,7 +307,6 @@ async function runDiagnosis({ input, mutation, runtime, execute, fetch }) {
343
307
  },
344
308
  execute: async () => execute({ node_id: input.node_id }, { confirmed: true }),
345
309
  });
346
- if (result.status === "declined") return declinedResult(input);
347
310
  return diagnosisResult(result.data, target);
348
311
  } catch (error) {
349
312
  if (wasInterrupted(error, runtime)) return interruptedResult(input);
@@ -388,7 +351,6 @@ async function runExecution({ input, mutation, runtime, execute, fetch }) {
388
351
  { confirmed: true }
389
352
  ),
390
353
  });
391
- if (result.status === "declined") return declinedResult(input);
392
354
  return executionResult(result.data, input, target);
393
355
  } catch (error) {
394
356
  if (wasInterrupted(error, runtime)) return interruptedResult(input);
@@ -1,3 +1,4 @@
1
+ import { getReservation } from "../vendor/api-client/commerce.js";
1
2
  import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./admin-mutations.mjs";
2
3
  import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
3
4
  import { waitForGoal as waitForReadGoal } from "./wait-runtime.mjs";
@@ -9,9 +10,7 @@ const POLL_INTERVAL_MS = 1_000;
9
10
  const COMMAND_BY_CAPABILITY = Object.freeze({
10
11
  "nodes.reboot": "reboot",
11
12
  "nodes.hard_reset": "hard-reset",
12
- "nodes.suspend_management": "suspend-management",
13
- "nodes.resume_management": "resume-management",
14
- "nodes.terminate": "terminate",
13
+ "node.terminate": "terminate",
15
14
  "nodes.assign": "assign",
16
15
  });
17
16
 
@@ -22,35 +21,22 @@ const CAPABILITY_BY_COMMAND = Object.freeze(
22
21
  );
23
22
 
24
23
  const USAGE = Object.freeze({
25
- reboot: "Usage: ornn admin nodes reboot <node-ref> [--yes] [--no-wait] [--timeout SECONDS]",
24
+ reboot: "Usage: ornn admin nodes reboot <node-ref> [--no-wait] [--timeout SECONDS]",
26
25
  "hard-reset":
27
- "Usage: ornn admin nodes hard-reset <node-ref> --erase-local-data [--yes] [--no-wait] [--timeout SECONDS]",
28
- "suspend-management":
29
- "Usage: ornn admin nodes suspend-management <node-ref> [--reason TEXT] [--yes] [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
30
- "resume-management":
31
- "Usage: ornn admin nodes resume-management <node-ref> [--yes] [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
26
+ "Usage: ornn admin nodes hard-reset <node-ref> --erase-local-data [--no-wait] [--timeout SECONDS]",
32
27
  terminate:
33
- "Usage: ornn admin nodes terminate <node-ref> --delete-node-record [--skip-cleanup] [--reason TEXT] [--yes] [--idempotency-key KEY]",
28
+ "Usage: ornn admin nodes terminate <node-ref> --delete-node-record [--reason TEXT] [--idempotency-key KEY]",
34
29
  assign:
35
- "Usage: ornn admin nodes assign <node-ref> --reservation RESERVATION [--yes] [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
30
+ "Usage: ornn admin nodes assign <node-ref> --reservation RESERVATION [--idempotency-key KEY] [--wait|--no-wait] [--timeout SECONDS]",
36
31
  });
37
32
 
38
- const BOOLEAN_OPTIONS = new Set([
39
- "delete-node-record",
40
- "erase-local-data",
41
- "no-wait",
42
- "skip-cleanup",
43
- "wait",
44
- "yes",
45
- ]);
33
+ const BOOLEAN_OPTIONS = new Set(["delete-node-record", "erase-local-data", "no-wait", "wait"]);
46
34
  const VALUE_OPTIONS = new Set(["idempotency-key", "reason", "reservation", "timeout"]);
47
35
  const ALLOWED_OPTIONS = Object.freeze({
48
- reboot: new Set(["yes", "wait", "no-wait", "timeout"]),
49
- "hard-reset": new Set(["erase-local-data", "yes", "wait", "no-wait", "timeout"]),
50
- "suspend-management": new Set(["reason", "yes", "idempotency-key", "wait", "no-wait", "timeout"]),
51
- "resume-management": new Set(["yes", "idempotency-key", "wait", "no-wait", "timeout"]),
52
- terminate: new Set(["delete-node-record", "skip-cleanup", "reason", "yes", "idempotency-key"]),
53
- assign: new Set(["reservation", "yes", "idempotency-key", "wait", "no-wait", "timeout"]),
36
+ reboot: new Set(["wait", "no-wait", "timeout"]),
37
+ "hard-reset": new Set(["erase-local-data", "wait", "no-wait", "timeout"]),
38
+ terminate: new Set(["delete-node-record", "reason", "idempotency-key"]),
39
+ assign: new Set(["reservation", "idempotency-key", "wait", "no-wait", "timeout"]),
54
40
  });
55
41
 
56
42
  function mutationError(message, { code = "usage_error", field } = {}) {
@@ -135,22 +121,16 @@ export function parseAdminNodeLifecycleInput(command, argv) {
135
121
  });
136
122
  }
137
123
  if (name === "hard-reset" && options["erase-local-data"] !== true) {
138
- throw mutationError(
139
- `${usage}\nHard reset requires --erase-local-data; --yes cannot imply data loss.`,
140
- {
141
- code: "erase_local_data_required",
142
- field: "erase-local-data",
143
- }
144
- );
124
+ throw mutationError(`${usage}\nHard reset requires --erase-local-data.`, {
125
+ code: "erase_local_data_required",
126
+ field: "erase-local-data",
127
+ });
145
128
  }
146
129
  if (name === "terminate" && options["delete-node-record"] !== true) {
147
- throw mutationError(
148
- `${usage}\nTermination requires --delete-node-record; --yes cannot imply deletion.`,
149
- {
150
- code: "delete_node_record_required",
151
- field: "delete-node-record",
152
- }
153
- );
130
+ throw mutationError(`${usage}\nTermination requires --delete-node-record.`, {
131
+ code: "delete_node_record_required",
132
+ field: "delete-node-record",
133
+ });
154
134
  }
155
135
  if (name === "assign" && !options.reservation) {
156
136
  throw mutationError(`${usage}\nAssignment requires --reservation.`, {
@@ -159,40 +139,29 @@ export function parseAdminNodeLifecycleInput(command, argv) {
159
139
  });
160
140
  }
161
141
  if (name !== "hard-reset" && options["erase-local-data"]) throw mutationError(usage);
162
- if (name !== "terminate" && (options["delete-node-record"] || options["skip-cleanup"])) {
163
- throw mutationError(usage);
164
- }
142
+ if (name !== "terminate" && options["delete-node-record"]) throw mutationError(usage);
165
143
  if (name !== "assign" && options.reservation) throw mutationError(usage);
166
- if (!name.includes("management") && name !== "terminate" && options.reason) {
167
- throw mutationError(usage);
168
- }
144
+ if (name !== "terminate" && options.reason) throw mutationError(usage);
169
145
 
170
146
  const idempotencyKey = options["idempotency-key"]?.trim();
171
147
  const safety =
172
148
  name === "reboot" || name === "hard-reset"
173
- ? ADMIN_MUTATION_SAFETY.eligibleHumanOnly
149
+ ? ADMIN_MUTATION_SAFETY.direct
174
150
  : ADMIN_MUTATION_SAFETY.reconcile;
175
151
  return {
176
152
  input: {
177
153
  node_ref: nodeRef,
178
- ...(options.yes ? { yes: true } : {}),
179
154
  ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
180
155
  ...(options.reason ? { reason: options.reason } : {}),
181
156
  ...(options.wait ? { wait: true } : options["no-wait"] ? { wait: false } : {}),
182
157
  ...(options.timeout ? { timeout_seconds: durationSeconds(options.timeout, usage) } : {}),
183
158
  ...(name === "hard-reset" ? { erase_local_data: true } : {}),
184
- ...(name === "terminate"
185
- ? {
186
- delete_node_record: true,
187
- ...(options["skip-cleanup"] ? { skip_cleanup: true } : {}),
188
- }
189
- : {}),
159
+ ...(name === "terminate" ? { delete_node_record: true } : {}),
190
160
  ...(name === "assign" ? { reservation_id: options.reservation } : {}),
191
161
  },
192
162
  mutation: {
193
163
  idempotencyKey,
194
164
  safety,
195
- yes: options.yes === true,
196
165
  },
197
166
  };
198
167
  }
@@ -218,16 +187,7 @@ function nodeSnapshot(node) {
218
187
  organization_id: text(node?.organization_id),
219
188
  facility_id: text(node?.facility_id ?? node?.facilityId),
220
189
  reservation_id: reservationId(node),
221
- off_grid: node?.off_grid === true,
222
190
  cleanup_state: text(node?.cleanup_state),
223
- reachability:
224
- node?.reachable === false || node?.health_readiness?.reachable === false
225
- ? "unreachable"
226
- : text(
227
- node?.reachability?.status ??
228
- node?.off_grid_reachability?.status ??
229
- node?.health_readiness?.reachability
230
- ),
231
191
  };
232
192
  }
233
193
 
@@ -235,18 +195,6 @@ function sameSnapshot(left, right) {
235
195
  return JSON.stringify(nodeSnapshot(left)) === JSON.stringify(nodeSnapshot(right));
236
196
  }
237
197
 
238
- function isOffGrid(node) {
239
- const snapshot = nodeSnapshot(node);
240
- return snapshot.off_grid || ["off-grid", "off_grid"].includes(snapshot.status.toLowerCase());
241
- }
242
-
243
- function isUnreachable(node) {
244
- const snapshot = nodeSnapshot(node);
245
- return (
246
- isOffGrid(node) || ["unreachable", "offline"].includes(snapshot.reachability.toLowerCase())
247
- );
248
- }
249
-
250
198
  function nodeRows(payload) {
251
199
  if (Array.isArray(payload)) return payload;
252
200
  return Array.isArray(payload?.nodes) ? payload.nodes : [];
@@ -357,20 +305,18 @@ async function resolveLiveMachine(fetch, node) {
357
305
  function reservationSnapshot(reservation, requestedId) {
358
306
  return {
359
307
  reservation_id: text(reservation?.reservationId) || requestedId,
360
- organization_id: text(reservation?.organizationId),
308
+ organization_id: text(reservation?.customerId ?? reservation?.organizationId),
361
309
  listing_id: text(reservation?.listingId),
362
310
  kind: text(reservation?.kind),
363
311
  status: text(reservation?.status),
364
- node_count: Number(reservation?.nodeCount ?? 0) || 0,
312
+ node_count: Number(reservation?.reservedNodeCount ?? 0) || 0,
365
313
  starts_at: text(reservation?.startsAt),
366
314
  ends_at: text(reservation?.endsAt),
367
315
  };
368
316
  }
369
317
 
370
318
  async function readReservation(fetch, id) {
371
- const reservation = await fetch(`/commerce/reservations/${encodeURIComponent(id)}`, {
372
- method: "GET",
373
- });
319
+ const reservation = await getReservation(fetch, id);
374
320
  return reservationSnapshot(reservation, id);
375
321
  }
376
322
 
@@ -412,11 +358,9 @@ function safeOwnerResult(result) {
412
358
  const safe = {};
413
359
  for (const key of [
414
360
  "deleted",
415
- "forced",
416
361
  "id",
417
362
  "mode",
418
363
  "node_id",
419
- "off_grid",
420
364
  "packet_id",
421
365
  "request_id",
422
366
  "reservation_id",
@@ -484,47 +428,13 @@ async function prepareLifecycle({ command, fetch, input, machine }) {
484
428
  };
485
429
  }
486
430
 
487
- if (command === "suspend-management") {
488
- fields.push({ label: "Host power", value: "leave powered on" });
489
- fields.push({ label: "Managed access", value: "remove agent management and managed SSH keys" });
490
- if (input.reason) fields.push({ label: "Reason", value: input.reason });
491
- return {
492
- review: {
493
- title: "Review management suspension",
494
- fields,
495
- consequence: "Ornn management and managed access end while the host remains powered on.",
496
- },
497
- value,
498
- };
499
- }
500
-
501
- if (command === "resume-management") {
502
- fields.push({ label: "Management", value: "remotely re-enroll the node" });
503
- fields.push({ label: "Managed access", value: "restore after successful enrollment" });
504
- return {
505
- review: {
506
- title: "Review management resumption",
507
- fields,
508
- consequence: "Ornn uses the stored admin access to reinstall management on this host.",
509
- },
510
- value,
511
- };
512
- }
513
-
514
431
  if (command === "terminate") {
515
- if (input.skip_cleanup && !isUnreachable(node)) {
516
- throw mutationError(
517
- "--skip-cleanup is allowed only when the authoritative node read says the node is unreachable or off-grid.",
518
- { code: "skip_cleanup_requires_unreachable", field: "skip-cleanup" }
519
- );
520
- }
521
432
  fields.push({
522
433
  label: "Host cleanup",
523
- value: input.skip_cleanup
524
- ? "SKIP; the node is authoritatively unreachable/off-grid"
525
- : "not dispatched by termination; complete any required host wipe separately before reuse",
434
+ value:
435
+ "not dispatched by termination; complete any required host wipe separately before reuse",
526
436
  });
527
- fields.push({ label: "Node record", value: "PERMANENTLY DELETE" });
437
+ fields.push({ label: "Node record", value: "MARK TERMINATED" });
528
438
  fields.push({
529
439
  label: "Reservation/capacity consequence",
530
440
  value: reservationId(node)
@@ -538,7 +448,7 @@ async function prepareLifecycle({ command, fetch, input, machine }) {
538
448
  title: "Review node termination",
539
449
  fields,
540
450
  consequence:
541
- "The node record is permanently deleted; this does not cancel its capacity reservation.",
451
+ "The node record is marked terminated; this does not cancel its capacity reservation.",
542
452
  },
543
453
  value,
544
454
  };
@@ -582,11 +492,11 @@ async function reconcileLifecycle({ fetch, input, value }) {
582
492
  data: { node_id: id, status: "already_absent", deleted: false },
583
493
  };
584
494
  }
585
- if (input.skip_cleanup && !isUnreachable(current)) {
495
+ // Terminate keeps the record, so a re-run finds it terminated, not gone.
496
+ if (isTerminated(current)) {
586
497
  return {
587
- status: "conflict",
588
- message:
589
- "The node is no longer authoritatively unreachable/off-grid; renew review without --skip-cleanup.",
498
+ status: "satisfied",
499
+ data: { node_id: id, status: "terminated", deleted: true },
590
500
  };
591
501
  }
592
502
  if (!sameSnapshot(reviewedNode, current)) {
@@ -599,29 +509,6 @@ async function reconcileLifecycle({ fetch, input, value }) {
599
509
  }
600
510
  if (!current) return { status: "conflict", message: "The exact node no longer exists." };
601
511
 
602
- if (command === "suspend-management") {
603
- if (isOffGrid(current)) {
604
- return { status: "satisfied", data: { node_id: id, status: "off-grid", off_grid: true } };
605
- }
606
- if (!sameSnapshot(reviewedNode, current)) {
607
- return { status: "conflict", message: "The node's management goal changed after review." };
608
- }
609
- return { status: "apply" };
610
- }
611
-
612
- if (command === "resume-management") {
613
- if (!isOffGrid(current)) {
614
- return {
615
- status: "satisfied",
616
- data: { node_id: id, status: nodeSnapshot(current).status, off_grid: false },
617
- };
618
- }
619
- if (!sameSnapshot(reviewedNode, current)) {
620
- return { status: "conflict", message: "The node's management goal changed after review." };
621
- }
622
- return { status: "apply" };
623
- }
624
-
625
512
  if (command === "assign") {
626
513
  const currentReservation = reservationId(current);
627
514
  if (currentReservation === input.reservation_id) {
@@ -669,15 +556,17 @@ async function reconcileLifecycle({ fetch, input, value }) {
669
556
  }
670
557
 
671
558
  function goalSatisfied(command, node, input) {
672
- if (command === "terminate") return node === null;
559
+ if (command === "terminate") return node === null || isTerminated(node);
673
560
  if (!node) return false;
674
- if (command === "suspend-management") return isOffGrid(node);
675
- if (command === "resume-management") return !isOffGrid(node);
676
561
  if (command === "assign") return reservationId(node) === input.reservation_id;
677
562
  if (cleanupState(node) === "failed") return false;
678
563
  return !restartInProgress(node);
679
564
  }
680
565
 
566
+ function isTerminated(node) {
567
+ return text(node?.status).toLowerCase() === "terminated";
568
+ }
569
+
681
570
  function cleanupState(node) {
682
571
  return text(node?.cleanup_state).toLowerCase();
683
572
  }
@@ -900,15 +789,6 @@ export async function runAdminNodeLifecycle({
900
789
  },
901
790
  });
902
791
 
903
- if (result.status === "declined") {
904
- return {
905
- ...result,
906
- action: null,
907
- resume: null,
908
- errors: [],
909
- };
910
- }
911
-
912
792
  const accepted = {
913
793
  operation: command,
914
794
  reconciled: result.reconciled === true || lateReconciled,
@@ -939,7 +819,7 @@ export const adminNodeLifecycleHandlers = Object.keys(COMMAND_BY_CAPABILITY).map
939
819
  safety:
940
820
  COMMAND_BY_CAPABILITY[capabilityId] === "reboot" ||
941
821
  COMMAND_BY_CAPABILITY[capabilityId] === "hard-reset"
942
- ? ADMIN_MUTATION_SAFETY.eligibleHumanOnly
822
+ ? ADMIN_MUTATION_SAFETY.direct
943
823
  : ADMIN_MUTATION_SAFETY.reconcile,
944
824
  run: runAdminNodeLifecycle,
945
825
  })