@ornncompute/cli 1.0.75 → 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 (52) hide show
  1. package/README.md +32 -5
  2. package/package.json +4 -2
  3. package/src/admin-capacity-reservations.mjs +4 -1
  4. package/src/admin-command-handlers.mjs +2 -0
  5. package/src/admin-node-diagnostics.mjs +1 -5
  6. package/src/admin-node-lifecycle.mjs +23 -125
  7. package/src/admin-node-sanitize.mjs +243 -0
  8. package/src/catalog-shell.mjs +6 -6
  9. package/src/evidence-commands.mjs +4 -6
  10. package/src/external-bucket-commands.mjs +71 -23
  11. package/src/interaction-runtime.mjs +3 -0
  12. package/src/kubernetes-commands.mjs +16 -83
  13. package/src/machine-reference.mjs +33 -56
  14. package/src/slurm-commands.mjs +13 -2
  15. package/src/vm-commands.mjs +39 -59
  16. package/vendor/api-client/commerce.js +743 -0
  17. package/vendor/capabilities/cli.js +7688 -7
  18. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
  19. package/vendor/capabilities/capabilities/admin-enrollment.js +0 -322
  20. package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
  21. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -137
  22. package/vendor/capabilities/capabilities/admin.js +0 -91
  23. package/vendor/capabilities/capabilities/cli.js +0 -156
  24. package/vendor/capabilities/capabilities/clusters.js +0 -248
  25. package/vendor/capabilities/capabilities/commerce-market.js +0 -405
  26. package/vendor/capabilities/capabilities/compute.js +0 -523
  27. package/vendor/capabilities/capabilities/credits.js +0 -83
  28. package/vendor/capabilities/capabilities/evidence.js +0 -145
  29. package/vendor/capabilities/capabilities/external-buckets.js +0 -191
  30. package/vendor/capabilities/capabilities/fleet.js +0 -386
  31. package/vendor/capabilities/capabilities/identity.js +0 -129
  32. package/vendor/capabilities/capabilities/listings.js +0 -398
  33. package/vendor/capabilities/capabilities/locality.js +0 -42
  34. package/vendor/capabilities/capabilities/market-shared.js +0 -46
  35. package/vendor/capabilities/capabilities/markets.js +0 -482
  36. package/vendor/capabilities/capabilities/mounts.js +0 -176
  37. package/vendor/capabilities/capabilities/native-listing.js +0 -99
  38. package/vendor/capabilities/capabilities/networks.js +0 -1264
  39. package/vendor/capabilities/capabilities/observability.js +0 -253
  40. package/vendor/capabilities/capabilities/organization-invites.js +0 -53
  41. package/vendor/capabilities/capabilities/reservation-list.js +0 -68
  42. package/vendor/capabilities/capabilities/staff-shared.js +0 -64
  43. package/vendor/capabilities/capabilities/tokens.js +0 -161
  44. package/vendor/capabilities/capabilities/users.js +0 -157
  45. package/vendor/capabilities/capabilities/volumes.js +0 -178
  46. package/vendor/capabilities/capability.js +0 -99
  47. package/vendor/capabilities/cli-catalog.js +0 -73
  48. package/vendor/capabilities/cli-migrations.js +0 -264
  49. package/vendor/capabilities/names.js +0 -6
  50. package/vendor/capabilities/software-catalog.js +0 -82
  51. package/vendor/capabilities/spec-catalog.js +0 -151
  52. package/vendor/capabilities/supplier-directory.js +0 -20
@@ -818,13 +818,13 @@ function parseCapabilityInput(capability, rawInput) {
818
818
  });
819
819
  }
820
820
 
821
- // Commerce's market routes and Markets serve every caller on one path and
822
- // decide from the session what it may do; a staff session is an admin there.
823
- const ROLE_AWARE_PREFIXES = ["/commerce/v2/listings", "/commerce/reservations", "/v1/markets"];
821
+ // Native Commerce and Markets routes authenticate the caller at the gateway.
822
+ // Commerce's /internal/v1 routes additionally require staff.
823
+ const DIRECT_GATEWAY_PREFIXES = ["/commerce", "/internal/v1", "/v1/markets"];
824
824
 
