@ornncompute/cli 1.0.72 → 1.0.75

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 (34) hide show
  1. package/README.md +24 -20
  2. package/package.json +1 -1
  3. package/src/admin-capacity-reservations.mjs +2 -12
  4. package/src/admin-command-handlers.mjs +0 -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 +2 -36
  9. package/src/admin-node-lifecycle.mjs +25 -43
  10. package/src/admin-shell.mjs +39 -32
  11. package/src/external-bucket-commands.mjs +5 -73
  12. package/src/interaction-runtime.mjs +2 -37
  13. package/src/kubernetes-commands.mjs +9 -100
  14. package/src/launch-mode-handlers.mjs +0 -1
  15. package/src/mount-commands.mjs +25 -131
  16. package/src/storage-commands.mjs +2 -87
  17. package/src/vm-commands.mjs +9 -42
  18. package/vendor/capabilities/capabilities/admin-enrollment.js +18 -17
  19. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +3 -101
  20. package/vendor/capabilities/capabilities/admin.js +3 -3
  21. package/vendor/capabilities/capabilities/clusters.js +3 -3
  22. package/vendor/capabilities/capabilities/commerce-market.js +9 -9
  23. package/vendor/capabilities/capabilities/compute.js +7 -7
  24. package/vendor/capabilities/capabilities/credits.js +7 -2
  25. package/vendor/capabilities/capabilities/external-buckets.js +4 -4
  26. package/vendor/capabilities/capabilities/fleet.js +109 -29
  27. package/vendor/capabilities/capabilities/market-shared.js +1 -1
  28. package/vendor/capabilities/capabilities/markets.js +12 -12
  29. package/vendor/capabilities/capabilities/mounts.js +4 -4
  30. package/vendor/capabilities/capabilities/networks.js +5 -5
  31. package/vendor/capabilities/capabilities/observability.js +49 -14
  32. package/vendor/capabilities/capabilities/tokens.js +5 -9
  33. package/vendor/capabilities/capabilities/users.js +8 -10
  34. package/vendor/capabilities/capabilities/volumes.js +5 -5
@@ -640,7 +640,7 @@ async function show(input, runtime, command) {
640
640
  return mountResult(runtime.interaction, await fetchMount(runtime, reservationId), reservationId);
641
641
  }
642
642
 
