@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
package/README.md CHANGED
@@ -7,8 +7,17 @@ sends browser and CLI traffic only through the API gateway.
7
7
  For the customer install and usage guide, see [`docs/cli.md`](../../docs/cli.md).
8
8
  The shared capability catalog in `packages/capabilities` is the authority for
9
9
  command identity, role visibility, input schemas, help, and resource discovery.
10
- The CLI imports `@ornn/capabilities/cli`; packaging includes only that entrypoint's
11
- dependencies, excluding staff commerce and CRM handlers.
10
+ The CLI imports `@ornn/capabilities/cli`; packaging includes its dependencies,
11
+ including `@ornn/api-client`, and excludes MCP-only catalog handlers. These
12
+ packages run without workspace links or React.
13
+
14
+ Native Commerce requests use the caller’s bearer directly through the gateway:
15
+ `/commerce/*` for public APIs and `/internal/v1/*` for staff APIs.
16
+ The gateway and Commerce enforce the caller’s organization and staff permissions.
17
+ All Commerce operations use `@ornn/api-client/commerce`, including the reservation
18
+ reads inside launch, connect, and evidence flows. Customer reservation pickers
19
+ explicitly scope every page to the selected organization, including for staff.
20
+ The gateway retains its Commerce workflow adapters for already-published CLI versions.
12
21
 
