@ornncompute/cli 1.0.67 → 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 (39) hide show
  1. package/README.md +35 -27
  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/catalog-shell.mjs +4 -18
  12. package/src/external-bucket-commands.mjs +5 -73
  13. package/src/interaction-runtime.mjs +2 -37
  14. package/src/kubernetes-commands.mjs +11 -102
  15. package/src/launch-mode-handlers.mjs +0 -1
  16. package/src/mount-commands.mjs +25 -131
  17. package/src/storage-commands.mjs +2 -87
  18. package/src/vm-commands.mjs +11 -44
  19. package/vendor/capabilities/capabilities/admin-capacity-reservations.js +12 -5
  20. package/vendor/capabilities/capabilities/admin-enrollment.js +18 -17
  21. package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +3 -101
  22. package/vendor/capabilities/capabilities/admin.js +3 -3
  23. package/vendor/capabilities/capabilities/clusters.js +3 -3
  24. package/vendor/capabilities/capabilities/commerce-market.js +9 -9
  25. package/vendor/capabilities/capabilities/compute.js +7 -7
  26. package/vendor/capabilities/capabilities/credits.js +7 -2
  27. package/vendor/capabilities/capabilities/external-buckets.js +4 -4
  28. package/vendor/capabilities/capabilities/fleet.js +109 -29
  29. package/vendor/capabilities/capabilities/listings.js +44 -16
  30. package/vendor/capabilities/capabilities/locality.js +42 -0
  31. package/vendor/capabilities/capabilities/market-shared.js +1 -1
  32. package/vendor/capabilities/capabilities/markets.js +14 -14
  33. package/vendor/capabilities/capabilities/mounts.js +4 -4
  34. package/vendor/capabilities/capabilities/native-listing.js +51 -15
  35. package/vendor/capabilities/capabilities/networks.js +5 -5
  36. package/vendor/capabilities/capabilities/observability.js +49 -14
  37. package/vendor/capabilities/capabilities/tokens.js +5 -9
  38. package/vendor/capabilities/capabilities/users.js +8 -10
  39. package/vendor/capabilities/capabilities/volumes.js +5 -5
package/README.md CHANGED
@@ -58,7 +58,7 @@ ornn resources
58
58
 
59
59
  The CLI uses Authorization's newest membership on every request, regardless of
60
60
  approval. `ornn context` displays that current organization; login and launch
61
- use the same context. Restricted operations retain their approval requirements.
61
+ use the same context. Reserve browsing and purchasing still require approval.
62
62
 
63
63
  Advanced customer resources are registered under one noun and one verb:
64
64
 
@@ -100,13 +100,13 @@ relisted (secondary) listing term with Markets `POST /v1/markets/bookings`
100
100
  when the window starts. `market` and `offers` call Markets (`/v1/markets`):
101
101
  buy `--nodes` at market (Markets also takes GPU counts and limit bids; the
102
102
  CLI does not send them yet), end a fill with
103
- `market end` (Commerce `POST /commerce/reservations/{id}/stop`), post an offer
103
+ `market end` (Commerce `DELETE /commerce/reservations/{id}`), post an offer
104
104
  on a whole primary listing for one of its terms (`--listing-term-id`, optional
105
105
  `--duration-seconds`), list the offers your organization made or received, and
106
106
  accept or cancel offers by `--offer-id`.
107
- Prices are integer micro-USD per GPU-hour (`1000000` is $1.00). Every write
108
- reviews its terms and needs default-no consent, or `--yes` with
109
- `--format json`; writes accept `--idempotency-key`. Customer payment methods and
107
+ Prices are integer micro-USD per GPU-hour (`1000000` is $1.00). Writes execute
108
+ immediately; use `--format json` for machine-readable output and
109
+ `--idempotency-key` for safe retries. Customer payment methods and
110
110
  billing stay in the web app. Launch consumes already allocated
111
111
  capacity; internal reservation lookups remain necessary for access and
112
112
  ownership.
@@ -116,18 +116,22 @@ Only admins can create primary listings or reservations through
116
116
  They post to the role-aware `POST /commerce/v2/listings` and Markets
117
117
  `POST /v1/markets/bookings` with the admin's own session; the owners enforce
118
118
  the admin role. Both commands retain explicit commercial-term validation and