643
- export async function attachVolumeMount(input, runtime, { skipReview = false } = {}) {
643
+ export async function attachVolumeMount(input, runtime) {
644
644
  const [rawReservation] = positionals(
645
645
  input,
646
646
  1,
@@ -684,50 +684,23 @@ export async function attachVolumeMount(input, runtime, { skipReview = false } =
684
684
  }
685
685
  );
686
686
  }
687
- if (!skipReview) {
688
- const accepted = await runtime.interaction.review({
689
- title: "Attach managed volume",
690
- terms: [
691
- { label: "Capacity reservation", value: reservationId },
692
- { label: "Managed volume", value: `${projectedVolume.name} (${projectedVolume.id})` },
693
- { label: "Access", value: access },
694
- { label: "Machine scope", value: "Every machine in the reservation" },
695
- {
696
- label: "Volume lifecycle",
697
- value: "The volume remains independent from this relationship",
698
- },
699
- ],
700
- prompt: "Attach this volume?",
687
+ const refreshedVolume = safeVolume(await resolveVolume(runtime, projectedVolume.id));
688
+ if (refreshedVolume.id !== projectedVolume.id || refreshedVolume.name !== projectedVolume.name) {
689
+ runtime.interaction.error("The selected managed volume changed before attachment.", {
690
+ code: "volume_selection_changed",
691
+ });
692
+ }
693
+ current = await fetchMount(runtime, reservationId);
694
+ if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
695
+ if (mountSatisfied(current, goal)) {
696
+ return wait && !mountReady(current)
697
+ ? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
698
+ : mountResult(runtime.interaction, current, reservationId, { replayed: true });
699
+ }
700
+ if (current) {
701
+ runtime.interaction.error("The volume-mount relationship changed before attachment.", {
702
+ code: "volume_mount_conflict",
701
703
  });
702
- if (!accepted) {
703
- return runtime.interaction.result({
704
- errors: [{ code: "declined", message: "Volume attachment declined.", retryable: false }],
705
- exitCode: 1,
706
- outcome: { class: "ended", phase: "declined" },
707
- presentation: { lines: ["Volume attachment declined. No relationship was created."] },
708
- });
709
- }
710
- const refreshedVolume = safeVolume(await resolveVolume(runtime, projectedVolume.id));
711
- if (
712
- refreshedVolume.id !== projectedVolume.id ||
713
- refreshedVolume.name !== projectedVolume.name
714
- ) {
715
- runtime.interaction.error("The selected managed volume changed after review.", {
716
- code: "volume_selection_changed",
717
- });
718
- }
719
- current = await fetchMount(runtime, reservationId);
720
- if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
721
- if (mountSatisfied(current, goal)) {
722
- return wait && !mountReady(current)
723
- ? waitForMount(input, runtime, reservationId, current, "volume-mounts attach")
724
- : mountResult(runtime.interaction, current, reservationId, { replayed: true });
725
- }
726
- if (current) {
727
- runtime.interaction.error("The volume-mount relationship changed after review.", {
728
- code: "volume_mount_conflict",
729
- });
730
- }
731
704
  }
732
705
 
733
706
  try {
@@ -805,7 +778,7 @@ async function waitCommand(input, runtime, command) {
805
778
  );
806
779
  }
807
780
 
808
- export async function detachVolumeMount(input, runtime, { skipReview = false } = {}) {
781
+ export async function detachVolumeMount(input, runtime) {
809
782
  const [rawReservation] = positionals(
810
783
  input,
811
784
  1,
@@ -831,42 +804,13 @@ export async function detachVolumeMount(input, runtime, { skipReview = false } =
831
804
  },
832
805
  });
833
806
  }
834
- if (!skipReview) {
835
- const accepted = await runtime.interaction.review({
836
- title: "Detach managed volume",
837
- terms: [
838
- { label: "Capacity reservation", value: reservationId },
839
- {
840
- label: "Managed volume",
841
- value: `${current.volume.name ?? "Managed volume"} (${current.volume.id})`,
842
- },
843
- {
844
- label: "Mounted machines",
845
- value: `${current.readiness.ready} of ${current.readiness.expected}`,
846
- },
847
- {
848
- label: "Consequence",
849
- value: "Mounted access ends; the managed volume and its data remain",
850
- },
851
- ],
852
- prompt: "Detach this volume?",
807
+ const refreshed = await fetchMount(runtime, reservationId);
808
+ if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
809
+ runtime.interaction.error("The volume-mount relationship changed before detachment.", {
810
+ code: "volume_mount_changed",
853
811
  });
854
- if (!accepted) {
855
- return runtime.interaction.result({
856
- errors: [{ code: "declined", message: "Volume detachment declined.", retryable: false }],
857
- exitCode: 1,
858
- outcome: { class: "ended", phase: "declined" },
859
- presentation: { lines: ["Volume detachment declined. No relationship was changed."] },
860
- });
861
- }
862
- const refreshed = await fetchMount(runtime, reservationId);
863
- if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
864
- runtime.interaction.error("The volume-mount relationship changed after review.", {
865
- code: "volume_mount_changed",
866
- });
867
- }
868
- current = refreshed;
869
812
  }
813
+ current = refreshed;
870
814
  try {
871
815
  await runtime.fetch(reservationAttachmentPath(reservationId), {
872
816
  method: "DELETE",
@@ -933,28 +877,10 @@ async function deployFilesystem(input, runtime) {
933
877
  ? waitForMount(input, runtime, reservationId, current, "filesystems deploy")
934
878
  : mountResult(runtime.interaction, current, reservationId, { replayed: true });
935
879
  }
936
- const accepted = await runtime.interaction.review({
937
- title: "Deploy managed filesystem",
938
- terms: [
939
- { label: "Capacity reservation", value: reservationId },
940
- { label: "Performance tier", value: performanceTier },
941
- { label: "Capacity", value: `${capacityGib} GiB` },
942
- { label: "Readiness", value: "Reported independently for every machine" },
943
- ],
944
- prompt: "Deploy this filesystem?",
945
- });
946
- if (!accepted) {
947
- return runtime.interaction.result({
948
- errors: [{ code: "declined", message: "Filesystem deployment declined.", retryable: false }],
949
- exitCode: 1,
950
- outcome: { class: "ended", phase: "declined" },
951
- presentation: { lines: ["Filesystem deployment declined. No deployment was created."] },
952
- });
953
- }
954
880
  current = await fetchMount(runtime, reservationId);
955
881
  if (current && ["detached", "ended"].includes(current.lifecycle.phase)) current = null;
956
882
  if (current && !filesystemSatisfied(current, performanceTier, capacityGib)) {
957
- runtime.interaction.error("The managed filesystem relationship changed after review.", {
883
+ runtime.interaction.error("The managed filesystem relationship changed before deployment.", {
958
884
  code: "filesystem_deployment_conflict",
959
885
  });
960
886
  }
@@ -1032,41 +958,9 @@ async function deleteFilesystem(input, runtime) {
1032
958
  },
1033
959
  });
1034
960
  }
1035
- const accepted = await runtime.interaction.review({
1036
- title: "Delete managed filesystem deployment",
1037
- terms: [
1038
- { label: "Capacity reservation", value: reservationId },
1039
- {
1040
- label: "Managed volume",
1041
- value: `${current.volume.name ?? "Managed volume"} (${current.volume.id})`,
1042
- },
1043
- {
1044
- label: "Mounted machines",
1045
- value: `${current.readiness.ready} of ${current.readiness.expected}`,
1046
- },
1047
- {
1048
- label: "Consequence",
1049
- value: "Filesystem access ends; the managed volume and its data remain",
1050
- },
1051
- ],
1052
- prompt: "Delete this filesystem deployment?",
1053
- });
1054
- if (!accepted) {
1055
- return runtime.interaction.result({
1056
- data: {
1057
- filesystem_deployment: current,
1058
- reservation_id: reservationId,
1059
- volume_preserved: current.volume,
1060
- },
1061
- errors: [{ code: "declined", message: "Filesystem deletion declined.", retryable: false }],
1062
- exitCode: 1,
1063
- outcome: { class: "ended", phase: "declined" },
1064
- presentation: { lines: ["Filesystem deletion declined. No relationship was changed."] },
1065
- });
1066
- }
1067
961
  const refreshed = await fetchMount(runtime, reservationId);
1068
962
  if (!refreshed || refreshed.id !== current.id || refreshed.volume.id !== current.volume.id) {
1069
- runtime.interaction.error("The managed filesystem relationship changed after review.", {
963
+ runtime.interaction.error("The managed filesystem relationship changed before deletion.", {
1070
964
  code: "filesystem_deployment_changed",
1071
965
  });
1072
966
  }
@@ -21,11 +21,6 @@ import {
21
21
  const DEFAULT_PAGE_LIMIT = 50;
22
22
  const MAX_PAGE_LIMIT = 500;
23
23
  const STORAGE_PREFIX = "/nodes/storage-drives";
24
- const UNSAFE_CONTENT_COMMANDS = new Set([
25
- "storage.volumes.clear",
26
- "storage.volumes.files.delete",
27
- "storage.volumes.folders.delete",
28
- ]);
29
24
 
30
25
  /**
31
26
  * Narrow CLI adapter expected by executeStorageCommand:
@@ -381,48 +376,11 @@ export async function resolveVolume(runtime, reference) {
381
376
  return resolveFromRows(await fetchDrives(runtime), reference);
382
377
  }
383
378
 
384
- async function review(runtime, options) {
385
- const accepted = await runtime.interaction.review(options);
386
- if (accepted !== false) return null;
387
- return runtime.interaction.result({
388
- data: null,
389
- exitCode: 1,
390
- outcome: { class: "ended", phase: "declined" },
391
- });
392
- }
393
-
394
- async function unsafeReview(command, input, runtime, reference, remotePath) {
395
- if (!UNSAFE_CONTENT_COMMANDS.has(command)) return null;
396
- const title =
397
- command === "storage.volumes.clear"
398
- ? "Clear volume contents"
399
- : command === "storage.volumes.files.delete"
400
- ? "Delete volume file"
401
- : "Delete volume folder";
402
- const terms = [
403
- { label: "Volume", value: reference },
404
- ...(remotePath ? [{ label: "Remote path", value: remotePath }] : []),
405
- {
406
- label: "Consequence",
407
- value:
408
- command === "storage.volumes.clear"
409
- ? "Every file and folder will be permanently deleted"
410
- : "The selected content will be permanently deleted",
411
- },
412
- ];
413
- return review(runtime, {
414
- prompt: "Continue?",
415
- requireLive: true,
416
- terms,
417
- title,
418
- });
419
- }
420
-
421
379
  async function createVolume(input, runtime) {
422
380
  positionals(
423
381
  input,
424
382
  0,
425
- "Usage: ornn volumes create --name <name> [--source <volume>] [--yes] [--idempotency-key <key>]"
383
+ "Usage: ornn volumes create --name <name> [--source <volume>] [--idempotency-key <key>]"
426
384
  );
427
385
  requireMachineMutationKey(input, runtime);
428
386
  const name = normalizeVolumeName(input.name);
@@ -439,21 +397,6 @@ async function createVolume(input, runtime) {
439
397
  { code: "volume_name_ambiguous", field: "name" }
440
398
  );
441
399
  }
442
- const declined = await review(runtime, {
443
- prompt: "Create this volume?",
444
- terms: [
445
- { label: "Volume name", value: name },
446
- { label: "Source", value: source ? String(source.id) : "empty volume" },
447
- {
448
- label: "Consequence",
449
- value: source
450
- ? "Create a durable managed volume copied from the selected source"
451
- : "Create an empty durable managed volume",
452
- },
453
- ],
454
- title: "Create volume",
455
- });
456
- if (declined) return declined;
457
400
  if (matches.length === 1) {
458
401
  const existing = matches[0];
459
402
  const existingSource = existing?.source_drive_id ?? existing?.source?.source_drive_id ?? null;
@@ -550,8 +493,6 @@ async function showVolume(input, runtime, { refresh = false } = {}) {
550
493
 
551
494
  async function clearVolume(input, runtime) {
552
495
  const [reference] = positionals(input, 1, "Usage: ornn volumes clear <volume>");
553
- const declined = await unsafeReview("storage.volumes.clear", input, runtime, reference);
554
- if (declined) return declined;
555
496
  const drive = await resolveVolume(runtime, reference);
556
497
  try {
557
498
  const payload = await runtime.fetch(
@@ -590,15 +531,6 @@ async function clearVolume(input, runtime) {
590
531
  async function deleteVolume(input, runtime) {
591
532
  const [reference] = positionals(input, 1, "Usage: ornn volumes delete <volume>");
592
533
  requireMachineMutationKey(input, runtime);
593
- const declined = await review(runtime, {
594
- prompt: "Delete this volume?",
595
- terms: [
596
- { label: "Volume", value: reference },
597
- { label: "Consequence", value: "The volume and all data in it will be permanently deleted" },
598
- ],
599
- title: "Delete volume",
600
- });
601
- if (declined) return declined;
602
534
  const drive = await resolveVolume(runtime, reference);
603
535
  try {
604
536
  await runtime.fetch(
@@ -802,7 +734,7 @@ async function uploadFile(input, runtime) {
802
734
  const [reference, localFile] = positionals(
803
735
  input,
804
736
  2,
805
- "Usage: ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] [--yes] [--idempotency-key <key>]"
737
+ "Usage: ornn volumes files upload <volume> <local-file> [--path <remote-path>] [--content-type <type>] [--idempotency-key <key>]"
806
738
  );
807
739
  requireMachineMutationKey(input, runtime);
808
740
  let fileInfo;
@@ -825,21 +757,6 @@ async function uploadFile(input, runtime) {
825
757
  ? requiredText(input.contentType, "--content-type")
826
758
  : "application/octet-stream";
827
759
  const drive = await resolveVolume(runtime, reference);
828
- const declined = await review(runtime, {
829
- prompt: "Upload this file?",
830
- terms: [
831
- { label: "Volume", value: `${drive.name} (${drive.id})` },
832
- { label: "Local file", value: localFile },
833
- { label: "Remote path", value: remotePath },
834
- { label: "Bytes", value: String(fileInfo.size) },
835
- {
836
- label: "Consequence",
837
- value: "Create or replace the selected path in the managed volume",
838
- },
839
- ],
840
- title: "Upload volume file",
841
- });
842
- if (declined) return declined;
843
760
  const requestHash = createHash("sha256");
844
761
  for await (const chunk of createReadStream(localFile)) requestHash.update(chunk);
845
762
  const sha256 = requestHash.digest("hex");
@@ -1032,8 +949,6 @@ async function deleteContent(command, input, runtime) {
1032
949
  `Usage: ornn volumes ${kind === "file" ? "files" : "folders"} delete <volume> <remote-path>`
1033
950
  );
1034
951
  const remotePath = normalizeRemotePath(rawRemotePath);
1035
- const declined = await unsafeReview(command, input, runtime, reference, remotePath);
1036
- if (declined) return declined;
1037
952
  const drive = await resolveVolume(runtime, reference);
1038
953
  const endpoint = computeEndpoint(
1039
954
  `${STORAGE_PREFIX}/${encodeURIComponent(String(drive.id))}/${kind}s/${encodedRemotePath(remotePath)}`
@@ -303,7 +303,6 @@ function failedOperationRecoveryCommand(value, interaction) {
303
303
  `--mode ${accessMode}`,
304
304
  `--network ${networkMode}`,
305
305
  accessMode !== sourceMode ? "--erase-local-data" : null,
306
- "--yes",
307
306
  `--idempotency-key ${requestId}`,
308
307
  ]
309
308
  .filter(Boolean)
@@ -317,7 +316,6 @@ function failedOperationRecoveryCommand(value, interaction) {
317
316
  `ornn machines ${command}`,
318
317
  shellQuote(id),
319
318
  command === "reset" ? "--erase-local-data" : null,
320
- "--yes",
321
319
  `--idempotency-key ${requestId}`,
322
320
  ]
323
321
  .filter(Boolean)
@@ -392,7 +390,7 @@ function positional(input, expected, usage, interaction) {
392
390
  return values;
393
391
  }
394
392
 
395
- function machineMutationPreflight(input, interaction, { requireYes = true } = {}) {
393
+ function machineMutationPreflight(input, interaction) {
396
394
  if (!interaction.machine) return;
397
395
  if (!text(input.idempotencyKey)) {
398
396
  interaction.usage("--idempotency-key is required for automation.", {
@@ -400,12 +398,6 @@ function machineMutationPreflight(input, interaction, { requireYes = true } = {}
400
398
  field: "idempotencyKey",
401
399
  });
402
400
  }
403
- if (requireYes && input.yes !== true) {
404
- interaction.usage("--yes is required for automation after reviewing the exact intent.", {
405
- code: "consent_required",
406
- field: "yes",
407
- });
408
- }
409
401
  }
410
402
 
411
403
  async function selectedOrganization(context, interaction, expectedId) {
@@ -821,12 +813,6 @@ function launchInputPreflight(input, interaction) {
821
813
  field: "label",
822
814
  });
823
815
  }
824
- if (input.yes !== true) {
825
- interaction.usage("--yes is required for automation after reviewing the launch intent.", {
826
- code: "consent_required",
827
- field: "yes",
828
- });
829
- }
830
816
  if (!text(input.idempotencyKey)) {
831
817
  interaction.usage("--idempotency-key is required for automation.", {
832
818
  code: "idempotency_key_required",
@@ -1199,7 +1185,7 @@ export const vmLaunchModeHandler = {
1199
1185
  resourceId(key),
1200
1186
  "--idempotency-key",
1201
1187
  idempotencyKey,
1202
- ...(interaction.machine ? ["--yes", "--no-wait", "--format", "json"] : []),
1188
+ ...(interaction.machine ? ["--no-wait", "--format", "json"] : []),
1203
1189
  ].join(" ");
1204
1190
  return uncertainMutationFailure(interaction, error, {
1205
1191
  data: {
@@ -1559,7 +1545,6 @@ function durableWorkspaceAllocation(reservation, interaction) {
1559
1545
  function workspaceStepContext(context, interaction, signal) {
1560
1546
  const stepInteraction = Object.create(interaction);
1561
1547
  stepInteraction.review = async () => true;
1562
- stepInteraction.options = { ...interaction.options, yes: true };
1563
1548
  return {
1564
1549
  ...context,
1565
1550
  fetch: (path, init = {}) =>
@@ -1681,7 +1666,7 @@ function workspaceResumeCommand(input, goal, facts, id, interaction, localResume
1681
1666
  if (goal.username) parts.push("--username", goal.username);
1682
1667
  if (input.noWait) parts.push("--no-wait");
1683
1668
  if (input.wait) parts.push("--wait", "--timeout", text(input.timeout));
1684
- if (interaction.machine) parts.push("--yes", "--format", interaction.format);
1669
+ if (interaction.machine) parts.push("--format", interaction.format);
1685
1670
  return parts.map(shellQuote).join(" ");
1686
1671
  }
1687
1672
 
@@ -1952,7 +1937,6 @@ async function launchDurableBareMetalWorkspace({
1952
1937
  $positionals: [reservationId],
1953
1938
  key: resourceId(key),
1954
1939
  idempotencyKey: `${id}:ssh-key`,
1955
- yes: true,
1956
1940
  }
1957
1941
  );
1958
1942
  states.ssh_key_assignment = {
@@ -1988,7 +1972,6 @@ async function launchDurableBareMetalWorkspace({
1988
1972
  idempotencyKey: `${id}:volume-mount`,
1989
1973
  noWait: true,
1990
1974
  volume: facts.volume.id,
1991
- yes: true,
1992
1975
  }
1993
1976
  );
1994
1977
  facts.volumeMount = mount.data.volume_mount;
@@ -2464,7 +2447,7 @@ export const bareMetalLaunchModeHandler = {
2464
2447
  resourceId(key),
2465
2448
  "--idempotency-key",
2466
2449
  idempotencyKey,
2467
- ...(interaction.machine ? ["--yes", "--no-wait", "--format", "json"] : []),
2450
+ ...(interaction.machine ? ["--no-wait", "--format", "json"] : []),
2468
2451
  ].join(" ");
2469
2452
  return uncertainMutationFailure(interaction, error, {
2470
2453
  data: {
@@ -3002,24 +2985,12 @@ function validateWaitInput(input, interaction, { waitCommand = false } = {}) {
3002
2985
  function requireMachineMutationSafety(input, interaction, { destructive = false } = {}) {
3003
2986
  validateWaitInput(input, interaction);
3004
2987
  if (destructive && input.eraseLocalData !== true) {
3005
- interaction.usage(
3006
- "machines reset requires --erase-local-data; --yes cannot infer destructive scope.",
3007
- {
3008
- code: "erase_local_data_required",
3009
- field: "eraseLocalData",
3010
- }
3011
- );
2988
+ interaction.usage("machines reset requires --erase-local-data.", {
2989
+ code: "erase_local_data_required",
2990
+ field: "eraseLocalData",
2991
+ });
3012
2992
  }
3013
2993
  if (interaction.machine || !interaction.eligibleHuman) {
3014
- if (input.yes !== true) {
3015
- interaction.usage(
3016
- "--yes is required outside an eligible terminal after reviewing the exact intent.",
3017
- {
3018
- code: "consent_required",
3019
- field: "yes",
3020
- }
3021
- );
3022
- }
3023
2994
  if (!text(input.idempotencyKey)) {
3024
2995
  interaction.usage("--idempotency-key is required outside an eligible terminal.", {
3025
2996
  code: "idempotency_key_required",
@@ -3452,7 +3423,7 @@ async function switchMachine(input, context, interaction) {
3452
3423
  const erasesLocalData = targetMode !== currentMode;
3453
3424
  if (erasesLocalData && input.eraseLocalData !== true) {
3454
3425
  interaction.usage(
3455
- "Changing between VM and Bare Metal permanently erases source-local data and requires --erase-local-data; --yes cannot infer destructive scope.",
3426
+ "Changing between VM and Bare Metal permanently erases source-local data and requires --erase-local-data; cannot infer destructive scope.",
3456
3427
  { code: "erase_local_data_required", field: "eraseLocalData" }
3457
3428
  );
3458
3429
  }
@@ -3561,7 +3532,6 @@ async function switchMachine(input, context, interaction) {
3561
3532
  requestedMode ? `--mode ${requestedMode}` : null,
3562
3533
  requestedNetwork ? `--network ${requestedNetwork}` : null,
3563
3534
  erasesLocalData ? "--erase-local-data" : null,
3564
- "--yes",
3565
3535
  "--no-wait",
3566
3536
  `--idempotency-key ${shellQuote(idempotencyKey)}`,
3567
3537
  ]
@@ -3663,7 +3633,6 @@ async function switchMachine(input, context, interaction) {
3663
3633
  `--mode ${targetMode}`,
3664
3634
  `--network ${targetNetwork}`,
3665
3635
  erasesLocalData ? "--erase-local-data" : null,
3666
- "--yes",
3667
3636
  `--idempotency-key ${shellQuote(interaction.idempotencyKey())}`,
3668
3637
  ]
3669
3638
  .filter(Boolean)
@@ -3781,7 +3750,6 @@ async function maintainMachine(command, input, context, interaction) {
3781
3750
  `ornn machines ${command}`,
3782
3751
  shellQuote(machineId(machine)),
3783
3752
  destructive ? "--erase-local-data" : null,
3784
- "--yes",
3785
3753
  "--no-wait",
3786
3754
  `--idempotency-key ${shellQuote(idempotencyKey)}`,
3787
3755
  ]
@@ -3874,7 +3842,6 @@ async function maintainMachine(command, input, context, interaction) {
3874
3842
  `ornn machines ${command}`,
3875
3843
  shellQuote(machineId(waitOutcome.value)),
3876
3844
  destructive ? "--erase-local-data" : null,
3877
- "--yes",
3878
3845
  `--idempotency-key ${shellQuote(interaction.idempotencyKey())}`,
3879
3846
  ]
3880
3847
  .filter(Boolean)
@@ -44,11 +44,13 @@ export const adminEnrollmentTokensListCapability = {
44
44
  };
45
45
  },
46
46
  };
47
- export const adminEnrollmentTokenCreateCapability = withVnextBinding(tokensCreateCapability, adminHelp(["admin", "enrollment-tokens", "create"], "Create one expiring enrollment token through an explicit secret export.", "ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite] [--yes]", "Unsupported: token creation requires an eligible human terminal.", ["canonical supplier organization UUID"], "Review the supplier, expiry, and secret destination before minting the token."));
47
+ export const adminEnrollmentTokenCreateCapability = withVnextBinding(tokensCreateCapability, adminHelp(["admin", "enrollment-tokens", "create"], "Create one expiring enrollment token through an explicit secret export.", "ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite] []", "Unsupported: token creation requires an eligible human terminal.", ["canonical supplier organization UUID"], "Review the supplier, expiry, and secret destination before minting the token."));
48
48
  const tokenRevokeInput = z.object({
49
- token_id: UUID,
49
+ tokenId: z.string().min(1),
50
+ token_id: UUID.optional(),
50
51
  expected_created_at: nonEmpty.optional(),
51
52
  idempotency_key: nonEmpty.optional(),
53
+ confirm: z.boolean().optional(),
52
54
  });
53
55
  function rowsFromTokenList(payload) {
54
56
  if (Array.isArray(payload))
@@ -104,22 +106,23 @@ async function readExactToken(ctx, tokenId, expectedCreatedAt) {
104
106
  return { disposition: "apply", target };
105
107
  }
106
108
  export const adminEnrollmentTokenRevokeCapability = defineCapability({
107
- id: "tokens.revoke",
109
+ id: "enrollment.token.revoke",
108
110
  domain: "fleet",
109
111
  roles: ["admin"],
110
- description: "Revoke one exact enrollment-token generation after reconciling its durable goal.",
112
+ description: "Internal staff only: revoke one exact enrollment-token generation after reconciling its durable goal.",
111
113
  mutation: "preview-confirm",
112
- http: { method: "DELETE", path: "/enrollment-tokens/{id}" },
114
+ http: { method: "DELETE", path: "/enrollment-tokens/{tokenId}" },
113
115
  cli: {
114
116
  command: ["admin", "enrollment-tokens", "revoke"],
115
- vnext: adminHelp(["admin", "enrollment-tokens", "revoke"], "Revoke one exact enrollment-token generation.", "ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] [--yes]", "ornn admin enrollment-tokens revoke <token-id> --expected-created-at TIME --yes --idempotency-key KEY --format json", ["canonical token UUID", "token created_at generation"], "Review the exact token generation and the loss of future enrollment access."),
117
+ vnext: adminHelp(["admin", "enrollment-tokens", "revoke"], "Revoke one exact enrollment-token generation.", "ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] []", "ornn admin enrollment-tokens revoke <token-id> --expected-created-at TIME --idempotency-key KEY --format json", ["canonical token UUID", "token created_at generation"], "Review the exact token generation and the loss of future enrollment access."),
116
118
  },
117
- mcp: false,
118
119
  slack: false,
120
+ mcp: { name: "enrollment_token_revoke" },
119
121
  input: tokenRevokeInput,
120
122
  execute: async (ctx, input) => {
121
123
  const parsed = tokenRevokeInput.parse(input);
122
- const current = await readExactToken(ctx, parsed.token_id, parsed.expected_created_at);
124
+ const tokenId = parsed.tokenId ?? parsed.token_id;
125
+ const current = await readExactToken(ctx, tokenId, parsed.expected_created_at);
123
126
  if (!ctx.confirmed)
124
127
  return current;
125
128
  if (current.disposition === "conflict") {
@@ -127,23 +130,21 @@ export const adminEnrollmentTokenRevokeCapability = defineCapability({
127
130
  }
128
131
  if (current.disposition === "satisfied") {
129
132
  return {
130
- id: parsed.token_id,
133
+ id: tokenId,
131
134
  created_at: parsed.expected_created_at ?? current.target?.created_at ?? null,
132
135
  status: "revoked",
133
136
  reconciled: true,
134
137
  };
135
138
  }
136
139
  if (!current.target.created_at) {
137
- throw codedError("token_identity_unavailable", `Enrollment token ${parsed.token_id} has no stable creation identity and cannot be revoked safely.`);
140
+ throw codedError("token_identity_unavailable", `Enrollment token ${tokenId} has no stable creation identity and cannot be revoked safely.`);
138
141
  }
139
- const revoked = (await ctx.fetch(`/enrollment-tokens/${encodeURIComponent(parsed.token_id)}`, {
142
+ const revoked = (await ctx.fetch(`/enrollment-tokens/${encodeURIComponent(tokenId)}`, {
140
143
  method: "DELETE",
141
- ...(parsed.idempotency_key
142
- ? { headers: { "Idempotency-Key": parsed.idempotency_key } }
143
- : {}),
144
+ ...(parsed.idempotency_key ? { headers: { "Idempotency-Key": parsed.idempotency_key } } : {}),
144
145
  }));
145
146
  return {
146
- id: parsed.token_id,
147
+ id: tokenId,
147
148
  created_at: current.target.created_at,
148
149
  organization_id: current.target.organization_id,
149
150
  expires_at: current.target.expires_at,
@@ -190,7 +191,7 @@ export const adminNodeEnrollCapability = defineCapability({
190
191
  http: { method: "POST", path: "/remote" },
191
192
  cli: {
192
193
  command: ["admin", "nodes", "enroll"],
193
- vnext: adminHelp(["admin", "nodes", "enroll"], "Install Ornn management on one explicitly assigned node.", "ornn admin nodes enroll <node-id> --host HOST --supplier UUID --facility UUID [--filter COMPARTMENT_IDS] --identity-file PATH --token-ttl SECONDS --replace-existing-management [--verbose] [--yes]", "Unsupported: remote enrollment requires an eligible human terminal.", ["canonical node UUID", "canonical supplier organization UUID", "canonical facility UUID"], "Review node identity, SSH target, assignment, compartment filter, and replacement of existing management."),
194
+ vnext: adminHelp(["admin", "nodes", "enroll"], "Install Ornn management on one explicitly assigned node.", "ornn admin nodes enroll <node-id> --host HOST --supplier UUID --facility UUID [--filter COMPARTMENT_IDS] --identity-file PATH --token-ttl SECONDS --replace-existing-management [--verbose] []", "Unsupported: remote enrollment requires an eligible human terminal.", ["canonical node UUID", "canonical supplier organization UUID", "canonical facility UUID"], "Review node identity, SSH target, assignment, compartment filter, and replacement of existing management."),
194
195
  },
195
196
  mcp: false,
196
197
  slack: false,
@@ -291,7 +292,7 @@ export const adminNodeRotateKeyCapability = defineCapability({
291
292
  http: { method: "POST", path: "/enrollment/admin-ssh-key" },
292
293
  cli: {
293
294
  command: ["admin", "nodes", "rotate-key"],
294
- vnext: adminHelp(["admin", "nodes", "rotate-key"], "Rotate one node admin key and export the replacement deliberately.", "ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite] [--yes]", "Unsupported: key rotation requires an eligible human terminal.", ["canonical node UUID"], "Review the node and loss of access through the previous private key."),
295
+ vnext: adminHelp(["admin", "nodes", "rotate-key"], "Rotate one node admin key and export the replacement deliberately.", "ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite] []", "Unsupported: key rotation requires an eligible human terminal.", ["canonical node UUID"], "Review the node and loss of access through the previous private key."),
295
296
  },
296
297
  mcp: false,
297
298
  slack: false,