825
- function isRoleAwarePath(path) {
825
+ function isDirectGatewayPath(path) {
826
826
  const pathname = String(path).split("?")[0];
827
- return ROLE_AWARE_PREFIXES.some(
827
+ return DIRECT_GATEWAY_PREFIXES.some(
828
828
  (prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`)
829
829
  );
830
830
  }
@@ -839,7 +839,7 @@ function adminFetch(io, path, init = {}) {
839
839
  ...(init.headers !== undefined ? { headers: init.headers } : {}),
840
840
  ...((init.signal ?? io.signal) !== undefined ? { signal: init.signal ?? io.signal } : {}),
841
841
  };
842
- return isRoleAwarePath(path) || (path === "/v1/staff/invites" && request.method === "POST")
842
+ return isDirectGatewayPath(path) || (path === "/v1/staff/invites" && request.method === "POST")
843
843
  ? cliRequest(request)
844
844
  : operatorRequest(request);
845
845
  }
@@ -264,15 +264,13 @@ function reachability(value) {
264
264
  if (explicit === true) return { state: "reachable", observed_at: null };
265
265
  if (explicit === false) return { state: "unreachable", observed_at: null };
266
266
  const candidate = record(source.reachability);
267
- const offGrid = record(source.off_grid_reachability);
268
- const state = stateToken(candidate.state ?? candidate.status ?? offGrid.state ?? offGrid.status);
269
- const phase = stateToken(candidate.phase ?? offGrid.phase);
270
- const observedAt =
271
- text(candidate.observed_at) ?? text(candidate.checked_at) ?? text(offGrid.last_checked_at);
267
+ const state = stateToken(candidate.state ?? candidate.status);
268
+ const phase = stateToken(candidate.phase);
269
+ const observedAt = text(candidate.observed_at) ?? text(candidate.checked_at);
272
270
  if (["reachable", "live", "online"].includes(state) || phase === "ssh_ready")
273
271
  return { state: "reachable", observed_at: observedAt };
274
272
  if (
275
- ["unreachable", "offline", "off_grid"].includes(state) ||
273
+ ["unreachable", "offline"].includes(state) ||
276
274
  ["ssh_unreachable", "unreachable"].includes(phase)
277
275
  )
278
276
  return { state: "unreachable", observed_at: observedAt };
@@ -5,6 +5,7 @@ import { CliApiError, computeEndpoint } from "./api-client.mjs";
5
5
  import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
6
6
  import {
7
7
  codeOf,
8
+ detailOf,
8
9
  fetchDrives,
9
10
  fetchDrivePage,
10
11
  idempotencyHeaders,
@@ -83,6 +84,26 @@ function verificationCode(source) {
83
84
  return typeof code === "string" && /^[A-Za-z][A-Za-z0-9_.-]{0,127}$/.test(code) ? code : null;
84
85
  }
85
86
 
87
+ const TRANSFER_ACCOUNT_PATTERN = /^[a-z][a-z0-9-]{4,28}[a-z0-9]@[a-z][a-z0-9-]{4,28}[a-z0-9]\.iam\.gserviceaccount\.com$/;
88
+
89
+ function safeTransferAccount(value) {
90
+ return typeof value === "string" && TRANSFER_ACCOUNT_PATTERN.test(value) ? value : null;
91
+ }
92
+
93
+ function gcsGrantPresentation(safe) {
94
+ const account = safe.access.transfer_account;
95
+ if (safe.kind !== "gcs" || !account) return null;
96
+ const grant = (role) =>
97
+ ` gcloud storage buckets add-iam-policy-binding gs://${safe.location.bucket} --member=serviceAccount:${account} --role=${role}`;
98
+ return [
99
+ `Your organization's Ornn transfer account: ${account}`,
100
+ "Grant it read access to the bucket:",
101
+ grant("roles/storage.objectViewer"),
102
+ grant("roles/storage.legacyBucketReader"),
103
+ "Then deploy the volume to a reservation from the Ornn console.",
104
+ ];
105
+ }
106
+
86
107
  function safeExternalBucket(drive, { forceUnverified = false } = {}) {
87
108
  const source = drive?.source ?? {};
88
109
  const kind = externalKind(drive);
@@ -110,6 +131,9 @@ function safeExternalBucket(drive, { forceUnverified = false } = {}) {
110
131
  access: {
111
132
  mode: source.read_only === false ? "read-write" : "read-only",
112
133
  credentials_configured: kind === "gcs" ? false : source.credentials_configured === true,
134
+ ...(kind === "gcs"
135
+ ? { transfer_account: safeTransferAccount(source.transfer_service_account) }
136
+ : {}),
113
137
  },
114
138
  verification: {
115
139
  phase,
@@ -448,13 +472,18 @@ function safeExternalError(error, fallback, { action, forcePartial = false } = {
448
472
  }
449
473
  const code = codeOf(error);
450
474
  const unavailable = error?.status === undefined || error?.status >= 500;
451
- return new StorageCommandError(SAFE_EXTERNAL_ERRORS[code] ?? fallback, {
452
- action,
453
- code: code ?? (unavailable ? "service_unavailable" : "external_bucket_operation_failed"),
454
- exitCode: forcePartial ? 4 : unavailable ? 6 : 1,
455
- outcomeClass: forcePartial ? "partial" : unavailable ? "failed" : "failed",
456
- retryable: forcePartial || unavailable,
457
- });
475
+ const ownerMessage = detailOf(error)?.message;
476
+ return new StorageCommandError(
477
+ SAFE_EXTERNAL_ERRORS[code] ??
478
+ (typeof ownerMessage === "string" && ownerMessage.trim() ? ownerMessage : fallback),
479
+ {
480
+ action,
481
+ code: code ?? (unavailable ? "service_unavailable" : "external_bucket_operation_failed"),
482
+ exitCode: forcePartial ? 4 : unavailable ? 6 : 1,
483
+ outcomeClass: forcePartial ? "partial" : unavailable ? "failed" : "failed",
484
+ retryable: forcePartial || unavailable,
485
+ }
486
+ );
458
487
  }
459
488
 
460
489
  async function listBuckets(input, runtime) {
@@ -510,13 +539,20 @@ async function connectBucket(input, runtime) {
510
539
  class: safe.verification.phase === "failed" ? "failed" : "ready",
511
540
  phase: "replayed",
512
541
  },
513
- presentation: {
514
- lines: [
515
- `External bucket connection replayed: ${safe.name} (${safe.id})`,
516
- "Connection and verification are separate operations.",
517
- ],
518
- next: `ornn external-buckets verify ${safe.id}`,
519
- },
542
+ presentation: gcsGrantPresentation(safe)
543
+ ? {
544
+ lines: [
545
+ `External bucket connection replayed: ${safe.name} (${safe.id})`,
546
+ ...gcsGrantPresentation(safe),
547
+ ],
548
+ }
549
+ : {
550
+ lines: [
551
+ `External bucket connection replayed: ${safe.name} (${safe.id})`,
552
+ "Connection and verification are separate operations.",
553
+ ],
554
+ next: `ornn external-buckets verify ${safe.id}`,
555
+ },
520
556
  });
521
557
  }
522
558
 
@@ -547,13 +583,20 @@ async function connectBucket(input, runtime) {
547
583
  return runtime.interaction.result({
548
584
  data: { external_bucket: safe, replayed: false, verification_required: true },
549
585
  outcome: { class: "ready", phase: "connected" },
550
- presentation: {
551
- lines: [
552
- `External bucket connected: ${safe.name} (${safe.id})`,
553
- "Connection recorded; access has not been verified.",
554
- ],
555
- next: `ornn external-buckets verify ${safe.id}`,
556
- },
586
+ presentation: gcsGrantPresentation(safe)
587
+ ? {
588
+ lines: [
589
+ `External bucket connected: ${safe.name} (${safe.id})`,
590
+ ...gcsGrantPresentation(safe),
591
+ ],
592
+ }
593
+ : {
594
+ lines: [
595
+ `External bucket connected: ${safe.name} (${safe.id})`,
596
+ "Connection recorded; access has not been verified.",
597
+ ],
598
+ next: `ornn external-buckets verify ${safe.id}`,
599
+ },
557
600
  });
558
601
  }
559
602
 
@@ -1282,7 +1325,9 @@ export function writeExternalBucketResultHuman(stdout, stderr, capabilityId, exe
1282
1325
  }
1283
1326
  for (const bucket of rows) {
1284
1327
  stdout.write(
1285
- `${bucket.name} ${bucket.kind} ${bucket.verification.phase} ${bucket.access.mode} ${bucket.id}\n`
1328
+ bucket.kind === "gcs"
1329
+ ? `${bucket.name} ${bucket.kind} ${bucket.access.mode} ${bucket.id}\n`
1330
+ : `${bucket.name} ${bucket.kind} ${bucket.verification.phase} ${bucket.access.mode} ${bucket.id}\n`
1286
1331
  );
1287
1332
  }
1288
1333
  if (execution.page?.next_cursor) {
@@ -1300,7 +1345,10 @@ export function writeExternalBucketResultHuman(stdout, stderr, capabilityId, exe
1300
1345
  stdout.write(`Bucket: ${bucket.location.bucket}\n`);
1301
1346
  if (bucket.location.prefix) stdout.write(`Prefix: ${bucket.location.prefix}\n`);
1302
1347
  stdout.write(`Access: ${bucket.access.mode}\n`);
1303
- stdout.write(`Verification: ${bucket.verification.phase}\n`);
1348
+ if (bucket.access.transfer_account) {
1349
+ stdout.write(`Transfer account: ${bucket.access.transfer_account}\n`);
1350
+ }
1351
+ if (bucket.kind !== "gcs") stdout.write(`Verification: ${bucket.verification.phase}\n`);
1304
1352
  }
1305
1353
  for (const error of execution.errors ?? []) stderr.write(`${error.message}\n`);
1306
1354
  }
@@ -4,7 +4,9 @@ import { createInterface } from "node:readline/promises";
4
4
  import { resolveAuthBaseUrl } from "./device-auth.mjs";
5
5
 
6
6
  const BOOLEAN_OPTIONS = new Set([
7
+ "acknowledge-retained-os",
7
8
  "all-nodes",
9
+ "data-drives-sanitized",
8
10
  "erase-local-data",
9
11
  "force",
10
12
  "follow",
@@ -17,6 +19,7 @@ const BOOLEAN_OPTIONS = new Set([
17
19
  "read-only",
18
20
  "read-write",
19
21
  "recursive",
22
+ "tenant-access-revoked",
20
23
  "wait",
21
24
  ]);
22
25
 
@@ -1,3 +1,4 @@
1
+ import { getReservation } from "../vendor/api-client/commerce.js";
1
2
  import { createHash } from "node:crypto";
2
3
 
3
4
  import {
@@ -8,7 +9,7 @@ import {
8
9
  import { isRecoverableWaitFailure } from "./interaction-runtime.mjs";
9
10
  import { waitForGoal as waitForReadGoal } from "./wait-runtime.mjs";
10
11
  import { gpuLabeler } from "./gpu-catalog.mjs";
11
- import { reservationLabeler } from "./machine-reference.mjs";
12
+ import { listActiveReservations, reservationLabeler } from "./machine-reference.mjs";
12
13
  import { keyInputShapePreflight, keyPlanFromInput, materializeKey } from "./vm-commands.mjs";
13
14
 
14
15
  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;
@@ -51,8 +52,7 @@ function clusterNativeAccess(accessMode) {
51
52
  return accessMode === "slurm" ? "SSH login" : "kubeconfig";
52
53
  }
53
54
  function reservationAccessMode(value) {
54
- const mode = cleanState(record(value).access_mode ?? record(value).accessMode);
55
- return mode === "virtual-machine" ? "vm" : mode;
55
+ return text(record(value).provisioningType);
56
56
  }
57
57
 
58
58
  function clusterConnectCommand(cluster) {
@@ -736,17 +736,13 @@ async function waitForClusterGoal({
736
736
  }
737
737
 
738
738
  async function fetchReservation(fetch, runtime, reservationId, accessMode = "kubernetes") {
739
- const reservation = record(
740
- await fetch(`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`, {
741
- method: "GET",
742
- })
743
- );
744
- if (text(reservation.id) !== reservationId) {
739
+ const reservation = record(await getReservation(fetch, reservationId));
740
+ if (text(reservation.reservationId) !== reservationId) {
745
741
  runtime.error("The gateway returned a different capacity reservation.", {
746
742
  code: "reservation_identity_changed",
747
743
  });
748
744
  }
749
- const status = cleanState(reservation.status ?? reservation.state);
745
+ const status = cleanState(reservation.status);
750
746
  if (reservationAccessMode(reservation) !== accessMode) {
751
747
  runtime.error(
752
748
  `The selected capacity reservation is not reserved for ${clusterModeLabel(accessMode)}.`,
@@ -766,50 +762,6 @@ async function fetchReservation(fetch, runtime, reservationId, accessMode = "kub
766
762
  return reservation;
767
763
  }
768
764
 
769
- async function fetchActiveReservations(fetch, runtime) {
770
- const reservations = [];
771
- const seen = new Set();
772
- let cursor;
773
- for (let page = 0; page < 20; page += 1) {
774
- const query = new URLSearchParams({ limit: "500", state: "active" });
775
- if (cursor) query.set("cursor", cursor);
776
- const payload = await fetch(`/v1/workflows/commerce/reservations?${query}`, {
777
- method: "GET",
778
- });
779
- const source = record(payload);
780
- const rows = Array.isArray(payload)
781
- ? payload
782
- : Array.isArray(source.reservations)
783
- ? source.reservations
784
- : null;
785
- if (!rows) {
786
- runtime.error("Capacity reservations returned an unsafe response.", {
787
- code: "reservation_projection_invalid",
788
- });
789
- }
790
- reservations.push(
791
- ...rows.filter((value) => {
792
- const reservation = record(value);
793
- return (
794
- UUID.test(text(reservation.id) ?? "") &&
795
- cleanState(reservation.status ?? reservation.state) === "active"
796
- );
797
- })
798
- );
799
- const next = text(source.next_cursor);
800
- if (!next) return reservations;
801
- if (seen.has(next))
802
- runtime.error("Capacity-reservation pagination did not advance.", {
803
- code: "reservation_pagination_invalid",
804
- });
805
- seen.add(next);
806
- cursor = next;
807
- }
808
- runtime.error("Capacity-reservation pagination exceeded its safety bound.", {
809
- code: "reservation_pagination_invalid",
810
- });
811
- }
812
-
813
765
  async function resolveLaunchReservation(context, runtime, input, accessMode) {
814
766
  const positionals = input.$positionals ?? [];
815
767
  if (positionals.length > 1) {
@@ -838,7 +790,7 @@ async function resolveLaunchReservation(context, runtime, input, accessMode) {
838
790
  field: "reservation",
839
791
  });
840
792
  }
841
- const active = (await fetchActiveReservations(context.fetch, runtime)).filter(
793
+ const active = (await listActiveReservations(context, runtime)).filter(
842
794
  (reservation) => reservationAccessMode(reservation) === accessMode
843
795
  );
844
796
  if (active.length === 0) {
@@ -850,14 +802,14 @@ async function resolveLaunchReservation(context, runtime, input, accessMode) {
850
802
  );
851
803
  }
852
804
  if (active.length === 1) {
853
- return fetchReservation(context.fetch, runtime, text(record(active[0]).id), accessMode);
805
+ return fetchReservation(context.fetch, runtime, text(active[0].reservationId), accessMode);
854
806
  }
855
807
  const reservationLabel = await reservationLabeler(context.fetch);
856
808
  const selected = await runtime.ask({
857
809
  label: "Capacity reservation",
858
810
  choices: active.flatMap((value) => {
859
811
  const reservation = record(value);
860
- const id = text(reservation.id);
812
+ const id = text(reservation.reservationId);
861
813
  return id ? [{ label: reservationLabel(reservation), value: id }] : [];
862
814
  }),
863
815
  });
@@ -1124,7 +1076,7 @@ export async function launchClusterMode(
1124
1076
  const organization = await selectedOrganization(context, runtime);
1125
1077
  const keyPlan = accessMode === "slurm" ? await keyPlanFromInput(context, input, runtime) : null;
1126
1078
  const reservation = await resolveLaunchReservation(context, runtime, input, accessMode);
1127
- const reservationId = text(reservation.id);
1079
+ const reservationId = text(reservation.reservationId);
1128
1080
  const initialExisting = await existingReservationCluster(
1129
1081
  context.fetch,
1130
1082
  runtime,
@@ -1810,44 +1762,25 @@ export async function executeKubernetesClusterCommand(command, input, deps) {
1810
1762
  return handler(input, deps);
1811
1763
  }
1812
1764
 
1813
- function projectReservationFacts(value, expectedId, runtime) {
1814
- const reservation = record(value);
1815
- const id = text(reservation.id);
1816
- if (id !== expectedId) {
1765
+ export async function reservationFactsForCluster(fetch, runtime, cluster) {
1766
+ const reservation = await getReservation(fetch, cluster.reservation_id);
1767
+ if (reservation.reservationId !== cluster.reservation_id) {
1817
1768
  runtime.error("The gateway returned facts for a different capacity reservation.", {
1818
1769
  code: "reservation_identity_changed",
1819
1770
  });
1820
1771
  }
1821
- return {
1822
- id,
1823
- facility:
1824
- text(reservation.facility_name) ??
1825
- text(reservation.site_nickname) ??
1826
- text(reservation.facility),
1827
- hardware: text(reservation.gpu_type) ?? text(reservation.hardware),
1828
- machine_count: integer(reservation.node_count) ?? integer(reservation.machines),
1829
- region: text(reservation.region),
1830
- };
1831
- }
1832
-
1833
- export async function reservationFactsForCluster(fetch, runtime, cluster) {
1834
- const payload = await fetch(
1835
- `/v1/workflows/commerce/reservations/${encodeURIComponent(cluster.reservation_id)}`,
1836
- { method: "GET" }
1837
- );
1838
- return projectReservationFacts(payload, cluster.reservation_id, runtime);
1772
+ return reservation;
1839
1773
  }
1840
1774
 
1841
1775
  export function clusterFactLabel(cluster, reservation, gpuLabel) {
1842
- const location = reservation.facility ?? reservation.region ?? "location unknown";
1843
- const gpu = reservation.hardware ?? cluster.gpu_type;
1776
+ const gpu = reservation.gpuSlug ?? (reservation.reservedGpuCount === 0 ? "cpu" : null);
1844
1777
  const hardware = gpu ? gpuLabel(gpu) : "hardware unknown";
1845
1778
  const nodeCount =
1846
1779
  integer(cluster.membership_count) ??
1847
1780
  integer(cluster.node_count) ??
1848
1781
  (Array.isArray(cluster.machines) ? cluster.machines.length : 0);
1849
1782
  const lifecycle = text(record(cluster.lifecycle).class) ?? text(cluster.state) ?? "unknown";
1850
- return `${location} · ${clusterModeLabel(cluster.access_mode)} · ${hardware} · ${nodeCount} nodes · ${lifecycle}`;
1783
+ return `${clusterModeLabel(cluster.access_mode)} · ${hardware} · ${nodeCount} nodes · ${lifecycle}`;
1851
1784
  }
1852
1785
  const clusterConnectCandidatesCache = new WeakMap();
1853
1786
 
@@ -1,8 +1,7 @@
1
+ import { listAllReservations } from "../vendor/api-client/commerce.js";
1
2
  import { gpuLabeler } from "./gpu-catalog.mjs";
2
3
 
3
4
  const CANONICAL_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
4
- const PAGE_SIZE = 500;
5
- const MAX_RESERVATION_PAGES = 20;
6
5
  const TERMINAL_MACHINE_STATES = new Set([
7
6
  "canceled",
8
7
  "cancelled",
@@ -31,18 +30,12 @@ function rows(payload, field) {
31
30
  }
32
31
 
33
32
  function reservationId(value) {
34
- const source = record(value);
35
- return (
36
- text(source.id) ??
37
- text(source.ref) ??
38
- text(source.reservation_id) ??
39
- text(source.commerce_reservation_id)
40
- );
33
+ return text(value.reservationId);
41
34
  }
42
35
 
43
36
  function reservationMode(value) {
44
37
  const source = record(value);
45
- const mode = text(source.access_mode) ?? text(source.accessMode);
38
+ const mode = text(source.provisioningType);
46
39
  return mode?.toLowerCase() === "virtual-machine" ? "vm" : (mode?.toLowerCase() ?? null);
47
40
  }
48
41
 
@@ -63,21 +56,16 @@ function reservationModeLabel(value) {
63
56
 
64
57
  export function reservationChoiceLabel(value, gpuLabel) {
65
58
  const reservation = record(value);
66
- const gpu = text(reservation.gpu_type);
67
- const hardware = gpu ? gpuLabel(gpu) : (text(reservation.hardware) ?? "GPU capacity");
68
- const quantity = Number(reservation.node_count ?? reservation.machine_count ?? 1);
59
+ // CPU capacity has no GPU type and no GPUs; the labeler names it CPU.
60
+ const gpu = text(reservation.gpuSlug) ?? (reservation.reservedGpuCount === 0 ? "cpu" : null);
61
+ const hardware = gpu ? gpuLabel(gpu) : "GPU capacity";
62
+ const quantity = reservation.reservedNodeCount;
69
63
  const machines = Number.isInteger(quantity) && quantity > 0 ? quantity : 1;
70
- const location =
71
- text(reservation.site_nickname) ??
72
- text(reservation.facility) ??
73
- text(reservation.location) ??
74
- text(reservation.region) ??
75
- "location unreported";
76
- const start = text(reservation.start_at) ?? text(reservation.start_date);
77
- const end = text(reservation.end_at) ?? text(reservation.end_date);
78
- const createdAt = text(reservation.created_at);
64
+ const start = text(reservation.startsAt);
65
+ const end = text(reservation.endsAt);
66
+ const createdAt = text(reservation.createdAt);
79
67
  return [
80
- `${hardware}, ${machines} machine${machines === 1 ? "" : "s"}, ${location}`,
68
+ `${hardware}, ${machines} machine${machines === 1 ? "" : "s"}`,
81
69
  reservationModeLabel(reservation),
82
70
  start && end ? `${start} to ${end}` : null,
83
71
  createdAt ? `reserved ${createdAt}` : null,
@@ -91,13 +79,6 @@ export async function reservationLabeler(fetch) {
91
79
  return (reservation) => reservationChoiceLabel(reservation, gpuLabel);
92
80
  }
93
81
 
94
- function isActiveDirectReservation(value) {
95
- const source = record(value);
96
- const status = (text(source.status) ?? text(source.state) ?? "").toLowerCase();
97
- const mode = reservationMode(source);
98
- return status === "active" && (!mode || mode === "vm" || mode === "bare-metal");
99
- }
100
-
101
82
  export function canonicalMachineId(value) {
102
83
  const source = record(value);
103
84
  return (
@@ -130,35 +111,31 @@ function invalidGateway(interaction, message) {
130
111
  });
131
112
  }
132
113
 
133
- export async function listActiveDirectReservations(context, interaction) {
134
- const reservations = [];
135
- const seen = new Set();
136
- let cursor = null;
137
- for (let page = 0; page < MAX_RESERVATION_PAGES; page += 1) {
138
- const query = new URLSearchParams({ limit: String(PAGE_SIZE), status: "active" });
139
- if (cursor) query.set("cursor", cursor);
140
- const payload = await context.fetch(`/v1/workflows/commerce/reservations?${query}`, {
141
- method: "GET",
114
+ export async function listActiveReservations(context, interaction) {
115
+ const session = context.cli?.session
116
+ ? (await context.cli.session("context", {})).data
117
+ : await context.fetch("/v1/cli/session");
118
+ const customerId = text((session.selectedOrganization ?? session.selected_organization)?.id);
119
+ if (!customerId) {
120
+ interaction.usage("Select an organization before choosing a reservation.", {
121
+ code: "organization_required",
122
+ field: "organization",
142
123
  });
143
- const pageRows = rows(payload, "reservations");
144
- if (!pageRows)
145
- invalidGateway(interaction, "The gateway returned an invalid capacity-reservation page.");
146
- reservations.push(...pageRows.map(record).filter(isActiveDirectReservation));
147
-
148
- const source = record(payload);
149
- const next = text(source.next_cursor) ?? text(source.nextCursor);
150
- if (!next || Array.isArray(payload)) return reservations;
151
- if (seen.has(next)) {
152
- invalidGateway(interaction, "Capacity-reservation pagination repeated a cursor.");
153
- }
154
- seen.add(next);
155
- cursor = next;
156
124
  }
157
- interaction.error("Machine resolution exceeded its bounded capacity-reservation search.", {
158
- code: "resource_search_incomplete",
159
- exitCode: 6,
160
- retryable: true,
125
+ // Native Commerce permits staff to read all organizations. Customer pickers
126
+ // retain the selected-organization scope of the published CLI workflow.
127
+ const { reservations } = await listAllReservations(context.fetch, {
128
+ customerId,
129
+ status: "active",
161
130
  });
131
+ return reservations.filter((reservation) => reservation.status === "active");
132
+ }
133
+
134
+ export async function listActiveDirectReservations(context, interaction) {
135
+ return (await listActiveReservations(context, interaction)).filter(
136
+ ({ provisioningType }) =>
137
+ provisioningType === "virtual-machine" || provisioningType === "bare-metal"
138
+ );
162
139
  }
163
140
 
164
141
  export async function listOwnedActiveMachines(context, interaction) {
@@ -33,6 +33,17 @@ function commandText(invocation) {
33
33
  return [invocation.command, ...invocation.args].map(shellQuote).join(" ");
34
34
  }
35
35
 
36
+ // Preserve the CLI result's summary; reads and picker state use Commerce JSON.
37
+ function reservationSummary(reservation) {
38
+ return {
39
+ id: reservation.reservationId,
40
+ facility: null,
41
+ hardware: reservation.gpuSlug ?? (reservation.reservedGpuCount === 0 ? "cpu" : null),
42
+ machine_count: reservation.reservedNodeCount,
43
+ region: null,
44
+ };
45
+ }
46
+
36
47
  function safeLogin(runtime, credentials, input) {
37
48
  const credentialType = text(credentials.credential_type);
38
49
  if (credentialType && credentialType !== "login-ssh") {
@@ -159,7 +170,7 @@ export function createSlurmConnectModeHandler({ spawnProcess = spawnProcessDefau
159
170
  return {
160
171
  action: retryConnectAction(prepared.cluster),
161
172
  data: {
162
- capacity_reservation: prepared.capacityReservation,
173
+ capacity_reservation: reservationSummary(prepared.capacityReservation),
163
174
  cluster: prepared.cluster,
164
175
  private_transport: transport,
165
176
  ssh: null,
@@ -198,7 +209,7 @@ export function createSlurmConnectModeHandler({ spawnProcess = spawnProcessDefau
198
209
  user: login.user,
199
210
  };
200
211
  const data = {
201
- capacity_reservation: prepared.capacityReservation,
212
+ capacity_reservation: reservationSummary(prepared.capacityReservation),
202
213
  cluster: prepared.cluster,
203
214
  private_transport: transport,
204
215
  ssh,