@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
@@ -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,
@@ -402,7 +426,7 @@ function connectionEquivalent(drive, intent) {
402
426
  );
403
427
  }
404
428
 
405
- function requireMutationPreflight(input, runtime, { consent = false } = {}) {
429
+ function requireMutationPreflight(input, runtime) {
406
430
  const noninteractive = runtime.interaction.machine || runtime.interaction.redirected;
407
431
  if (noninteractive && !String(input.idempotencyKey ?? "").trim()) {
408
432
  usage(
@@ -410,23 +434,6 @@ function requireMutationPreflight(input, runtime, { consent = false } = {}) {
410
434
  "idempotency-key"
411
435
  );
412
436
  }
413
- if (noninteractive && consent && input.yes !== true) {
414
- usage(
415
- "Consent is required. Review the exact intent and supply --yes.",
416
- "yes",
417
- "consent_required"
418
- );
419
- }
420
- }
421
-
422
- async function reviewed(runtime, options) {
423
- const accepted = await runtime.interaction.review(options);
424
- if (accepted) return null;
425
- return runtime.interaction.result({
426
- data: null,
427
- exitCode: 1,
428
- outcome: { class: "ended", phase: "declined" },
429
- });
430
437
  }
431
438
 
432
439
  const SAFE_EXTERNAL_ERRORS = {
@@ -465,13 +472,18 @@ function safeExternalError(error, fallback, { action, forcePartial = false } = {
465
472
  }
466
473
  const code = codeOf(error);
467
474
  const unavailable = error?.status === undefined || error?.status >= 500;
468
- return new StorageCommandError(SAFE_EXTERNAL_ERRORS[code] ?? fallback, {
469
- action,
470
- code: code ?? (unavailable ? "service_unavailable" : "external_bucket_operation_failed"),
471
- exitCode: forcePartial ? 4 : unavailable ? 6 : 1,
472
- outcomeClass: forcePartial ? "partial" : unavailable ? "failed" : "failed",
473
- retryable: forcePartial || unavailable,
474
- });
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
+ );
475
487
  }
476
488
 
477
489
  async function listBuckets(input, runtime) {
@@ -493,28 +505,11 @@ async function showBucket(input, runtime) {
493
505
 
494
506
  async function connectBucket(input, runtime) {
495
507
  const intent = await connectionIntent(input);
496
- requireMutationPreflight(input, runtime, { consent: true });
508
+ requireMutationPreflight(input, runtime);
497
509
  const fingerprint =
498
510
  intent.accessKeyId && intent.secretAccessKey
499
511
  ? credentialFingerprint(intent.accessKeyId, intent.secretAccessKey)
500
512
  : null;
501
- const declined = await reviewed(runtime, {
502
- title: "Connect external bucket",
503
- terms: [
504
- { label: "Kind", value: providerName(intent.kind) },
505
- { label: "Name", value: intent.name },
506
- { label: "Bucket", value: intent.bucket },
507
- { label: "Prefix", value: intent.prefix ?? "(bucket root)" },
508
- { label: "Access", value: intent.readOnly ? "read-only" : "read-write" },
509
- {
510
- label: "Credentials",
511
- value: fingerprint ? `fingerprint ${fingerprint.slice(0, 12)}` : "ambient GCS access",
512
- },
513
- { label: "Verification", value: "separate command required" },
514
- ],
515
- prompt: "Connect this external bucket?",
516
- });
517
- if (declined) return declined;
518
513
 
519
514
  const rows = await fetchDrives(runtime);
520
515
  const sameName = bucketRows(rows).filter(
@@ -544,13 +539,20 @@ async function connectBucket(input, runtime) {
544
539
  class: safe.verification.phase === "failed" ? "failed" : "ready",
545
540
  phase: "replayed",
546
541
  },
547
- presentation: {
548
- lines: [
549
- `External bucket connection replayed: ${safe.name} (${safe.id})`,
550
- "Connection and verification are separate operations.",
551
- ],
552
- next: `ornn external-buckets verify ${safe.id}`,
553
- },
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
+ },
554
556
  });
555
557
  }
556
558
 
@@ -581,13 +583,20 @@ async function connectBucket(input, runtime) {
581
583
  return runtime.interaction.result({
582
584
  data: { external_bucket: safe, replayed: false, verification_required: true },
583
585
  outcome: { class: "ready", phase: "connected" },
584
- presentation: {
585
- lines: [
586
- `External bucket connected: ${safe.name} (${safe.id})`,
587
- "Connection recorded; access has not been verified.",
588
- ],
589
- next: `ornn external-buckets verify ${safe.id}`,
590
- },
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
+ },
591
600
  });
592
601
  }
593
602
 
@@ -644,7 +653,7 @@ async function updateCredentials(input, runtime) {
644
653
  );
645
654
  const accessKeyId = requiredText(input.accessKeyId, "--access-key-id", "access-key-id");
646
655
  const secret = await readCredentialFile(input.secretAccessKeyFile);
647
- requireMutationPreflight(input, runtime, { consent: true });
656
+ requireMutationPreflight(input, runtime);
648
657
  const fingerprint = credentialFingerprint(accessKeyId, secret);
649
658
  const source = resolveExternalBucket(await fetchDrives(runtime), reference);
650
659
  const kind = externalKind(source);
@@ -655,17 +664,6 @@ async function updateCredentials(input, runtime) {
655
664
  );
656
665
  }