13
22
  For GCS imports, start with the [source-bucket permissions and version-specific CLI guide](https://docs.ornn.com/cli#gcs-imports-and-storage-transfer-service).
14
23
  It documents the current CLI 1.x GCS verification limitation. Scoped
@@ -82,6 +91,9 @@ filesystems deploy | show | wait | delete
82
91
  ```
83
92
 
84
93
  `ssh-keys list` uses backend cursors; see [pagination](../../docs/cli.md#advanced-customer-resources).
94
+ Organization key deletion saves removal on reservation lists before revoking the
95
+ account key. If preparation fails, retry the deletion; re-registering the key
96
+ does not restore its removed reservation access.
85
97
 
86
98
  Market commands call one gateway route each and print that route's JSON.
87
99
  `capacity-offers list|show|terms` reads native Commerce capacity
@@ -104,9 +116,11 @@ CLI does not send them yet), end a fill with
104
116
  on a whole primary listing for one of its terms (`--listing-term-id`, optional
105
117
  `--duration-seconds`), list the offers your organization made or received, and
106
118
  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
119
+ Prices are integer micro-USD per GPU-hour (`1000000` is $1.00); CPU capacity
120
+ (a null GPU type, traded on the `cpu` book) is priced and counted per vCPU in
121
+ the same fields. Writes execute
122
+ immediately; use `--format json` for machine-readable output and
123
+ `--idempotency-key` for safe retries. Customer payment methods and
110
124
  billing stay in the web app. Launch consumes already allocated
111
125
  capacity; internal reservation lookups remain necessary for access and
112
126
  ownership.
@@ -116,7 +130,7 @@ Only admins can create primary listings or reservations through
116
130
  They post to the role-aware `POST /commerce/v2/listings` and Markets
117
131
  `POST /v1/markets/bookings` with the admin's own session; the owners enforce
118
132
  the admin role. Both commands retain explicit commercial-term validation and
119
- default-no human confirmation. Reservation creation books a window for a
133
+ execute directly. Reservation creation books a window for a
120
134
  customer and takes `--organization-id UUID`, `--listing-id UUID`,
121
135
  `--listing-term-id UUID`, `--nodes COUNT`, `--starts-at TIME`, `--ends-at TIME`,
122
136
  `--price-per-gpu-hour-micro-usd N`, and `--idempotency-key KEY`. The window is
@@ -125,8 +139,10 @@ Reuse the key and payload on retries. Creation requires an eligible human
125
139
  terminal. Native capacity creation takes
126
140
  `--gpu-slug`, `--gpus-per-node`, `--facility-id`, `--provisioning-type`,
127
141
  `--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.
142
+ `--organization-id`. CPU capacity takes `--gpu-slug cpu` and
143
+ `--cores-per-node` (a whole number of at least 1; each core is 2 vCPUs) instead
144
+ of `--gpus-per-node`; its term prices are per vCPU-hour. Give
145
+ `--capacity-node-count`, or `--node-ids` (a JSON array) to attach nodes at creation; the count then defaults to the number of nodes.
130
146
  Primary capacity takes `--days-to-rfs` (0 when ready now) or both
131
147
  `--available-at` and `--available-until`, not both. Reuse the key and the same
132
148
  body on retries. Prices are integer micro-USD inside terms, with explicit
@@ -155,6 +171,8 @@ Every `ornn admin` command, read or mutation, requires an admin session.
155
171
  Authorization directory by person name, email, or company name, with bounded `--limit` and
156
172
  `--offset` pagination. Users can be restricted by `--organization`; organization
157
173
  results can be filtered by `--role`, `--approved`, or `--ids`.
174
+ User, organization, and supplier directory lists return `next_offset`; pass it
175
+ unchanged as the next `--offset`.
158
176
 
159
177
  `ornn admin organizations invite --organization UUID --email EMAIL` emails an
160
178
  invitation immediately using the selected organization UUID from the directory.
@@ -166,8 +184,9 @@ delivery or share the sign-in link before retrying. Use `--format json` for auto
166
184
  `ornn admin organizations grant-credit --organization UUID --amount-cents 10000
167
185
  --note "Customer goodwill credit" --idempotency-key UUID` grants $100.00 in Ornn
168
186
  credits to the organization. Find the organization with the directory commands
169
- above. Like admin reservation creation, it requires a human terminal and
170
- reviews the organization, USD amount, note, and UUID before default-no consent.
187
+ above. Like admin reservation creation, it requires an eligible human terminal
188
+ and executes immediately after validating the organization, USD amount, note,
189
+ and UUID.
171
190
  The key supplies the existing API's required `adjustmentId`; repeating it with
172
191
  the same details returns the same grant. The admin's session supplies audit
173
192
  identity. See the [credit-adjustment contract](../../services/commerce/README.md#staff-credit-adjustments).
@@ -184,6 +203,17 @@ include the final newline required by OpenSSH, both in files and with `--print`.
184
203
  `ornn admin nodes enroll` takes optional `--filter COMPARTMENT_IDS`
185
204
  (omit to run every compartment step); the retired `--mode` flag is gone. The
186
205
  gateway, not CLI metadata, authoritatively enforces the admin role.
206
+ `ornn admin nodes sanitize`, `sanitize-status`, and `sanitize-decide` drive one
207
+ node's held sanitize job, sharing the capability with MCP
208
+ `admin_node_sanitize_*` and Slack `node_sanitize_*`. `sanitize` needs
209
+ `--operation`, its exact `--confirmation` phrase, and `--recovery-uuid` for
210
+ `ram_roundtrip` (refused for `data_wipe`); the job then waits for staff approval.
211
+ `sanitize-decide --confirmation "ACCEPT MANUAL CLEANUP"` also needs
212
+ `--reservation`, `--release-operation` (the Orchestrator release operation),
213
+ `--reason`, `--evidence-file` (only its SHA-256 is sent), and each attestation
214
+ flag: `--tenant-access-revoked`, `--data-drives-sanitized`, and
215
+ `--acknowledge-retained-os`. Sanitize writes run only in an eligible human
216
+ terminal. See [manual cleanup with the boot OS retained](../../docs/provisioning/dedicated-machine-runbook.md#manual-cleanup-with-the-boot-os-retained).
187
217
  Suppliers are Authorization supplier organizations: `ornn admin suppliers list`
188
218
  returns their `id` and `name`, and `--supplier UUID` on `facilities list`,
189
219
  `nodes list`, `enrollment-tokens create`, and `nodes enroll` takes that
@@ -212,15 +242,15 @@ never contains admin children. There is no second parser or command registry.
212
242
  ## Interaction contract
213
243
 
214
244
  An eligible human context requires TTY stdin and TTY stdout and no JSON/NDJSON
215
- format. Human commands may guide selection, render a normalized review, and ask
216
- default-no `[y/N]` consent. Empty input, EOF, and refusal submit nothing.
245
+ format. Human commands may guide selection and render a normalized summary;
246
+ mutations execute immediately without a confirmation prompt.
217
247
 
218
248
  Automation supplies complete semantic inputs and an explicit format:
219
249
 
220
250
  ```bash
221
251
  ornn status --format json
222
252
  ornn machines list --limit 100 --format ndjson
223
- ornn launch RESERVATION_ID --mode vm --yes \
253
+ ornn launch RESERVATION_ID --mode vm \
224
254
  --idempotency-key caller-stable-key --format json
225
255
  ```
226
256
 
@@ -229,19 +259,20 @@ Redirected commands require `--format human`, `--format json`, or
229
259
  prompts or launches a browser. Inputs missing for a safe decision fail before
230
260
  network, browser, or process I/O.
231
261
 
232
- Global interaction modifiers are `--format`, `--guided`, `--yes`, `--wait`,
262
+ Global interaction modifiers are `--format`, `--guided`, `--wait`,
233
263
  `--no-wait`, `--timeout`, `--no-browser`, `--overwrite`, and
234
- `--idempotency-key`. Mutations that create or increase spend, erase data, tear
235
- down resources, or reduce access require consent. Machine retries require a
264
+ `--idempotency-key`. Destructive operations require explicit intent flags such
265
+ as `--erase-local-data` or `--delete-node-record`. Machine retries require a
236
266
  caller-stable idempotency key and reconcile against durable owner state.
237
267
 
238
- Permanent deletion and teardown requests never fire without consent. Private
239
- networks, detached volumes, filesystem deployments, and Kubernetes or Slurm
240
- clusters accept `--yes` for automation or ask an eligible human for default-no
241
- confirmation. Clearing a volume or deleting nested content is live-terminal
242
- only and always asks, even when `--yes` is present.
268
+ Permanent deletion and teardown requests execute directly when explicitly
269
+ requested. Private networks, detached volumes, filesystem deployments, and
270
+ Kubernetes or Slurm clusters use their command arguments to express the exact
271
+ operation. Clearing a volume or deleting nested content is live-terminal only
272
+ and does not prompt for consent.
243
273
 
244
- List commands use bounded `--limit` values and opaque `--cursor` values. Waits
274
+ Other paged list commands use bounded `--limit` values and opaque `--cursor`
275
+ values. Pass the returned cursor unchanged as the next `--cursor`. Waits
245
276
  poll only retryable transitional states, honor the caller deadline and
246
277
  cancellation, and return the last observed state on timeout. Durable Bare Metal
247
278
  workspace flows persist local recovery pointers for `launch --resume`.
@@ -334,8 +365,8 @@ with one replacement and <https://docs.ornn.com/cli#migrating-to-vnext>.
334
365
  | `--json` | `--format json` |
335
366
  | `--interactive` | `--guided` |
336
367
 
337
- Changed behavior is intentional: waits are deadline-bound, consent is semantic
338
- and default-no, pagination is bounded and cursor-based, machine output is always
368
+ Changed behavior is intentional: waits are deadline-bound, mutations execute
369
+ directly, pagination is bounded and cursor-based, machine output is always
339
370
  enveloped, and exit statuses are semantic. Old help, completion entries,
340
371
  fixtures, staff roots, and owner-route spellings are not shipped.
341
372
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ornncompute/cli",
3
- "version": "1.0.72",
3
+ "version": "1.0.97",
4
4
  "description": "Command-line interface for Ornn compute access workflows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,7 +35,9 @@
35
35
  "test": "node --test 'test/*.test.mjs'"
36
36
  },
37
37
  "devDependencies": {
38
- "@ornn/capabilities": "0.1.0"
38
+ "@ornn/api-client": "0.1.0",
39
+ "@ornn/capabilities": "0.1.0",
40
+ "esbuild": "^0.28.2"
39
41
  },
40
42
  "engines": {
41
43
  "node": ">=20"
@@ -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
  }
@@ -163,7 +153,10 @@ function reservationCreateReview(input) {
163
153
  { label: "Nodes", value: input.nodes },
164
154
  { label: "Starts at", value: input.startsAt },
165
155
  { label: "Ends at", value: input.endsAt },
166
- { label: "Price per GPU-hour (micro-USD)", value: input.pricePerGpuHourMicroUsd },
156
+ {
157
+ label: "Price per unit-hour (micro-USD; per vCPU on CPU capacity)",
158
+ value: input.pricePerGpuHourMicroUsd,
159
+ },
167
160
  { label: "Idempotency key", value: input.idempotencyKey },
168
161
  ],
169
162
  consequence:
@@ -3,6 +3,7 @@ import { adminCreditHandlers } from "./admin-credits.mjs";
3
3
  import { adminEnrollmentHandlers } from "./admin-enrollment.mjs";
4
4
  import { adminNodeDiagnosticsHandlers } from "./admin-node-diagnostics.mjs";
5
5
  import { adminNodeLifecycleHandlers } from "./admin-node-lifecycle.mjs";
6
+ import { adminNodeSanitizeHandlers } from "./admin-node-sanitize.mjs";
6
7
  import { ADMIN_MUTATION_SAFETY, assertAdminMutationContext } from "./admin-mutations.mjs";
7
8
 
8
9
  const handlers = [
@@ -11,6 +12,7 @@ const handlers = [
11
12
  ...adminEnrollmentHandlers,
12
13
  ...adminNodeDiagnosticsHandlers,
13
14
  ...adminNodeLifecycleHandlers,
15
+ ...adminNodeSanitizeHandlers,
14
16
  ];
15
17
 
16
18
  export function adminCommandHandler(capability) {
@@ -50,7 +52,6 @@ export function preflightAdminCommand(catalog, argv, runtime) {
50
52
  ...runtime,
51
53
  idempotencyKey: parsed.mutation?.idempotencyKey,
52
54
  safety: handler.safety,
53
- yes: parsed.mutation?.yes,
54
55
  });
55
56
  }
56
57
  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,