119
- default-no human confirmation. Reservation creation books a window for a
119
+ execute directly. Reservation creation books a window for a
120
120
  customer and takes `--organization-id UUID`, `--listing-id UUID`,
121
121
  `--listing-term-id UUID`, `--nodes COUNT`, `--starts-at TIME`, `--ends-at TIME`,
122
122
  `--price-per-gpu-hour-micro-usd N`, and `--idempotency-key KEY`. The window is
123
123
  whole seconds, the term is non-renewable Reserved, and the price is the term's.
124
124
  Reuse the key and payload on retries. Creation requires an eligible human
125
125
  terminal. Native capacity creation takes
126
- `--gpu-slug`, `--capacity-node-count`, `--gpus-per-node`, `--facility-id`,
127
- `--provisioning-type`, `--state`, `--terms` (a JSON array) and `--idempotency-key`,
128
- plus optional `--organization-id`. Supply availability dates or `--days-to-rfs`.
129
- Reuse the key and the same body on retries. Prices are integer micro-USD inside
130
- terms, with explicit duration bounds, nullable deposit and renewable flag.
126
+ `--gpu-slug`, `--gpus-per-node`, `--facility-id`, `--provisioning-type`,
127
+ `--state`, `--terms` (a JSON array) and `--idempotency-key`, plus optional
128
+ `--organization-id`. Give `--capacity-node-count`, or `--node-ids` (a JSON array)
129
+ to attach nodes at creation; the count then defaults to the number of nodes.
130
+ Primary capacity takes `--days-to-rfs` (0 when ready now) or both
131
+ `--available-at` and `--available-until`, not both. Reuse the key and the same
132
+ body on retries. Prices are integer micro-USD inside terms, with explicit
133
+ duration bounds, nullable deposit, renewable flag and optional
134
+ `preemptNoticeSeconds`.
131
135
  `ornn admin capacity-offers update` edits a selected term using `--listing-id`,
132
136
  `--listing-terms-id`, the complete term fields and `--idempotency-key`. Financial
133
137
  editing remains staff-only. The old customer `capacity-offers update` is removed.
@@ -151,6 +155,8 @@ Every `ornn admin` command, read or mutation, requires an admin session.
151
155
  Authorization directory by person name, email, or company name, with bounded `--limit` and
152
156
  `--offset` pagination. Users can be restricted by `--organization`; organization
153
157
  results can be filtered by `--role`, `--approved`, or `--ids`.
158
+ User, organization, and supplier directory lists return `next_offset`; pass it
159
+ unchanged as the next `--offset`.
154
160
 
155
161
  `ornn admin organizations invite --organization UUID --email EMAIL` emails an
156
162
  invitation immediately using the selected organization UUID from the directory.
@@ -162,8 +168,9 @@ delivery or share the sign-in link before retrying. Use `--format json` for auto
162
168
  `ornn admin organizations grant-credit --organization UUID --amount-cents 10000
163
169
  --note "Customer goodwill credit" --idempotency-key UUID` grants $100.00 in Ornn
164
170
  credits to the organization. Find the organization with the directory commands
165
- above. Like admin reservation creation, it requires a human terminal and
166
- reviews the organization, USD amount, note, and UUID before default-no consent.
171
+ above. Like admin reservation creation, it requires an eligible human terminal
172
+ and executes immediately after validating the organization, USD amount, note,
173
+ and UUID.
167
174
  The key supplies the existing API's required `adjustmentId`; repeating it with
168
175
  the same details returns the same grant. The admin's session supplies audit
