@ornncompute/cli 1.0.75 → 1.0.107
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.
- package/README.md +38 -5
- package/package.json +4 -2
- package/src/admin-capacity-reservations.mjs +4 -1
- package/src/admin-command-handlers.mjs +4 -0
- package/src/admin-node-diagnostics.mjs +1 -5
- package/src/admin-node-lifecycle.mjs +23 -125
- package/src/admin-node-sanitize.mjs +243 -0
- package/src/admin-ssh.mjs +113 -0
- package/src/api-client.mjs +4 -1
- package/src/catalog-shell.mjs +6 -6
- package/src/evidence-commands.mjs +4 -6
- package/src/external-bucket-commands.mjs +71 -23
- package/src/interaction-runtime.mjs +3 -0
- package/src/kubernetes-commands.mjs +16 -83
- package/src/machine-reference.mjs +33 -56
- package/src/slurm-commands.mjs +13 -2
- package/src/vm-commands.mjs +39 -59
- package/vendor/api-client/commerce.js +745 -0
- package/vendor/capabilities/cli.js +7756 -7
- package/vendor/capabilities/capabilities/admin-capacity-reservations.js +0 -101
- package/vendor/capabilities/capabilities/admin-enrollment.js +0 -322
- package/vendor/capabilities/capabilities/admin-node-diagnostics.js +0 -88
- package/vendor/capabilities/capabilities/admin-nodes-lifecycle.js +0 -137
- package/vendor/capabilities/capabilities/admin.js +0 -91
- package/vendor/capabilities/capabilities/cli.js +0 -156
- package/vendor/capabilities/capabilities/clusters.js +0 -248
- package/vendor/capabilities/capabilities/commerce-market.js +0 -405
- package/vendor/capabilities/capabilities/compute.js +0 -523
- package/vendor/capabilities/capabilities/credits.js +0 -83
- package/vendor/capabilities/capabilities/evidence.js +0 -145
- package/vendor/capabilities/capabilities/external-buckets.js +0 -191
- package/vendor/capabilities/capabilities/fleet.js +0 -386
- package/vendor/capabilities/capabilities/identity.js +0 -129
- package/vendor/capabilities/capabilities/listings.js +0 -398
- package/vendor/capabilities/capabilities/locality.js +0 -42
- package/vendor/capabilities/capabilities/market-shared.js +0 -46
- package/vendor/capabilities/capabilities/markets.js +0 -482
- package/vendor/capabilities/capabilities/mounts.js +0 -176
- package/vendor/capabilities/capabilities/native-listing.js +0 -99
- package/vendor/capabilities/capabilities/networks.js +0 -1264
- package/vendor/capabilities/capabilities/observability.js +0 -253
- package/vendor/capabilities/capabilities/organization-invites.js +0 -53
- package/vendor/capabilities/capabilities/reservation-list.js +0 -68
- package/vendor/capabilities/capabilities/staff-shared.js +0 -64
- package/vendor/capabilities/capabilities/tokens.js +0 -161
- package/vendor/capabilities/capabilities/users.js +0 -157
- package/vendor/capabilities/capabilities/volumes.js +0 -178
- package/vendor/capabilities/capability.js +0 -99
- package/vendor/capabilities/cli-catalog.js +0 -73
- package/vendor/capabilities/cli-migrations.js +0 -264
- package/vendor/capabilities/names.js +0 -6
- package/vendor/capabilities/software-catalog.js +0 -82
- package/vendor/capabilities/spec-catalog.js +0 -151
- package/vendor/capabilities/supplier-directory.js +0 -20
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { defineCapability, preview } from "../capability.js";
|
|
3
|
-
import { ensureFacility, locateRegion, unplacedLocation } from "./locality.js";
|
|
4
|
-
import { nativeListingCreateInput } from "./native-listing.js";
|
|
5
|
-
import { idempotencyHeaders, microUsd } from "./market-shared.js";
|
|
6
|
-
function adminHelp(command, outcome, human, automation, review) {
|
|
7
|
-
return {
|
|
8
|
-
audience: "admin",
|
|
9
|
-
category: "resource",
|
|
10
|
-
command,
|
|
11
|
-
outcome,
|
|
12
|
-
human,
|
|
13
|
-
automation,
|
|
14
|
-
acceptedReferences: [],
|
|
15
|
-
...(review ? { review } : {}),
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export const adminCapacityOffersCreateCapability = defineCapability({
|
|
19
|
-
id: "listing.create",
|
|
20
|
-
domain: "listings",
|
|
21
|
-
roles: ["admin"],
|
|
22
|
-
description: "Create native capacity with an organization, a facilityId or a location, hardware, availability and advertised terms. A location is any state, city, address, or catalog region (us-west-3); Ornn places it in a catalog region and uses the organization's facility there. Primary capacity needs daysToRfs (0 when ready now) or both availableAt and availableUntil. nodeIds attach nodes at creation. Returns {listing, terms}. Requires an idempotencyKey reused on retries and confirm: true.",
|
|
23
|
-
mutation: "preview-confirm",
|
|
24
|
-
http: { method: "POST", path: "/commerce/v2/listings" },
|
|
25
|
-
input: nativeListingCreateInput,
|
|
26
|
-
slack: false,
|
|
27
|
-
cli: {
|
|
28
|
-
command: ["admin", "capacity-offers", "create"],
|
|
29
|
-
vnext: adminHelp(["admin", "capacity-offers", "create"], "Create native capacity and its advertised financial terms.", "ornn admin capacity-offers create --gpu-slug GPU --gpus-per-node COUNT (--facility-id UUID | --location PLACE) --provisioning-type bare-metal|virtual-machine --state draft|available --terms JSON --idempotency-key KEY (--days-to-rfs DAYS | --available-at TIME --available-until TIME) [--capacity-node-count COUNT] [--node-ids JSON] [--organization-id UUID]", "Requires an eligible human terminal; --location also needs --organization-id; reuse --idempotency-key on retries.", "Review the native parent fields and each financial term before default-no consent."),
|
|
30
|
-
},
|
|
31
|
-
execute: async (ctx, input) => {
|
|
32
|
-
const { confirm: _confirm, idempotencyKey, location, ...body } = nativeListingCreateInput.parse(input);
|
|
33
|
-
const located = location ? await locateRegion(ctx.fetch, location) : null;
|
|
34
|
-
if (location && !located)
|
|
35
|
-
throw new Error(unplacedLocation(location));
|
|
36
|
-
if (!ctx.confirmed)
|
|
37
|
-
return preview("listing.create", located ? { ...body, located } : body);
|
|
38
|
-
const facilityId = located
|
|
39
|
-
? (await ensureFacility(ctx.fetch, body.organizationId, located.region)).facilityId
|
|
40
|
-
: body.facilityId;
|
|
41
|
-
return ctx.fetch("/commerce/v2/listings", {
|
|
42
|
-
method: "POST",
|
|
43
|
-
body: { ...body, facilityId },
|
|
44
|
-
headers: idempotencyHeaders(ctx, idempotencyKey),
|
|
45
|
-
});
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
// Markets books whole-second windows; a fractional digit would shift or refuse one.
|
|
49
|
-
const wholeSecondInstant = z.iso
|
|
50
|
-
.datetime({ offset: true })
|
|
51
|
-
.refine((value) => !/\.\d*[1-9]/.test(value), "Use a time in whole seconds.");
|
|
52
|
-
export const reservationCreateInput = z.object({
|
|
53
|
-
organizationId: z.string().uuid().describe("The customer organization the window is booked for."),
|
|
54
|
-
listingId: z
|
|
55
|
-
.string()
|
|
56
|
-
.uuid()
|
|
57
|
-
.describe("The listing's capacityListingId. Staff may book any available listing."),
|
|
58
|
-
listingTermId: z
|
|
59
|
-
.string()
|
|
60
|
-
.uuid()
|
|
61
|
-
.describe("The listingTermsId of a non-renewable Reserved term on that listing."),
|
|
62
|
-
nodes: z.number().int().positive().max(2_147_483_647).describe("Whole nodes to book."),
|
|
63
|
-
startsAt: wholeSecondInstant.describe("RFC 3339 start of the window in whole seconds, no more than five minutes ago."),
|
|
64
|
-
endsAt: wholeSecondInstant.describe("RFC 3339 end of the window in whole seconds, at least an hour after the start and within the term's duration range."),
|
|
65
|
-
pricePerGpuHourMicroUsd: microUsd.describe("The term's price in micro-USD per GPU-hour. A changed price refuses the booking."),
|
|
66
|
-
idempotencyKey: z
|
|
67
|
-
.string()
|
|
68
|
-
.trim()
|
|
69
|
-
.min(1)
|
|
70
|
-
.max(200)
|
|
71
|
-
.describe("Reuse this key when retrying the same booking."),
|
|
72
|
-
});
|
|
73
|
-
export const adminReservationsCreateCapability = defineCapability({
|
|
74
|
-
id: "reservations.create",
|
|
75
|
-
domain: "reservations",
|
|
76
|
-
roles: ["admin"],
|
|
77
|
-
description: "Book a window of a listing term for a customer organization (admin). Markets holds the GPUs until the window starts, then settles a Commerce reservation with the booking's ID. Returns {bookings}.",
|
|
78
|
-
mutation: "preview-confirm",
|
|
79
|
-
http: { method: "POST", path: "/v1/markets/bookings" },
|
|
80
|
-
cli: {
|
|
81
|
-
command: ["admin", "reservations", "create"],
|
|
82
|
-
vnext: adminHelp(["admin", "reservations", "create"], "Book a listing term window for a customer organization.", "ornn admin reservations create --organization-id UUID --listing-id UUID --listing-term-id UUID --nodes COUNT --starts-at TIME --ends-at TIME --price-per-gpu-hour-micro-usd N --idempotency-key KEY", "Requires an eligible human terminal. Reuse --idempotency-key when retrying the same booking.", "Customer organization, listing, term, nodes, window, price, and idempotency key are reviewed before default-no consent."),
|
|
83
|
-
},
|
|
84
|
-
slack: false,
|
|
85
|
-
input: reservationCreateInput,
|
|
86
|
-
execute: async (ctx, input) => {
|
|
87
|
-
const { organizationId, idempotencyKey, ...leg } = reservationCreateInput.parse(input);
|
|
88
|
-
const body = { organizationId, legs: [leg] };
|
|
89
|
-
if (!ctx.confirmed)
|
|
90
|
-
return { preview: body };
|
|
91
|
-
return ctx.fetch("/v1/markets/bookings", {
|
|
92
|
-
method: "POST",
|
|
93
|
-
body,
|
|
94
|
-
headers: idempotencyHeaders(ctx, idempotencyKey),
|
|
95
|
-
});
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
export const adminCapacityReservationCapabilities = [
|
|
99
|
-
adminCapacityOffersCreateCapability,
|
|
100
|
-
adminReservationsCreateCapability,
|
|
101
|
-
];
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { defineCapability } from "../capability.js";
|
|
3
|
-
import { buildEnrollmentInstallCommand, tokensCreateCapability, tokensListCapability, } from "./tokens.js";
|
|
4
|
-
const UUID = z.string().uuid();
|
|
5
|
-
const nonEmpty = z.string().trim().min(1);
|
|
6
|
-
const tokenTtl = z
|
|
7
|
-
.number()
|
|
8
|
-
.int()
|
|
9
|
-
.min(60)
|
|
10
|
-
.max(24 * 60 * 60);
|
|
11
|
-
function adminHelp(command, outcome, human, automation, acceptedReferences = [], review) {
|
|
12
|
-
return {
|
|
13
|
-
audience: "admin",
|
|
14
|
-
category: "resource",
|
|
15
|
-
command,
|
|
16
|
-
outcome,
|
|
17
|
-
human,
|
|
18
|
-
automation,
|
|
19
|
-
acceptedReferences,
|
|
20
|
-
...(review ? { review } : {}),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
function withVnextBinding(capability, help) {
|
|
24
|
-
if (!help.command)
|
|
25
|
-
throw new Error(`${capability.id} requires a canonical CLI command`);
|
|
26
|
-
return {
|
|
27
|
-
...capability,
|
|
28
|
-
cli: {
|
|
29
|
-
command: [...help.command],
|
|
30
|
-
vnext: help,
|
|
31
|
-
},
|
|
32
|
-
};
|
|
33
|
-
}
|
|
34
|
-
export const adminEnrollmentTokensListCapability = {
|
|
35
|
-
...withVnextBinding(tokensListCapability, adminHelp(["admin", "enrollment-tokens", "list"], "List enrollment-token metadata without token bytes.", "ornn admin enrollment-tokens list [--limit 50] [--cursor CURSOR]", "ornn admin enrollment-tokens list --limit 50 --format json")),
|
|
36
|
-
execute: async (ctx) => {
|
|
37
|
-
const payload = await tokensListCapability.execute(ctx, {});
|
|
38
|
-
if (!ctx.cli)
|
|
39
|
-
return payload;
|
|
40
|
-
return {
|
|
41
|
-
enrollment_tokens: rowsFromTokenList(payload)
|
|
42
|
-
.map(tokenMetadata)
|
|
43
|
-
.filter((token) => token !== null),
|
|
44
|
-
};
|
|
45
|
-
},
|
|
46
|
-
};
|
|
47
|
-
export const adminEnrollmentTokenCreateCapability = withVnextBinding(tokensCreateCapability, adminHelp(["admin", "enrollment-tokens", "create"], "Create one expiring enrollment token through an explicit secret export.", "ornn admin enrollment-tokens create --supplier UUID --expires-in SECONDS (--print | --output PATH) [--overwrite] []", "Unsupported: token creation requires an eligible human terminal.", ["canonical supplier organization UUID"], "Review the supplier, expiry, and secret destination before minting the token."));
|
|
48
|
-
const tokenRevokeInput = z.object({
|
|
49
|
-
tokenId: z.string().min(1),
|
|
50
|
-
token_id: UUID.optional(),
|
|
51
|
-
expected_created_at: nonEmpty.optional(),
|
|
52
|
-
idempotency_key: nonEmpty.optional(),
|
|
53
|
-
confirm: z.boolean().optional(),
|
|
54
|
-
});
|
|
55
|
-
function rowsFromTokenList(payload) {
|
|
56
|
-
if (Array.isArray(payload))
|
|
57
|
-
return payload;
|
|
58
|
-
if (!payload || typeof payload !== "object")
|
|
59
|
-
return [];
|
|
60
|
-
const source = payload;
|
|
61
|
-
for (const key of ["enrollment_tokens", "tokens", "items", "data"]) {
|
|
62
|
-
if (Array.isArray(source[key]))
|
|
63
|
-
return source[key];
|
|
64
|
-
}
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
function tokenMetadata(value) {
|
|
68
|
-
if (!value || typeof value !== "object")
|
|
69
|
-
return null;
|
|
70
|
-
const row = value;
|
|
71
|
-
if (typeof row.id !== "string" || !row.id)
|
|
72
|
-
return null;
|
|
73
|
-
return {
|
|
74
|
-
id: row.id,
|
|
75
|
-
organization_id: typeof row.organization_id === "string" ? row.organization_id : null,
|
|
76
|
-
status: typeof row.status === "string" ? row.status : "unknown",
|
|
77
|
-
expires_at: typeof row.expires_at === "string" ? row.expires_at : null,
|
|
78
|
-
created_at: typeof row.created_at === "string" ? row.created_at : null,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function codedError(code, message) {
|
|
82
|
-
const error = new Error(message);
|
|
83
|
-
error.detail = { code, retryable: false };
|
|
84
|
-
return error;
|
|
85
|
-
}
|
|
86
|
-
async function readExactToken(ctx, tokenId, expectedCreatedAt) {
|
|
87
|
-
// Filter at the owner before its list limit; an older active token may not be in the latest 500.
|
|
88
|
-
const listed = await ctx.fetch(`/enrollment-tokens?id=${encodeURIComponent(tokenId)}`, {
|
|
89
|
-
method: "GET",
|
|
90
|
-
});
|
|
91
|
-
const target = rowsFromTokenList(listed)
|
|
92
|
-
.map(tokenMetadata)
|
|
93
|
-
.find((candidate) => candidate?.id === tokenId);
|
|
94
|
-
if (!target)
|
|
95
|
-
return { disposition: "satisfied", target: null };
|
|
96
|
-
if (expectedCreatedAt && target.created_at !== expectedCreatedAt) {
|
|
97
|
-
return {
|
|
98
|
-
disposition: "conflict",
|
|
99
|
-
target,
|
|
100
|
-
message: `Enrollment token ${tokenId} was replaced after ${expectedCreatedAt}; refusing to revoke it.`,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
if (target.status === "revoked" || target.status === "expired") {
|
|
104
|
-
return { disposition: "satisfied", target };
|
|
105
|
-
}
|
|
106
|
-
return { disposition: "apply", target };
|
|
107
|
-
}
|
|
108
|
-
export const adminEnrollmentTokenRevokeCapability = defineCapability({
|
|
109
|
-
id: "enrollment.token.revoke",
|
|
110
|
-
domain: "fleet",
|
|
111
|
-
roles: ["admin"],
|
|
112
|
-
description: "Internal staff only: revoke one exact enrollment-token generation after reconciling its durable goal.",
|
|
113
|
-
mutation: "preview-confirm",
|
|
114
|
-
http: { method: "DELETE", path: "/enrollment-tokens/{tokenId}" },
|
|
115
|
-
cli: {
|
|
116
|
-
command: ["admin", "enrollment-tokens", "revoke"],
|
|
117
|
-
vnext: adminHelp(["admin", "enrollment-tokens", "revoke"], "Revoke one exact enrollment-token generation.", "ornn admin enrollment-tokens revoke <token-id> [--expected-created-at TIME] []", "ornn admin enrollment-tokens revoke <token-id> --expected-created-at TIME --idempotency-key KEY --format json", ["canonical token UUID", "token created_at generation"], "Review the exact token generation and the loss of future enrollment access."),
|
|
118
|
-
},
|
|
119
|
-
slack: false,
|
|
120
|
-
mcp: { name: "enrollment_token_revoke" },
|
|
121
|
-
input: tokenRevokeInput,
|
|
122
|
-
execute: async (ctx, input) => {
|
|
123
|
-
const parsed = tokenRevokeInput.parse(input);
|
|
124
|
-
const tokenId = parsed.tokenId ?? parsed.token_id;
|
|
125
|
-
const current = await readExactToken(ctx, tokenId, parsed.expected_created_at);
|
|
126
|
-
if (!ctx.confirmed)
|
|
127
|
-
return current;
|
|
128
|
-
if (current.disposition === "conflict") {
|
|
129
|
-
throw codedError("token_identity_changed", current.message);
|
|
130
|
-
}
|
|
131
|
-
if (current.disposition === "satisfied") {
|
|
132
|
-
return {
|
|
133
|
-
id: tokenId,
|
|
134
|
-
created_at: parsed.expected_created_at ?? current.target?.created_at ?? null,
|
|
135
|
-
status: "revoked",
|
|
136
|
-
reconciled: true,
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
if (!current.target.created_at) {
|
|
140
|
-
throw codedError("token_identity_unavailable", `Enrollment token ${tokenId} has no stable creation identity and cannot be revoked safely.`);
|
|
141
|
-
}
|
|
142
|
-
const revoked = (await ctx.fetch(`/enrollment-tokens/${encodeURIComponent(tokenId)}`, {
|
|
143
|
-
method: "DELETE",
|
|
144
|
-
...(parsed.idempotency_key ? { headers: { "Idempotency-Key": parsed.idempotency_key } } : {}),
|
|
145
|
-
}));
|
|
146
|
-
return {
|
|
147
|
-
id: tokenId,
|
|
148
|
-
created_at: current.target.created_at,
|
|
149
|
-
organization_id: current.target.organization_id,
|
|
150
|
-
expires_at: current.target.expires_at,
|
|
151
|
-
status: "revoked",
|
|
152
|
-
reconciled: false,
|
|
153
|
-
...(typeof revoked?.deleted === "boolean" ? { deleted: revoked.deleted } : {}),
|
|
154
|
-
};
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
const nodeEnrollInput = z.object({
|
|
158
|
-
node_id: UUID,
|
|
159
|
-
host: nonEmpty,
|
|
160
|
-
organization_id: UUID,
|
|
161
|
-
facility_id: UUID,
|
|
162
|
-
filter: z.string().optional(),
|
|
163
|
-
identity_file: nonEmpty,
|
|
164
|
-
private_key: z.string().min(1).optional(),
|
|
165
|
-
username: nonEmpty.default("root"),
|
|
166
|
-
port: z.number().int().min(1).max(65535).default(22),
|
|
167
|
-
expires_in_seconds: tokenTtl,
|
|
168
|
-
installer_base: nonEmpty.optional(),
|
|
169
|
-
replace_existing_management: z.literal(true),
|
|
170
|
-
verbose: z.boolean().default(false),
|
|
171
|
-
});
|
|
172
|
-
function safeRemoteResult(value) {
|
|
173
|
-
if (!value || typeof value !== "object")
|
|
174
|
-
return {};
|
|
175
|
-
const row = value;
|
|
176
|
-
const safe = {};
|
|
177
|
-
for (const key of ["id", "status", "state", "exit_code", "started_at", "completed_at"]) {
|
|
178
|
-
const field = row[key];
|
|
179
|
-
if (typeof field === "string" || typeof field === "number" || typeof field === "boolean") {
|
|
180
|
-
safe[key] = field;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
return safe;
|
|
184
|
-
}
|
|
185
|
-
export const adminNodeEnrollCapability = defineCapability({
|
|
186
|
-
id: "nodes.enroll",
|
|
187
|
-
domain: "fleet",
|
|
188
|
-
roles: ["admin"],
|
|
189
|
-
description: "Enroll one explicit node over SSH with an explicit supplier and facility assignment.",
|
|
190
|
-
mutation: "preview-confirm",
|
|
191
|
-
http: { method: "POST", path: "/remote" },
|
|
192
|
-
cli: {
|
|
193
|
-
command: ["admin", "nodes", "enroll"],
|
|
194
|
-
vnext: adminHelp(["admin", "nodes", "enroll"], "Install Ornn management on one explicitly assigned node.", "ornn admin nodes enroll <node-id> --host HOST --supplier UUID --facility UUID [--filter COMPARTMENT_IDS] --identity-file PATH --token-ttl SECONDS --replace-existing-management [--verbose] []", "Unsupported: remote enrollment requires an eligible human terminal.", ["canonical node UUID", "canonical supplier organization UUID", "canonical facility UUID"], "Review node identity, SSH target, assignment, compartment filter, and replacement of existing management."),
|
|
195
|
-
},
|
|
196
|
-
mcp: false,
|
|
197
|
-
slack: false,
|
|
198
|
-
input: nodeEnrollInput,
|
|
199
|
-
execute: async (ctx, input) => {
|
|
200
|
-
const parsed = nodeEnrollInput.parse(input);
|
|
201
|
-
if (!ctx.confirmed) {
|
|
202
|
-
return {
|
|
203
|
-
node_id: parsed.node_id,
|
|
204
|
-
host: parsed.host,
|
|
205
|
-
organization_id: parsed.organization_id,
|
|
206
|
-
facility_id: parsed.facility_id,
|
|
207
|
-
filter: parsed.filter,
|
|
208
|
-
expires_in_seconds: parsed.expires_in_seconds,
|
|
209
|
-
replace_existing_management: true,
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
if (!parsed.private_key) {
|
|
213
|
-
throw codedError("identity_file_unreadable", "The SSH identity file could not be read.");
|
|
214
|
-
}
|
|
215
|
-
const minted = (await ctx.fetch("/enrollment-tokens", {
|
|
216
|
-
method: "POST",
|
|
217
|
-
body: {
|
|
218
|
-
organization_id: parsed.organization_id,
|
|
219
|
-
expires_in_seconds: parsed.expires_in_seconds,
|
|
220
|
-
},
|
|
221
|
-
}));
|
|
222
|
-
const token = typeof minted.token === "string"
|
|
223
|
-
? minted.token
|
|
224
|
-
: typeof minted.plaintext === "string"
|
|
225
|
-
? minted.plaintext
|
|
226
|
-
: "";
|
|
227
|
-
if (!token) {
|
|
228
|
-
throw codedError("token_material_unavailable", "The enrollment token was created without usable token material.");
|
|
229
|
-
}
|
|
230
|
-
const command = buildEnrollmentInstallCommand({
|
|
231
|
-
token,
|
|
232
|
-
base: parsed.installer_base ?? ctx.installerBaseUrl,
|
|
233
|
-
facilityId: parsed.facility_id,
|
|
234
|
-
filter: parsed.filter,
|
|
235
|
-
restoreNodeId: parsed.node_id,
|
|
236
|
-
force: true,
|
|
237
|
-
});
|
|
238
|
-
const remote = await ctx.fetch("/remote", {
|
|
239
|
-
method: "POST",
|
|
240
|
-
body: {
|
|
241
|
-
host: parsed.host,
|
|
242
|
-
username: parsed.username,
|
|
243
|
-
port: parsed.port,
|
|
244
|
-
private_key: parsed.private_key,
|
|
245
|
-
command,
|
|
246
|
-
timeout_seconds: 600,
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
return {
|
|
250
|
-
node_id: parsed.node_id,
|
|
251
|
-
host: parsed.host,
|
|
252
|
-
organization_id: parsed.organization_id,
|
|
253
|
-
facility_id: parsed.facility_id,
|
|
254
|
-
filter: parsed.filter,
|
|
255
|
-
management: "replaced",
|
|
256
|
-
token_metadata: {
|
|
257
|
-
id: typeof minted.id === "string" ? minted.id : null,
|
|
258
|
-
expires_at: typeof minted.expires_at === "string" ? minted.expires_at : null,
|
|
259
|
-
},
|
|
260
|
-
remote: safeRemoteResult(remote),
|
|
261
|
-
};
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
const nodeKeyInput = z.object({ node_id: UUID });
|
|
265
|
-
export const adminNodeExportKeyCapability = defineCapability({
|
|
266
|
-
id: "nodes.export-key",
|
|
267
|
-
domain: "fleet",
|
|
268
|
-
roles: ["admin"],
|
|
269
|
-
description: "Export an enrolled node's private admin key only through an explicit secret destination.",
|
|
270
|
-
mutation: "read",
|
|
271
|
-
http: { method: "GET", path: "/enrollment/admin-ssh-key" },
|
|
272
|
-
cli: {
|
|
273
|
-
command: ["admin", "nodes", "export-key"],
|
|
274
|
-
vnext: adminHelp(["admin", "nodes", "export-key"], "Export one node admin key deliberately.", "ornn admin nodes export-key <node-id> (--print | --output PATH) [--overwrite]", "ornn admin nodes export-key <node-id> --output PATH [--overwrite] --format json", ["canonical node UUID"]),
|
|
275
|
-
},
|
|
276
|
-
mcp: false,
|
|
277
|
-
slack: false,
|
|
278
|
-
input: nodeKeyInput,
|
|
279
|
-
execute: async (ctx, input) => {
|
|
280
|
-
const parsed = nodeKeyInput.parse(input);
|
|
281
|
-
return ctx.fetch(`/enrollment/admin-ssh-key?node_id=${encodeURIComponent(parsed.node_id)}`, {
|
|
282
|
-
method: "GET",
|
|
283
|
-
});
|
|
284
|
-
},
|
|
285
|
-
});
|
|
286
|
-
export const adminNodeRotateKeyCapability = defineCapability({
|
|
287
|
-
id: "nodes.rotate-key",
|
|
288
|
-
domain: "fleet",
|
|
289
|
-
roles: ["admin"],
|
|
290
|
-
description: "Replace a node's admin key and export the new private key explicitly.",
|
|
291
|
-
mutation: "preview-confirm",
|
|
292
|
-
http: { method: "POST", path: "/enrollment/admin-ssh-key" },
|
|
293
|
-
cli: {
|
|
294
|
-
command: ["admin", "nodes", "rotate-key"],
|
|
295
|
-
vnext: adminHelp(["admin", "nodes", "rotate-key"], "Rotate one node admin key and export the replacement deliberately.", "ornn admin nodes rotate-key <node-id> (--print | --output PATH) [--overwrite] []", "Unsupported: key rotation requires an eligible human terminal.", ["canonical node UUID"], "Review the node and loss of access through the previous private key."),
|
|
296
|
-
},
|
|
297
|
-
mcp: false,
|
|
298
|
-
slack: false,
|
|
299
|
-
input: nodeKeyInput,
|
|
300
|
-
execute: async (ctx, input) => {
|
|
301
|
-
const parsed = nodeKeyInput.parse(input);
|
|
302
|
-
if (!ctx.confirmed)
|
|
303
|
-
return { node_id: parsed.node_id };
|
|
304
|
-
return ctx.fetch(`/enrollment/admin-ssh-key?node_id=${encodeURIComponent(parsed.node_id)}`, {
|
|
305
|
-
method: "POST",
|
|
306
|
-
});
|
|
307
|
-
},
|
|
308
|
-
});
|
|
309
|
-
const replacements = {
|
|
310
|
-
"tokens.create": adminEnrollmentTokenCreateCapability,
|
|
311
|
-
"tokens.list": adminEnrollmentTokensListCapability,
|
|
312
|
-
};
|
|
313
|
-
/** Applies the canonical admin CLI bindings without changing MCP or Slack identities. */
|
|
314
|
-
export function bindAdminEnrollmentCapabilities(capabilities) {
|
|
315
|
-
return capabilities.map((capability) => replacements[capability.id] ?? capability);
|
|
316
|
-
}
|
|
317
|
-
export const adminEnrollmentCapabilities = [
|
|
318
|
-
adminEnrollmentTokenRevokeCapability,
|
|
319
|
-
adminNodeEnrollCapability,
|
|
320
|
-
adminNodeExportKeyCapability,
|
|
321
|
-
adminNodeRotateKeyCapability,
|
|
322
|
-
];
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { defineCapability } from "../capability.js";
|
|
3
|
-
import { nodeExecCapability } from "./fleet.js";
|
|
4
|
-
const nodeId = z
|
|
5
|
-
.string()
|
|
6
|
-
.min(1)
|
|
7
|
-
.refine((value) => value === value.trim(), {
|
|
8
|
-
message: "node_id must be an exact canonical node ID without surrounding whitespace",
|
|
9
|
-
});
|
|
10
|
-
export const adminNodeDiagnoseInput = z.object({
|
|
11
|
-
node_id: nodeId,
|
|
12
|
-
});
|
|
13
|
-
export const adminNodeExecInput = z.object({
|
|
14
|
-
node_id: nodeId,
|
|
15
|
-
command: z.string().min(1),
|
|
16
|
-
timeout_seconds: z.number().int().min(1).max(900),
|
|
17
|
-
});
|
|
18
|
-
function adminNodeHelp(command, outcome, human, automation, review) {
|
|
19
|
-
return {
|
|
20
|
-
audience: "admin",
|
|
21
|
-
category: "resource",
|
|
22
|
-
command,
|
|
23
|
-
outcome,
|
|
24
|
-
human,
|
|
25
|
-
automation,
|
|
26
|
-
acceptedReferences: ["canonical node ID"],
|
|
27
|
-
review,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
export const adminNodeDiagnoseCapability = defineCapability({
|
|
31
|
-
id: "node.diagnose",
|
|
32
|
-
domain: "fleet",
|
|
33
|
-
roles: ["admin"],
|
|
34
|
-
description: "Queue the staff-authorized diagnosis workflow for one exact node after a live review.",
|
|
35
|
-
mutation: "preview-confirm",
|
|
36
|
-
http: { method: "POST", path: "/jobs" },
|
|
37
|
-
cli: {
|
|
38
|
-
command: ["admin", "nodes", "diagnose"],
|
|
39
|
-
vnext: adminNodeHelp(["admin", "nodes", "diagnose"], "Queue diagnosis for one exact node.", "ornn admin nodes diagnose <node-id>", "Unavailable in machine and redirected modes; use an eligible human terminal.", "Exact node, lifecycle, reachability, and diagnosis workflow."),
|
|
40
|
-
},
|
|
41
|
-
mcp: false,
|
|
42
|
-
slack: false,
|
|
43
|
-
input: adminNodeDiagnoseInput,
|
|
44
|
-
execute: async (ctx, input) => {
|
|
45
|
-
const parsed = adminNodeDiagnoseInput.parse(input);
|
|
46
|
-
if (!ctx.confirmed) {
|
|
47
|
-
return {
|
|
48
|
-
action: "node.diagnose",
|
|
49
|
-
payload: { node_id: parsed.node_id, source: { mode: "cli" }, burn_time_minutes: 0 },
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return ctx.fetch("/jobs", {
|
|
53
|
-
method: "POST",
|
|
54
|
-
body: { node_id: parsed.node_id, source: { mode: "cli" }, burn_time_minutes: 0 },
|
|
55
|
-
});
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
|
-
export const adminNodeExecCapability = {
|
|
59
|
-
...nodeExecCapability,
|
|
60
|
-
mutation: "preview-confirm",
|
|
61
|
-
cli: {
|
|
62
|
-
...nodeExecCapability.cli,
|
|
63
|
-
vnext: adminNodeHelp(["admin", "nodes", "exec"], "Execute exact command bytes on one exact node.", "ornn admin nodes exec <node-id> --command COMMAND --timeout DURATION", "Unavailable in machine and redirected modes; use an eligible human terminal.", "Exact node, lifecycle, reachability, command bytes, and timeout."),
|
|
64
|
-
},
|
|
65
|
-
input: adminNodeExecInput,
|
|
66
|
-
execute: async (ctx, input) => {
|
|
67
|
-
const parsed = adminNodeExecInput.parse(input);
|
|
68
|
-
if (ctx.cli && !ctx.confirmed) {
|
|
69
|
-
return {
|
|
70
|
-
action: "node.exec",
|
|
71
|
-
payload: {
|
|
72
|
-
node_id: parsed.node_id,
|
|
73
|
-
command: parsed.command,
|
|
74
|
-
timeout_seconds: parsed.timeout_seconds,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
return nodeExecCapability.execute(ctx, {
|
|
79
|
-
node_id: parsed.node_id,
|
|
80
|
-
command: parsed.command,
|
|
81
|
-
timeout_seconds: parsed.timeout_seconds,
|
|
82
|
-
});
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
export const adminNodeDiagnosticCapabilities = [
|
|
86
|
-
adminNodeDiagnoseCapability,
|
|
87
|
-
adminNodeExecCapability,
|
|
88
|
-
];
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { defineCapability } from "../capability.js";
|
|
3
|
-
const nodeReference = z.string().trim().min(1);
|
|
4
|
-
const optionalText = z.string().trim().min(1).optional();
|
|
5
|
-
const commonInput = {
|
|
6
|
-
node_ref: nodeReference,
|
|
7
|
-
node_id: optionalText,
|
|
8
|
-
idempotency_key: optionalText,
|
|
9
|
-
reason: optionalText,
|
|
10
|
-
yes: z.boolean().optional(),
|
|
11
|
-
wait: z.boolean().optional(),
|
|
12
|
-
timeout_seconds: z.number().positive().optional(),
|
|
13
|
-
};
|
|
14
|
-
const rebootInput = z.object({
|
|
15
|
-
...commonInput,
|
|
16
|
-
machine_id: optionalText,
|
|
17
|
-
});
|
|
18
|
-
const hardResetInput = rebootInput.extend({
|
|
19
|
-
erase_local_data: z.literal(true),
|
|
20
|
-
});
|
|
21
|
-
const assignmentInput = z.object({
|
|
22
|
-
...commonInput,
|
|
23
|
-
reservation_id: z.string().trim().min(1),
|
|
24
|
-
});
|
|
25
|
-
function adminLifecycleHelp(command, outcome, human, automation, review) {
|
|
26
|
-
return {
|
|
27
|
-
audience: "admin",
|
|
28
|
-
category: "resource",
|
|
29
|
-
command,
|
|
30
|
-
outcome,
|
|
31
|
-
human,
|
|
32
|
-
automation,
|
|
33
|
-
acceptedReferences: ["canonical node ID", "exact unique node hostname in an eligible terminal"],
|
|
34
|
-
review,
|
|
35
|
-
resumableStates: ["accepted", "pending", "timed out", "interrupted"],
|
|
36
|
-
related: ["admin nodes health", "admin nodes list"],
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
function nodeId(input) {
|
|
40
|
-
if (!input.node_id)
|
|
41
|
-
throw new Error("The lifecycle target must be resolved before execution.");
|
|
42
|
-
return input.node_id;
|
|
43
|
-
}
|
|
44
|
-
function requestHeaders(idempotencyKey) {
|
|
45
|
-
return idempotencyKey ? { "Idempotency-Key": idempotencyKey } : undefined;
|
|
46
|
-
}
|
|
47
|
-
function preview(action, payload) {
|
|
48
|
-
return { ok: true, action, payload };
|
|
49
|
-
}
|
|
50
|
-
async function reboot(ctx, input, mode) {
|
|
51
|
-
const parsed = mode === "hard_reset" ? hardResetInput.parse(input) : rebootInput.parse(input);
|
|
52
|
-
const payload = {
|
|
53
|
-
node_id: parsed.node_id,
|
|
54
|
-
machine_id: parsed.machine_id,
|
|
55
|
-
mode,
|
|
56
|
-
...(mode === "hard_reset" ? { erase_local_data: true } : {}),
|
|
57
|
-
};
|
|
58
|
-
if (!ctx.confirmed)
|
|
59
|
-
return preview(`nodes.${mode}`, payload);
|
|
60
|
-
if (!parsed.machine_id)
|
|
61
|
-
throw new Error("The live machine target must be resolved before reboot.");
|
|
62
|
-
return ctx.fetch(`/nodes/${encodeURIComponent(parsed.machine_id)}/reboot`, {
|
|
63
|
-
method: "POST",
|
|
64
|
-
body: {
|
|
65
|
-
mode,
|
|
66
|
-
...(parsed.idempotency_key ? { request_id: parsed.idempotency_key } : {}),
|
|
67
|
-
},
|
|
68
|
-
headers: requestHeaders(parsed.idempotency_key),
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
export const adminNodeRebootCapability = defineCapability({
|
|
72
|
-
id: "nodes.reboot",
|
|
73
|
-
domain: "fleet",
|
|
74
|
-
roles: ["admin"],
|
|
75
|
-
description: "Reboot the exact live machine backing one managed node after a normalized review.",
|
|
76
|
-
mutation: "preview-confirm",
|
|
77
|
-
http: { method: "POST", path: "/nodes/{id}/reboot" },
|
|
78
|
-
cli: {
|
|
79
|
-
command: ["admin", "nodes", "reboot"],
|
|
80
|
-
vnext: adminLifecycleHelp(["admin", "nodes", "reboot"], "Reboot one exact live machine while preserving local data and access configuration.", "ornn admin nodes reboot <node-ref> [] [--no-wait] [--timeout SECONDS]", "Unsupported: reboot requires an eligible human terminal.", "Node and live-machine identities, supplier, facility, capacity assignment, and preserved access."),
|
|
81
|
-
},
|
|
82
|
-
mcp: false,
|
|
83
|
-
slack: false,
|
|
84
|
-
input: rebootInput,
|
|
85
|
-
execute: (ctx, input) => reboot(ctx, input, "reboot"),
|
|
86
|
-
});
|
|
87
|
-
export const adminNodeHardResetCapability = defineCapability({
|
|
88
|
-
id: "nodes.hard_reset",
|
|
89
|
-
domain: "fleet",
|
|
90
|
-
roles: ["admin"],
|
|
91
|
-
description: "Hard reset the exact live machine backing one node with explicit local-data erasure scope.",
|
|
92
|
-
mutation: "preview-confirm",
|
|
93
|
-
http: { method: "POST", path: "/nodes/{id}/reboot" },
|
|
94
|
-
cli: {
|
|
95
|
-
command: ["admin", "nodes", "hard-reset"],
|
|
96
|
-
vnext: adminLifecycleHelp(["admin", "nodes", "hard-reset"], "Erase local tenant data and hard reset one exact live machine.", "ornn admin nodes hard-reset <node-ref> --erase-local-data [] [--no-wait] [--timeout SECONDS]", "Unsupported: hard reset requires an eligible human terminal.", "Node and live-machine identities, supplier, facility, capacity assignment, erased local data, and restored keys."),
|
|
97
|
-
},
|
|
98
|
-
mcp: false,
|
|
99
|
-
slack: false,
|
|
100
|
-
input: hardResetInput,
|
|
101
|
-
execute: (ctx, input) => reboot(ctx, input, "hard_reset"),
|
|
102
|
-
});
|
|
103
|
-
export const adminNodeAssignCapability = defineCapability({
|
|
104
|
-
id: "nodes.assign",
|
|
105
|
-
domain: "fleet",
|
|
106
|
-
roles: ["admin"],
|
|
107
|
-
description: "Assign one exact node to one exact capacity reservation after consequence review.",
|
|
108
|
-
mutation: "preview-confirm",
|
|
109
|
-
http: { method: "POST", path: "/nodes/{id}/assign" },
|
|
110
|
-
cli: {
|
|
111
|
-
command: ["admin", "nodes", "assign"],
|
|
112
|
-
vnext: adminLifecycleHelp(["admin", "nodes", "assign"], "Assign one exact node to one exact capacity reservation.", "ornn admin nodes assign <node-ref> --reservation <reservation-id> [] [--no-wait]", "ornn admin nodes assign <node-id> --reservation <reservation-id> --idempotency-key KEY --format json", "Node, supplier, facility, previous and requested capacity assignments, and tenant access change."),
|
|
113
|
-
},
|
|
114
|
-
mcp: false,
|
|
115
|
-
slack: false,
|
|
116
|
-
input: assignmentInput,
|
|
117
|
-
execute: async (ctx, input) => {
|
|
118
|
-
const parsed = assignmentInput.parse(input);
|
|
119
|
-
const id = nodeId(parsed);
|
|
120
|
-
const body = {
|
|
121
|
-
reservation_id: parsed.reservation_id,
|
|
122
|
-
...(parsed.idempotency_key ? { request_id: parsed.idempotency_key } : {}),
|
|
123
|
-
};
|
|
124
|
-
if (!ctx.confirmed)
|
|
125
|
-
return preview("nodes.assign", { node_id: id, ...body });
|
|
126
|
-
return ctx.fetch(`/nodes/${encodeURIComponent(id)}/assign`, {
|
|
127
|
-
method: "POST",
|
|
128
|
-
body,
|
|
129
|
-
headers: requestHeaders(parsed.idempotency_key),
|
|
130
|
-
});
|
|
131
|
-
},
|
|
132
|
-
});
|
|
133
|
-
export const adminNodeLifecycleCapabilities = [
|
|
134
|
-
adminNodeRebootCapability,
|
|
135
|
-
adminNodeHardResetCapability,
|
|
136
|
-
adminNodeAssignCapability,
|
|
137
|
-
];
|