657
666
  const id = safeIdentifier(source.id);
658
- const declined = await reviewed(runtime, {
659
- title: "Update external bucket credentials",
660
- terms: [
661
- { label: "External bucket", value: `${safeName(source.name)} (${id})` },
662
- { label: "Kind", value: providerName(kind) },
663
- { label: "Credential fingerprint", value: fingerprint.slice(0, 12) },
664
- { label: "Verification", value: "must be run again" },
665
- ],
666
- prompt: "Replace these credentials?",
667
- });
668
- if (declined) return declined;
669
667
  let updated;
670
668
  try {
671
669
  updated = await runtime.fetch(
@@ -698,20 +696,9 @@ async function updateCredentials(input, runtime) {
698
696
 
699
697
  async function disconnectBucket(input, runtime) {
700
698
  const [reference] = positions(input, 1, "ornn external-buckets disconnect <external-bucket>");
701
- requireMutationPreflight(input, runtime, { consent: true });
699
+ requireMutationPreflight(input, runtime);
702
700
  const source = resolveExternalBucket(await fetchDrives(runtime), reference);
703
701
  const safe = safeExternalBucket(source);
704
- const declined = await reviewed(runtime, {
705
- title: "Disconnect external bucket",
706
- terms: [
707
- { label: "External bucket", value: `${safe.name} (${safe.id})` },
708
- { label: "Kind", value: providerName(safe.kind) },
709
- { label: "Source writeback", value: "disabled" },
710
- { label: "Consequence", value: "source access is removed after all mounts are detached" },
711
- ],
712
- prompt: "Disconnect this external bucket?",
713
- });
714
- if (declined) return declined;
715
702
  const attachedReservations = activeMountReservations(source);
716
703
  if (attachedReservations.length > 0) {
717
704
  throw new StorageCommandError(
@@ -1159,7 +1146,7 @@ async function importBucket(input, runtime) {
1159
1146
  usage("Noninteractive --wait requires --timeout <duration>.", "timeout");
1160
1147
  }
1161
1148
  const timeoutMs = parseTimeout(input.timeout);
1162
- requireMutationPreflight(input, runtime, { consent: true });
1149
+ requireMutationPreflight(input, runtime);
1163
1150
 
1164
1151
  const source = resolveExternalBucket(await fetchDrives(runtime), reference);
1165
1152
  const bucket = safeExternalBucket(source);
@@ -1229,18 +1216,6 @@ async function importBucket(input, runtime) {
1229
1216
  : null;
1230
1217
  const replayed = attachment !== null;
1231
1218
  if (!attachment) {
1232
- const declined = await reviewed(runtime, {
1233
- title: "Import external bucket",
1234
- terms: [
1235
- { label: "Source", value: `${providerName(bucket.kind)} ${bucket.location.bucket}` },
1236
- { label: "Working volume", value: `${bucket.name} (${bucket.id})` },
1237
- { label: "Reservation", value: reservationId },
1238
- { label: "Isolation", value: "one Ornn-managed working copy" },
1239
- { label: "Source writeback", value: "disabled" },
1240
- ],
1241
- prompt: "Create this managed working copy?",
1242
- });
1243
- if (declined) return declined;
1244
1219
  try {
1245
1220
  const payload = await runtime.fetch(
1246
1221
  computeEndpoint(
@@ -1350,7 +1325,9 @@ export function writeExternalBucketResultHuman(stdout, stderr, capabilityId, exe
1350
1325
  }
1351
1326
  for (const bucket of rows) {
1352
1327
  stdout.write(
1353
- `${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`
1354
1331
  );
1355
1332
  }
1356
1333
  if (execution.page?.next_cursor) {
@@ -1368,7 +1345,10 @@ export function writeExternalBucketResultHuman(stdout, stderr, capabilityId, exe
1368
1345
  stdout.write(`Bucket: ${bucket.location.bucket}\n`);
1369
1346
  if (bucket.location.prefix) stdout.write(`Prefix: ${bucket.location.prefix}\n`);
1370
1347
  stdout.write(`Access: ${bucket.access.mode}\n`);
1371
- 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`);
1372
1352
  }
1373
1353
  for (const error of execution.errors ?? []) stderr.write(`${error.message}\n`);
1374
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,8 +19,8 @@ const BOOLEAN_OPTIONS = new Set([
17
19
  "read-only",
18
20
  "read-write",
19
21
  "recursive",
22
+ "tenant-access-revoked",
20
23
  "wait",
21
- "yes",
22
24
  ]);
23
25
 
24
26
  const OPTIONAL_VALUE_OPTIONS = new Set(["resume"]);
@@ -230,42 +232,8 @@ export function createInteractionRuntime({
230
232
  return answer;
231
233
  }
232
234
 
233
- async function review({ prompt = "Continue?", requireLive = false, terms, title }) {
234
- if (machine) {
235
- if (!requireLive && options.yes === true) return true;
236
- usage(
237
- requireLive
238
- ? "This operation is supported only in an eligible interactive terminal."
239
- : "Consent is required. Review the terms and supply --yes to continue.",
240
- {
241
- code: requireLive ? "unsupported_in_machine_mode" : "consent_required",
242
- ...(!requireLive ? { field: "yes" } : {}),
243
- }
244
- );
245
- }
246
- if (requireLive && !eligibleHuman) {
247
- usage("This operation is supported only in an eligible interactive terminal.", {
248
- code: "unsupported_in_machine_mode",
249
- });
250
- }
251
- stderr.write(`${title}\n`);
252
- for (const term of terms) stderr.write(` ${term.label}: ${term.value}\n`);
253
-
254
- if (!requireLive && options.yes === true) return true;
255
- if (!eligibleHuman) {
256
- usage("Consent is required. Review the terms and supply --yes to continue.", {
257
- code: "consent_required",
258
- field: "yes",
259
- });
260
- }
261
- let answer;
262
- try {
263
- answer = await readline().question(`${prompt} [y/N] `);
264
- } catch (caught) {
265
- if (caught?.code === "ERR_USE_AFTER_CLOSE") return false;
266
- throw caught;
267
- }
268
- return /^(y|yes)$/i.test(String(answer ?? "").trim());
235
+ async function review() {
236
+ return true;
269
237
  }
270
238
 
271
239
  function webUrl(path) {