169
176
  identity. See the [credit-adjustment contract](../../services/commerce/README.md#staff-credit-adjustments).
@@ -208,15 +215,15 @@ never contains admin children. There is no second parser or command registry.
208
215
  ## Interaction contract
209
216
 
210
217
  An eligible human context requires TTY stdin and TTY stdout and no JSON/NDJSON
211
- format. Human commands may guide selection, render a normalized review, and ask
212
- default-no `[y/N]` consent. Empty input, EOF, and refusal submit nothing.
218
+ format. Human commands may guide selection and render a normalized summary;
219
+ mutations execute immediately without a confirmation prompt.
213
220
 
214
221
  Automation supplies complete semantic inputs and an explicit format:
215
222
 
216
223
  ```bash
217
224
  ornn status --format json
218
225
  ornn machines list --limit 100 --format ndjson
219
- ornn launch RESERVATION_ID --mode vm --yes \
226
+ ornn launch RESERVATION_ID --mode vm \
220
227
  --idempotency-key caller-stable-key --format json
221
228
  ```
222
229
 
@@ -225,19 +232,20 @@ Redirected commands require `--format human`, `--format json`, or
225
232
  prompts or launches a browser. Inputs missing for a safe decision fail before
226
233
  network, browser, or process I/O.
227
234
 
228
- Global interaction modifiers are `--format`, `--guided`, `--yes`, `--wait`,
235
+ Global interaction modifiers are `--format`, `--guided`, `--wait`,
229
236
  `--no-wait`, `--timeout`, `--no-browser`, `--overwrite`, and
230
- `--idempotency-key`. Mutations that create or increase spend, erase data, tear
231
- down resources, or reduce access require consent. Machine retries require a
237
+ `--idempotency-key`. Destructive operations require explicit intent flags such
238
+ as `--erase-local-data` or `--delete-node-record`. Machine retries require a
232
239
  caller-stable idempotency key and reconcile against durable owner state.
233
240
 
234
- Permanent deletion and teardown requests never fire without consent. Private
235
- networks, detached volumes, filesystem deployments, and Kubernetes or Slurm
236
- clusters accept `--yes` for automation or ask an eligible human for default-no
237
- confirmation. Clearing a volume or deleting nested content is live-terminal
238
- only and always asks, even when `--yes` is present.
241
+ Permanent deletion and teardown requests execute directly when explicitly
242
+ requested. Private networks, detached volumes, filesystem deployments, and
243
+ Kubernetes or Slurm clusters use their command arguments to express the exact
244
+ operation. Clearing a volume or deleting nested content is live-terminal only
245
+ and does not prompt for consent.
239
246
 
240
- List commands use bounded `--limit` values and opaque `--cursor` values. Waits
247
+ Other paged list commands use bounded `--limit` values and opaque `--cursor`
248
+ values. Pass the returned cursor unchanged as the next `--cursor`. Waits
241
249
  poll only retryable transitional states, honor the caller deadline and
242
250
  cancellation, and return the last observed state on timeout. Durable Bare Metal
243
251
  workspace flows persist local recovery pointers for `launch --resume`.
@@ -330,8 +338,8 @@ with one replacement and <https://docs.ornn.com/cli#migrating-to-vnext>.
330
338
  | `--json` | `--format json` |
331
339
  | `--interactive` | `--guided` |
332
340
 
333
- Changed behavior is intentional: waits are deadline-bound, consent is semantic
334
- and default-no, pagination is bounded and cursor-based, machine output is always
341
+ Changed behavior is intentional: waits are deadline-bound, mutations execute
342
+ directly, pagination is bounded and cursor-based, machine output is always
335
343
  enveloped, and exit statuses are semantic. Old help, completion entries,
336
344
  fixtures, staff roots, and owner-route spellings are not shipped.
337
345
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornncompute/cli",
3
- "version": "1.0.67",
3
+ "version": "1.0.75",
4
4
  "description": "Command-line interface for Ornn compute access workflows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -5,7 +5,7 @@ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-
5
5
 
6
6
  const USAGE = {
7
7
  "reservations.create":
8
- "Usage: ornn admin reservations create --organization-id ID --listing-id ID --listing-term-id ID --nodes COUNT --starts-at TIME --ends-at TIME --price-per-gpu-hour-micro-usd N --idempotency-key KEY [--yes]",
8
+ "Usage: ornn admin reservations create --organization-id ID --listing-id ID --listing-term-id ID --nodes COUNT --starts-at TIME --ends-at TIME --price-per-gpu-hour-micro-usd N --idempotency-key KEY",
9
9
  };
10
10
 
11
11
  function parseOptions(argv, { values, positionals }, usage) {
@@ -17,14 +17,6 @@ function parseOptions(argv, { values, positionals }, usage) {
17
17
  positionalValues.push(argument);
18
18
  continue;
19
19
  }
20
- if (argument === "--yes") {
21
- if (options.yes !== undefined)
22
- throw new AdminMutationError(`${usage}\n--yes may be supplied only once.`, {
23
- field: "yes",
24
- });
25
- options.yes = true;
26
- continue;
27
- }
28
20
  const separator = argument.indexOf("=");
29
21
  const name = argument.slice(2, separator === -1 ? undefined : separator);
30
22
  if (!values.includes(name)) throw new AdminMutationError(`${usage}\nUnknown option: --${name}`);
@@ -84,14 +76,13 @@ function timestamp(options, name, usage) {
84
76
  }
85
77
 
86
78
  function parseCapacityOffer(argv) {
87
- const { $positionals, yes, ...input } = parseSemanticArguments(argv);
79
+ const { $positionals, ...input } = parseSemanticArguments(argv);
88
80
  if ($positionals.length) throw new AdminMutationError("Use named options for listing writes.");
89
81
  return {
90
82
  input,
91
83
  mutation: {
92
84
  idempotencyKey: input.idempotencyKey,
93
85
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
94
- yes: yes === true,
95
86
  },
96
87
  };
97
88
  }
@@ -135,7 +126,6 @@ function parseReservationCreate(argv) {
135
126
  mutation: {
136
127
  idempotencyKey,
137
128
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
138
- yes: options.yes === true,
139
129
  },
140
130
  };
141
131
  }
@@ -50,7 +50,6 @@ export function preflightAdminCommand(catalog, argv, runtime) {
50
50
  ...runtime,
51
51
  idempotencyKey: parsed.mutation?.idempotencyKey,
52
52
  safety: handler.safety,
53
- yes: parsed.mutation?.yes,
54
53
  });
