@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
package/src/slurm-commands.mjs
CHANGED
|
@@ -33,6 +33,17 @@ function commandText(invocation) {
|
|
|
33
33
|
return [invocation.command, ...invocation.args].map(shellQuote).join(" ");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
// Preserve the CLI result's summary; reads and picker state use Commerce JSON.
|
|
37
|
+
function reservationSummary(reservation) {
|
|
38
|
+
return {
|
|
39
|
+
id: reservation.reservationId,
|
|
40
|
+
facility: null,
|
|
41
|
+
hardware: reservation.gpuSlug ?? (reservation.reservedGpuCount === 0 ? "cpu" : null),
|
|
42
|
+
machine_count: reservation.reservedNodeCount,
|
|
43
|
+
region: null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
function safeLogin(runtime, credentials, input) {
|
|
37
48
|
const credentialType = text(credentials.credential_type);
|
|
38
49
|
if (credentialType && credentialType !== "login-ssh") {
|
|
@@ -159,7 +170,7 @@ export function createSlurmConnectModeHandler({ spawnProcess = spawnProcessDefau
|
|
|
159
170
|
return {
|
|
160
171
|
action: retryConnectAction(prepared.cluster),
|
|
161
172
|
data: {
|
|
162
|
-
capacity_reservation: prepared.capacityReservation,
|
|
173
|
+
capacity_reservation: reservationSummary(prepared.capacityReservation),
|
|
163
174
|
cluster: prepared.cluster,
|
|
164
175
|
private_transport: transport,
|
|
165
176
|
ssh: null,
|
|
@@ -198,7 +209,7 @@ export function createSlurmConnectModeHandler({ spawnProcess = spawnProcessDefau
|
|
|
198
209
|
user: login.user,
|
|
199
210
|
};
|
|
200
211
|
const data = {
|
|
201
|
-
capacity_reservation: prepared.capacityReservation,
|
|
212
|
+
capacity_reservation: reservationSummary(prepared.capacityReservation),
|
|
202
213
|
cluster: prepared.cluster,
|
|
203
214
|
private_transport: transport,
|
|
204
215
|
ssh,
|
package/src/vm-commands.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getReservation } from "../vendor/api-client/commerce.js";
|
|
1
2
|
import { spawn as spawnProcessDefault } from "node:child_process";
|
|
2
3
|
import { createHash } from "node:crypto";
|
|
3
4
|
import { readFile } from "node:fs/promises";
|
|
@@ -9,6 +10,7 @@ import { resolvedMountPath } from "./mount-commands.mjs";
|
|
|
9
10
|
import {
|
|
10
11
|
isReferenceMiss,
|
|
11
12
|
listActiveDirectReservations,
|
|
13
|
+
listActiveReservations,
|
|
12
14
|
listOwnedActiveMachines,
|
|
13
15
|
reservationLabeler,
|
|
14
16
|
resolveMachineReference,
|
|
@@ -70,14 +72,13 @@ function machineAccessMode(value) {
|
|
|
70
72
|
|
|
71
73
|
function reservationAccessMode(value) {
|
|
72
74
|
const reservation = record(value);
|
|
73
|
-
const mode =
|
|
74
|
-
(text(reservation.access_mode) ?? text(reservation.accessMode))?.toLowerCase() ?? null;
|
|
75
|
+
const mode = text(reservation.provisioningType)?.toLowerCase() ?? null;
|
|
75
76
|
return mode === "virtual-machine" ? "vm" : mode;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function isDirectReservation(value) {
|
|
79
80
|
const mode = reservationAccessMode(value);
|
|
80
|
-
return
|
|
81
|
+
return mode === "vm" || mode === "bare-metal";
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
function machineIsReady(value) {
|
|
@@ -424,7 +425,7 @@ async function selectedOrganization(context, interaction, expectedId) {
|
|
|
424
425
|
|
|
425
426
|
function assertActiveReservation(value, expectedRef, interaction, expectedMode = null) {
|
|
426
427
|
const reservation = record(value);
|
|
427
|
-
const ref =
|
|
428
|
+
const ref = text(reservation.reservationId);
|
|
428
429
|
if (!ref || ref !== expectedRef) {
|
|
429
430
|
interaction.error("The gateway returned an unrelated capacity reservation.", {
|
|
430
431
|
code: "reservation_correlation_changed",
|
|
@@ -463,7 +464,7 @@ async function resolveReservation(context, input, interaction, expectedMode = nu
|
|
|
463
464
|
if (supplied) {
|
|
464
465
|
const ref = requireCanonical(interaction, supplied, "reservation", "Capacity reservation");
|
|
465
466
|
return assertActiveReservation(
|
|
466
|
-
await context.fetch
|
|
467
|
+
await getReservation(context.fetch, ref),
|
|
467
468
|
ref,
|
|
468
469
|
interaction,
|
|
469
470
|
expectedMode
|
|
@@ -475,29 +476,23 @@ async function resolveReservation(context, input, interaction, expectedMode = nu
|
|
|
475
476
|
field: "reservation",
|
|
476
477
|
});
|
|
477
478
|
}
|
|
478
|
-
const payload = await context.fetch("/v1/workflows/commerce/reservations?status=active&limit=50");
|
|
479
479
|
const requestedMode = expectedMode ?? text(input.mode)?.toLowerCase() ?? null;
|
|
480
|
-
const candidates = (
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
(reservation) =>
|
|
484
|
-
(text(reservation.status) ?? "").toLowerCase() === ACTIVE_RESERVATION &&
|
|
485
|
-
resourceId(reservation) &&
|
|
486
|
-
(!requestedMode || reservationAccessMode(reservation) === requestedMode)
|
|
487
|
-
);
|
|
480
|
+
const candidates = (await listActiveReservations(context, interaction)).filter(
|
|
481
|
+
(reservation) => !requestedMode || reservationAccessMode(reservation) === requestedMode
|
|
482
|
+
);
|
|
488
483
|
if (!candidates.length) {
|
|
489
484
|
interaction.error("No active capacity reservation is eligible for launch.", {
|
|
490
485
|
code: "no_active_reservation",
|
|
491
486
|
});
|
|
492
487
|
}
|
|
493
|
-
let ref =
|
|
488
|
+
let ref = text(candidates[0].reservationId);
|
|
494
489
|
if (candidates.length > 1) {
|
|
495
490
|
const reservationLabel = await reservationLabeler(context.fetch);
|
|
496
491
|
ref = await interaction.ask({
|
|
497
492
|
label: "Capacity reservation",
|
|
498
493
|
choices: candidates.map((reservation) => ({
|
|
499
494
|
label: reservationLabel(reservation),
|
|
500
|
-
value:
|
|
495
|
+
value: text(reservation.reservationId),
|
|
501
496
|
})),
|
|
502
497
|
});
|
|
503
498
|
}
|
|
@@ -506,7 +501,7 @@ async function resolveReservation(context, input, interaction, expectedMode = nu
|
|
|
506
501
|
code: "missing_input",
|
|
507
502
|
});
|
|
508
503
|
return assertActiveReservation(
|
|
509
|
-
await context.fetch
|
|
504
|
+
await getReservation(context.fetch, ref),
|
|
510
505
|
ref,
|
|
511
506
|
interaction,
|
|
512
507
|
expectedMode
|
|
@@ -1044,7 +1039,7 @@ export const vmLaunchModeHandler = {
|
|
|
1044
1039
|
input = await normalizePublicKeyInput(input);
|
|
1045
1040
|
const organization = await selectedOrganization(context, interaction);
|
|
1046
1041
|
const reservation = await resolveReservation(context, input, interaction, "vm");
|
|
1047
|
-
const reservationId =
|
|
1042
|
+
const reservationId = text(reservation.reservationId);
|
|
1048
1043
|
const keyPlan = await keyPlanFromInput(context, input, interaction);
|
|
1049
1044
|
const [reviewedMachines, reviewedRelationships] = await Promise.all([
|
|
1050
1045
|
fetchMachines(context, reservationId),
|
|
@@ -1095,9 +1090,7 @@ export const vmLaunchModeHandler = {
|
|
|
1095
1090
|
|
|
1096
1091
|
await selectedOrganization(context, interaction, organization.id);
|
|
1097
1092
|
const currentReservation = assertActiveReservation(
|
|
1098
|
-
await context.fetch
|
|
1099
|
-
`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`
|
|
1100
|
-
),
|
|
1093
|
+
await getReservation(context.fetch, reservationId),
|
|
1101
1094
|
reservationId,
|
|
1102
1095
|
interaction,
|
|
1103
1096
|
"vm"
|
|
@@ -1348,15 +1341,13 @@ function reservationAllocationContext(value) {
|
|
|
1348
1341
|
const reservation = record(value);
|
|
1349
1342
|
return {
|
|
1350
1343
|
term: {
|
|
1351
|
-
start: text(reservation.
|
|
1352
|
-
end: text(reservation.
|
|
1344
|
+
start: text(reservation.startsAt),
|
|
1345
|
+
end: text(reservation.endsAt),
|
|
1353
1346
|
},
|
|
1354
|
-
machine_count:
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
: null,
|
|
1359
|
-
gpu_count: Number.isSafeInteger(reservation.gpu_count) ? reservation.gpu_count : null,
|
|
1347
|
+
machine_count: reservation.reservedNodeCount,
|
|
1348
|
+
gpu_count: Number.isSafeInteger(reservation.reservedGpuCount)
|
|
1349
|
+
? reservation.reservedGpuCount
|
|
1350
|
+
: null,
|
|
1360
1351
|
};
|
|
1361
1352
|
}
|
|
1362
1353
|
|
|
@@ -1420,7 +1411,7 @@ function bareMetalIntentSignature(reservation, relationships) {
|
|
|
1420
1411
|
}
|
|
1421
1412
|
|
|
1422
1413
|
function bareMetalLaunchData(reservation, machine, key, relationships, waitPolicy, replayed) {
|
|
1423
|
-
const reservationId =
|
|
1414
|
+
const reservationId = text(reservation.reservationId);
|
|
1424
1415
|
const invocation = isSshReady(machine, key) ? sshInvocation(machine) : null;
|
|
1425
1416
|
return {
|
|
1426
1417
|
mode: "bare-metal",
|
|
@@ -1506,16 +1497,13 @@ function durableWorkspaceAllocation(reservation, interaction) {
|
|
|
1506
1497
|
const endMilliseconds = Date.parse(end ?? "");
|
|
1507
1498
|
const duration = endMilliseconds - startMilliseconds;
|
|
1508
1499
|
const machineCount = allocation.machine_count;
|
|
1509
|
-
const gpusPerMachine =
|
|
1510
|
-
? reservation.gpus_per_node
|
|
1511
|
-
: Number.isSafeInteger(reservation.gpus_per_machine)
|
|
1512
|
-
? reservation.gpus_per_machine
|
|
1513
|
-
: null;
|
|
1500
|
+
const gpusPerMachine = reservation.gpusPerNode;
|
|
1514
1501
|
const gpuCount = Number.isSafeInteger(allocation.gpu_count)
|
|
1515
1502
|
? allocation.gpu_count
|
|
1516
1503
|
: Number.isSafeInteger(machineCount) && Number.isSafeInteger(gpusPerMachine)
|
|
1517
1504
|
? machineCount * gpusPerMachine
|
|
1518
1505
|
: null;
|
|
1506
|
+
// CPU capacity has no GPUs; a workspace needs only its whole machines.
|
|
1519
1507
|
if (
|
|
1520
1508
|
!start ||
|
|
1521
1509
|
!end ||
|
|
@@ -1523,9 +1511,7 @@ function durableWorkspaceAllocation(reservation, interaction) {
|
|
|
1523
1511
|
!Number.isFinite(endMilliseconds) ||
|
|
1524
1512
|
duration <= 0 ||
|
|
1525
1513
|
!Number.isSafeInteger(machineCount) ||
|
|
1526
|
-
machineCount < 1
|
|
1527
|
-
!Number.isSafeInteger(gpuCount) ||
|
|
1528
|
-
gpuCount < 1
|
|
1514
|
+
machineCount < 1
|
|
1529
1515
|
) {
|
|
1530
1516
|
interaction.error("The active capacity reservation does not contain valid allocation facts.", {
|
|
1531
1517
|
code: "reservation_allocation_incomplete",
|
|
@@ -1627,7 +1613,7 @@ function workspaceRelationshipConflict(facts, goal, interaction) {
|
|
|
1627
1613
|
function durableWorkspaceSignature(reservation, key, facts, goal, interaction) {
|
|
1628
1614
|
return JSON.stringify({
|
|
1629
1615
|
capacity_reservation: {
|
|
1630
|
-
id:
|
|
1616
|
+
id: text(reservation.reservationId),
|
|
1631
1617
|
status: text(reservation.status),
|
|
1632
1618
|
allocation: durableWorkspaceAllocation(reservation, interaction),
|
|
1633
1619
|
},
|
|
@@ -1784,7 +1770,7 @@ async function launchDurableBareMetalWorkspace({
|
|
|
1784
1770
|
keyPlan,
|
|
1785
1771
|
}) {
|
|
1786
1772
|
const id = workspaceId(input, interaction);
|
|
1787
|
-
const reservationId =
|
|
1773
|
+
const reservationId = text(reservation.reservationId);
|
|
1788
1774
|
if (keyPlan.kind !== "existing") {
|
|
1789
1775
|
interaction.usage("A durable Bare Metal workspace requires a declared existing SSH key.", {
|
|
1790
1776
|
code: "workspace_ssh_key_required",
|
|
@@ -1828,8 +1814,8 @@ async function launchDurableBareMetalWorkspace({
|
|
|
1828
1814
|
value: `${allocation.term.start} to ${allocation.term.end} (${allocation.term.hours} hours)`,
|
|
1829
1815
|
},
|
|
1830
1816
|
{
|
|
1831
|
-
label: "
|
|
1832
|
-
value: `${allocation.quantity.gpu_count} GPUs across ${allocation.quantity.machine_count} machine${allocation.quantity.machine_count === 1 ? "" : "s"}`,
|
|
1817
|
+
label: "Quantity",
|
|
1818
|
+
value: `${allocation.quantity.gpu_count > 0 ? `${allocation.quantity.gpu_count} GPUs` : "CPU"} across ${allocation.quantity.machine_count} machine${allocation.quantity.machine_count === 1 ? "" : "s"}`,
|
|
1833
1819
|
},
|
|
1834
1820
|
{
|
|
1835
1821
|
label: "Creation",
|
|
@@ -1860,7 +1846,7 @@ async function launchDurableBareMetalWorkspace({
|
|
|
1860
1846
|
|
|
1861
1847
|
await selectedOrganization(context, interaction, organization.id);
|
|
1862
1848
|
const [currentReservationValue, currentKeyPlan, currentFacts] = await Promise.all([
|
|
1863
|
-
context.fetch
|
|
1849
|
+
getReservation(context.fetch, reservationId),
|
|
1864
1850
|
keyPlanFromInput(context, input, interaction),
|
|
1865
1851
|
workspaceResourceFacts(context, interaction, input, reservationId),
|
|
1866
1852
|
]);
|
|
@@ -2290,7 +2276,7 @@ export const bareMetalLaunchModeHandler = {
|
|
|
2290
2276
|
input = await normalizePublicKeyInput(input);
|
|
2291
2277
|
const organization = await selectedOrganization(context, interaction, resumeOrganizationId);
|
|
2292
2278
|
const reservation = await resolveReservation(context, input, interaction, "bare-metal");
|
|
2293
|
-
const reservationId =
|
|
2279
|
+
const reservationId = text(reservation.reservationId);
|
|
2294
2280
|
const keyPlan = await keyPlanFromInput(context, input, interaction);
|
|
2295
2281
|
if (durableWorkspace) {
|
|
2296
2282
|
return launchDurableBareMetalWorkspace({
|
|
@@ -2358,7 +2344,7 @@ export const bareMetalLaunchModeHandler = {
|
|
|
2358
2344
|
|
|
2359
2345
|
const [currentReservationValue, currentRelationships, existingAssigned, projectedMachines] =
|
|
2360
2346
|
await Promise.all([
|
|
2361
|
-
context.fetch
|
|
2347
|
+
getReservation(context.fetch, reservationId),
|
|
2362
2348
|
bareMetalRelationships(context, reservationId),
|
|
2363
2349
|
fetchAssignedKeys(context, reservationId),
|
|
2364
2350
|
fetchMachines(context, reservationId),
|
|
@@ -2740,9 +2726,7 @@ function createDirectMachineConnectModeHandler({
|
|
|
2740
2726
|
code: "machine_correlation_failed",
|
|
2741
2727
|
});
|
|
2742
2728
|
}
|
|
2743
|
-
const reservation = await context.fetch
|
|
2744
|
-
`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`
|
|
2745
|
-
);
|
|
2729
|
+
const reservation = await getReservation(context.fetch, reservationId);
|
|
2746
2730
|
if (!isDirectReservation(reservation)) return null;
|
|
2747
2731
|
const privateTransportRequired = text(machine.network_mode) === "private";
|
|
2748
2732
|
return {
|
|
@@ -3074,9 +3058,7 @@ async function resolveMachineCommandTarget(input, context, interaction, usage) {
|
|
|
3074
3058
|
code: "machine_correlation_failed",
|
|
3075
3059
|
});
|
|
3076
3060
|
}
|
|
3077
|
-
const reservation = await context.fetch
|
|
3078
|
-
`/v1/workflows/commerce/reservations/${encodeURIComponent(reservationId)}`
|
|
3079
|
-
);
|
|
3061
|
+
const reservation = await getReservation(context.fetch, reservationId);
|
|
3080
3062
|
if (!isDirectReservation(reservation)) {
|
|
3081
3063
|
interaction.error(
|
|
3082
3064
|
"The selected machine belongs to a cluster rather than the direct-machine lifecycle.",
|
|
@@ -3331,19 +3313,19 @@ async function guidedSwitchReservation(context, interaction) {
|
|
|
3331
3313
|
code: "no_active_reservation",
|
|
3332
3314
|
});
|
|
3333
3315
|
}
|
|
3334
|
-
let reference =
|
|
3316
|
+
let reference = text(reservations[0].reservationId);
|
|
3335
3317
|
if (reservations.length > 1) {
|
|
3336
3318
|
const reservationLabel = await reservationLabeler(context.fetch);
|
|
3337
3319
|
reference = await interaction.ask({
|
|
3338
3320
|
label: "Capacity reservation",
|
|
3339
3321
|
choices: reservations.map((reservation) => ({
|
|
3340
3322
|
label: reservationLabel(reservation),
|
|
3341
|
-
value:
|
|
3323
|
+
value: text(reservation.reservationId),
|
|
3342
3324
|
})),
|
|
3343
3325
|
});
|
|
3344
3326
|
}
|
|
3345
3327
|
return assertActiveReservation(
|
|
3346
|
-
await context.fetch
|
|
3328
|
+
await getReservation(context.fetch, reference),
|
|
3347
3329
|
reference,
|
|
3348
3330
|
interaction
|
|
3349
3331
|
);
|
|
@@ -3380,14 +3362,12 @@ async function switchMachine(input, context, interaction) {
|
|
|
3380
3362
|
}
|
|
3381
3363
|
const reservation = suppliedReservation
|
|
3382
3364
|
? assertActiveReservation(
|
|
3383
|
-
await context.fetch
|
|
3384
|
-
`/v1/workflows/commerce/reservations/${encodeURIComponent(suppliedReservation)}`
|
|
3385
|
-
),
|
|
3365
|
+
await getReservation(context.fetch, suppliedReservation),
|
|
3386
3366
|
suppliedReservation,
|
|
3387
3367
|
interaction
|
|
3388
3368
|
)
|
|
3389
3369
|
: await guidedSwitchReservation(context, interaction);
|
|
3390
|
-
const reservationId =
|
|
3370
|
+
const reservationId = text(reservation.reservationId);
|
|
3391
3371
|
if (!isDirectReservation(reservation)) {
|
|
3392
3372
|
interaction.error("machines switch accepts only a direct VM or Bare Metal reservation.", {
|
|
3393
3373
|
code: "reservation_not_direct",
|
|
@@ -3496,7 +3476,7 @@ async function switchMachine(input, context, interaction) {
|
|
|
3496
3476
|
}
|
|
3497
3477
|
const [currentReservationValue, currentMachines, currentRelationships, currentKeys] =
|
|
3498
3478
|
await Promise.all([
|
|
3499
|
-
context.fetch
|
|
3479
|
+
getReservation(context.fetch, reservationId),
|
|
3500
3480
|
fetchMachines(context, reservationId),
|
|
3501
3481
|
bareMetalRelationships(context, reservationId),
|
|
3502
3482
|
fetchAssignedKeys(context, reservationId),
|