55
54
  }
56
55
  return { capabilityId: capability.id, command: command.join(" "), parsed };
@@ -2,7 +2,7 @@ import { ADMIN_MUTATION_SAFETY, AdminMutationError, runAdminMutation } from "./a
2
2
  import { parseSemanticArguments } from "./interaction-runtime.mjs";
3
3
 
4
4
  function parseGrant(argv, capability) {
5
- const { $positionals, yes, organization, amountCents, note, idempotencyKey, ...extra } =
5
+ const { $positionals, organization, amountCents, note, idempotencyKey, ...extra } =
6
6
  parseSemanticArguments(argv);
7
7
  if ($positionals.length || Object.keys(extra).length) {
8
8
  throw new AdminMutationError(`Usage: ${capability.cli.vnext.human}`);
@@ -21,7 +21,6 @@ function parseGrant(argv, capability) {
21
21
  mutation: {
22
22
  idempotencyKey: parsed.data.adjustmentId,
23
23
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
24
- yes: yes === true,
25
24
  },
26
25
  };
27
26
  }
@@ -11,15 +11,15 @@ const ENROLLMENT_LOG_READ_TIMEOUT_MS = 5_000;
11
11
 
12
12
  const USAGE = {
13
13
  "tokens.create":
14
- "Usage: ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite] [--yes]",
15
- "tokens.revoke":
16
- "Usage: ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] [--yes] [--idempotency-key KEY]",
14
+ "Usage: ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite]",
15
+ "enrollment.token.revoke":
16
+ "Usage: ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] [--idempotency-key KEY]",
17
17
  "nodes.enroll":
18
- "Usage: 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]",
18
+ "Usage: 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]",
19
19
  "nodes.export-key":
20
20
  "Usage: ornn admin nodes export-key <node-id> (--print | --output PATH) [--overwrite]",
21
21
  "nodes.rotate-key":
22
- "Usage: ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite] [--yes]",
22
+ "Usage: ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite]",
23
23
  };
24
24
 
25
25
  function parseOptions(argv, { booleans = [], positionals, values }, usage) {
@@ -108,7 +108,7 @@ function parseTokenCreate(argv) {
108
108
  const { options } = parseOptions(
109
109
  argv,
110
110
  {
111
- booleans: ["overwrite", "print", "yes"],
111
+ booleans: ["overwrite", "print"],
112
112
  positionals: 0,
113
113
  values: ["supplier", "expires-in", "output"],
114
114
  },
@@ -127,21 +127,22 @@ function parseTokenCreate(argv) {
127
127
  },
128
128
  mutation: {
129
129
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
130
- yes: options.yes === true,
131
130
  },
132
131
  };
133
132
  }
134
133
 
135
134
  function parseTokenRevoke(argv) {
136
- const usage = USAGE["tokens.revoke"];
135
+ const usage = USAGE["enrollment.token.revoke"];
137
136
  const { options, positionals } = parseOptions(
138
137
  argv,
139
- { booleans: ["yes"], positionals: 1, values: ["expected-created-at", "idempotency-key"] },
138
+ { booleans: [], positionals: 1, values: ["expected-created-at", "idempotency-key"] },
140
139
  usage
141
140
  );
141
+ const tokenId = uuid(positionals[0], "token-id");
142
142
  return {
143
143
  input: {
144
- token_id: uuid(positionals[0], "token-id"),
144
+ tokenId,
145
+ token_id: tokenId,
145
146
  ...(options["expected-created-at"]?.trim()
146
147
  ? { expected_created_at: options["expected-created-at"].trim() }
147
148
  : {}),
@@ -152,7 +153,6 @@ function parseTokenRevoke(argv) {
152
153
  mutation: {
153
154
  idempotencyKey: options["idempotency-key"]?.trim(),
154
155
  safety: ADMIN_MUTATION_SAFETY.reconcile,
155
- yes: options.yes === true,
156
156
  },
157
157
  };
158
158
  }
@@ -162,7 +162,7 @@ function parseNodeEnroll(argv) {
162
162
  const { options, positionals } = parseOptions(
163
163
  argv,
164
164
  {
165
- booleans: ["replace-existing-management", "verbose", "yes"],
165
+ booleans: ["replace-existing-management", "verbose"],
166
166
  positionals: 1,
167
167
  values: [
168
168
  "facility",
@@ -179,12 +179,9 @@ function parseNodeEnroll(argv) {
179
179
  );
180
180
  const filter = options.filter?.trim();
181
181
  if (!options["replace-existing-management"]) {
182
- throw new AdminMutationError(
183
- `${usage}\n--replace-existing-management is required and cannot be inferred by --yes.`,
184
- {
185
- field: "replace-existing-management",
186
- }
187
- );
182
+ throw new AdminMutationError(`${usage}\n--replace-existing-management is required.`, {
183
+ field: "replace-existing-management",
184
+ });
188
185
  }
189
186
  return {
190
187
  input: {
@@ -207,7 +204,6 @@ function parseNodeEnroll(argv) {
207
204
  },
208
205
  mutation: {
209
206
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
210
- yes: options.yes === true,
211
207
  },
212
208
  };
213
209
  }
@@ -218,7 +214,7 @@ function parseNodeKey(argv, rotate) {
218
214
  const { options, positionals } = parseOptions(
219
215
  argv,
220
216
  {
221
- booleans: rotate ? ["overwrite", "print", "yes"] : ["overwrite", "print"],
217
+ booleans: ["overwrite", "print"],
222
218
  positionals: 1,
223
219
  values: ["output"],
224
220
  },
@@ -233,7 +229,6 @@ function parseNodeKey(argv, rotate) {
233
229
  ? {
234
230
  mutation: {
235
231
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
236
- yes: options.yes === true,
237
232
  },
238
233
  }
239
234
  : {}),
@@ -353,7 +348,7 @@ function reviewFor(capabilityId, input, prepared) {
353
348
  consequence: "Creates a bearer credential that can enroll a node until it expires.",
354
349
  };
355
350
  }
356
- if (capabilityId === "tokens.revoke") {
351
+ if (capabilityId === "enrollment.token.revoke") {
357
352
  const target = prepared?.target;
358
353
  return {
359
354
  title: "Revoke enrollment token",
@@ -374,9 +369,7 @@ function reviewFor(capabilityId, input, prepared) {
374
369
  { label: "SSH target", value: `${input.username}@${input.host}:${input.port}` },
375
370
  { label: "Supplier", value: input.organization_id },
376
371
  { label: "Facility", value: input.facility_id },
377
- ...(input.filter
378
- ? [{ label: "Compartment filter", value: input.filter }]
379
- : []),
372
+ ...(input.filter ? [{ label: "Compartment filter", value: input.filter }] : []),
380
373
  { label: "Token lifetime", value: `${input.expires_in_seconds} seconds` },
381
374
  ],
382
375
  consequence:
@@ -573,7 +566,7 @@ async function runEnrollmentMutation({ capability, execute, fetch, input, mutati
573
566
  assertSecretOutputContext(input, runtime);
574
567
  await refuseExistingOutput(input.export);
575
568
  }
576
- if (capabilityId === "tokens.revoke" && runtime.machine && !input.expected_created_at) {
569
+ if (capabilityId === "enrollment.token.revoke" && runtime.machine && !input.expected_created_at) {
577
570
  throw new AdminMutationError(
578
571
  "Machine revocation requires --expected-created-at from the listed token generation.",
579
572
  { code: "exact_token_identity_required", field: "expected-created-at" }
@@ -585,7 +578,7 @@ async function runEnrollmentMutation({ capability, execute, fetch, input, mutati
585
578
  ...mutation,
586
579
  stdout: runtime.stderr ?? runtime.stdout,
587
580
  prepare: async () => {
588
- if (capabilityId === "tokens.revoke") {
581
+ if (capabilityId === "enrollment.token.revoke") {
589
582
  const prepared = await execute(input);
590
583
  return {
591
584
  review: reviewFor(capabilityId, input, prepared),
@@ -599,7 +592,7 @@ async function runEnrollmentMutation({ capability, execute, fetch, input, mutati
599
592
  return { review: reviewFor(capabilityId, input), value: input };
600
593
  },
601
594
  reconcile:
602
- capabilityId === "tokens.revoke"
595
+ capabilityId === "enrollment.token.revoke"
603
596
  ? async (value) => {
604
597
  if (value.prepared.disposition === "satisfied") {
605
598
  return {
@@ -635,7 +628,6 @@ async function runEnrollmentMutation({ capability, execute, fetch, input, mutati
635
628
  },
636
629
  });
637
630
 
638
- if (execution.status === "declined") return { ...execution, suppressResult: true };
639
631
  if (!SECRET_OUTPUT.has(capabilityId)) return execution;
640
632
  const exported = await exportSecret({
641
633
  capabilityId,
@@ -655,7 +647,7 @@ export const adminEnrollmentHandlers = [
655
647
  safety: ADMIN_MUTATION_SAFETY.eligibleHumanOnly,
656
648
  },
657
649
  {
658
- capabilityId: "tokens.revoke",
650
+ capabilityId: "enrollment.token.revoke",
659
651
  parse: parseTokenRevoke,
660
652
  run: runEnrollmentMutation,
661
653
  safety: ADMIN_MUTATION_SAFETY.reconcile,
@@ -1,6 +1,5 @@
1
- import { createInterface } from "node:readline/promises";
2
-
3
1
  export const ADMIN_MUTATION_SAFETY = Object.freeze({
2
+ direct: "direct",
4
3
  eligibleHumanOnly: "eligible-human-only",
5
4
  reconcile: "reconcile",
6
5
  });
@@ -46,32 +45,8 @@ export function writeAdminMutationReview(stdout, review) {
46
45
  }
47
46
  }
48
47
 
49
- async function defaultNoConsent(stdin, stdout) {
50
- const readline = createInterface({ input: stdin, output: stdout, terminal: false });
51
- try {
52
- const answer = await readline.question("Proceed? [y/N] ");
53
- return /^(y|yes)$/i.test(answer.trim());
54
- } catch (error) {
55
- if (
56
- error?.code === "ERR_USE_AFTER_CLOSE" ||
57
- error?.code === "ABORT_ERR" ||
58
- error?.name === "AbortError"
59
- ) {
60
- return false;
61
- }
62
- throw error;
63
- } finally {
64
- readline.close();
65
- }
66
- }
67
-
68
- export function assertAdminMutationContext({
69
- eligibleHuman,
70
- idempotencyKey,
71
- machine,
72
- safety,
73
- yes,
74
- }) {
48
+ export function assertAdminMutationContext({ eligibleHuman, idempotencyKey, machine, safety }) {
49
+ if (safety === ADMIN_MUTATION_SAFETY.direct) return;
75
50
  if (safety === ADMIN_MUTATION_SAFETY.eligibleHumanOnly) {
76
51
  if (!eligibleHuman || machine) {
77
52
  throw new AdminMutationError(
@@ -86,12 +61,6 @@ export function assertAdminMutationContext({
86
61
  code: "admin_mutation_policy_missing",
87
62
  });
88
63
  }
89
- if (!eligibleHuman && yes !== true) {
90
- throw new AdminMutationError("Noninteractive admin mutations require --yes.", {
91
- code: "consent_required",
92
- field: "yes",
93
- });
94
- }
95
64
  if (!eligibleHuman && !idempotencyKey?.trim()) {
96
65
  throw new AdminMutationError(
97
66
  "Noninteractive admin mutations require --idempotency-key with a caller-stable value.",
@@ -100,12 +69,6 @@ export function assertAdminMutationContext({
100
69
  }
101
70
  }
102
71
 
103
- /**
104
- * Shared privileged-mutation gate. All noninteractive prerequisites are checked
105
- * before prepare/reconcile/execute can perform I/O. Domain handlers own semantic
106
- * validation and exact goal comparison; this function owns context safety,
107
- * normalized review, default-no consent, and the apply/satisfied/conflict flow.
108
- */
109
72
  export async function runAdminMutation({
110
73
  eligibleHuman,
111
74
  execute,
@@ -114,11 +77,9 @@ export async function runAdminMutation({
114
77
  prepare,
115
78
  reconcile,
116
79
  safety,
117
- stdin,
118
80
  stdout,
119
- yes = false,
120
81
  }) {
121
- assertAdminMutationContext({ eligibleHuman, idempotencyKey, machine, safety, yes });
82
+ assertAdminMutationContext({ eligibleHuman, idempotencyKey, machine, safety });
122
83
  if (typeof prepare !== "function" || typeof execute !== "function") {
123
84
  throw new AdminMutationError("Admin mutation handler is incomplete.", {
124
85
  code: "admin_mutation_handler_incomplete",
@@ -132,8 +93,8 @@ export async function runAdminMutation({
132
93
 
133
94
  let prepared = await prepare();
134
95
  if (!prepared || typeof prepared !== "object") {
135
- throw new AdminMutationError("Admin mutation preparation returned no review.", {
136
- code: "admin_review_incomplete",
96
+ throw new AdminMutationError("Admin mutation preparation returned no operation.", {
97
+ code: "admin_preparation_invalid",
137
98
  });
138
99
  }
139
100
 
@@ -161,22 +122,11 @@ export async function runAdminMutation({
161
122
  }
162
123
  prepared = {
163
124
  ...prepared,
164
- ...(reconciliation.review ? { review: reconciliation.review } : {}),
165
125
  ...(reconciliation.value !== undefined ? { value: reconciliation.value } : {}),
166
126
  };
167
127
  }
168
128
 
169
129
  if (!machine) writeAdminMutationReview(stdout, prepared.review);
170
- if (eligibleHuman && yes !== true && !(await defaultNoConsent(stdin, stdout))) {
171
- return {
172
- data: { submitted: false },
173
- exitCode: 1,
174
- outcome: { class: "ended", phase: "declined" },
175
- reconciled: false,
176
- status: "declined",
177
- };
178
- }
179
-
180
130
  return {
181
131
  data: await execute(prepared.value),
182
132
  exitCode: 0,
@@ -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
  }
@@ -272,27 +261,6 @@ function executionResult(raw, input, target) {
272
261
  };
273
262
  }
274
263
 
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
264
  function interruptedResult(input) {
297
265
  return {
298
266
  data: {
@@ -343,7 +311,6 @@ async function runDiagnosis({ input, mutation, runtime, execute, fetch }) {
343
311
  },
344
312
  execute: async () => execute({ node_id: input.node_id }, { confirmed: true }),
345
313
  });
346
- if (result.status === "declined") return declinedResult(input);
347
314
  return diagnosisResult(result.data, target);
348
315
  } catch (error) {
349
316
  if (wasInterrupted(error, runtime)) return interruptedResult(input);
@@ -388,7 +355,6 @@ async function runExecution({ input, mutation, runtime, execute, fetch }) {
388
355
  { confirmed: true }
389
356
  ),
390
357
  });
391
- if (result.status === "declined") return declinedResult(input);
392
358
  return executionResult(result.data, input, target);
393
359
  } catch (error) {
394
360
  if (wasInterrupted(error, runtime)) return interruptedResult(input);