@agoric/swingset-vat 0.32.3-upgrade-18-dev-6ddbef0.0 → 0.32.3-upgrade-19-dev-c605745.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-vat",
3
- "version": "0.32.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
3
+ "version": "0.32.3-upgrade-19-dev-c605745.0+c605745",
4
4
  "description": "Vat/Container Launcher",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -27,44 +27,44 @@
27
27
  "@types/yargs-parser": "^21.0.0"
28
28
  },
29
29
  "dependencies": {
30
- "@agoric/internal": "0.3.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
31
- "@agoric/kmarshal": "0.1.1-upgrade-18-dev-6ddbef0.0+6ddbef0",
32
- "@agoric/store": "0.9.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
33
- "@agoric/swing-store": "0.9.2-upgrade-18-dev-6ddbef0.0+6ddbef0",
34
- "@agoric/swingset-liveslots": "0.10.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
35
- "@agoric/swingset-xsnap-supervisor": "0.10.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
36
- "@agoric/time": "0.3.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
37
- "@agoric/vat-data": "0.5.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
38
- "@agoric/xsnap": "0.14.3-upgrade-18-dev-6ddbef0.0+6ddbef0",
39
- "@agoric/xsnap-lockdown": "0.14.1-upgrade-18-dev-6ddbef0.0+6ddbef0",
40
- "@endo/base64": "^1.0.8",
41
- "@endo/bundle-source": "^3.4.2",
42
- "@endo/captp": "^4.4.2",
43
- "@endo/check-bundle": "^1.0.11",
44
- "@endo/compartment-mapper": "^1.3.1",
45
- "@endo/errors": "^1.2.7",
46
- "@endo/eventual-send": "^1.2.7",
47
- "@endo/far": "^1.1.8",
48
- "@endo/import-bundle": "^1.3.1",
49
- "@endo/init": "^1.1.6",
50
- "@endo/marshal": "^1.6.1",
51
- "@endo/nat": "^5.0.12",
52
- "@endo/pass-style": "^1.4.6",
53
- "@endo/patterns": "^1.4.6",
54
- "@endo/promise-kit": "^1.1.7",
55
- "@endo/ses-ava": "^1.2.7",
56
- "@endo/stream": "^1.2.7",
57
- "@endo/zip": "^1.0.8",
30
+ "@agoric/internal": "0.3.3-upgrade-19-dev-c605745.0+c605745",
31
+ "@agoric/kmarshal": "0.1.1-upgrade-19-dev-c605745.0+c605745",
32
+ "@agoric/store": "0.9.3-upgrade-19-dev-c605745.0+c605745",
33
+ "@agoric/swing-store": "0.9.2-upgrade-19-dev-c605745.0+c605745",
34
+ "@agoric/swingset-liveslots": "0.10.3-upgrade-19-dev-c605745.0+c605745",
35
+ "@agoric/swingset-xsnap-supervisor": "0.10.3-upgrade-19-dev-c605745.0+c605745",
36
+ "@agoric/time": "0.3.3-upgrade-19-dev-c605745.0+c605745",
37
+ "@agoric/vat-data": "0.5.3-upgrade-19-dev-c605745.0+c605745",
38
+ "@agoric/xsnap-lockdown": "0.14.1-upgrade-19-dev-c605745.0+c605745",
39
+ "@endo/base64": "^1.0.9",
40
+ "@endo/bundle-source": "^3.5.1",
41
+ "@endo/captp": "^4.4.4",
42
+ "@endo/check-bundle": "^1.0.13",
43
+ "@endo/compartment-mapper": "^1.5.0",
44
+ "@endo/errors": "^1.2.9",
45
+ "@endo/eventual-send": "^1.3.0",
46
+ "@endo/far": "^1.1.10",
47
+ "@endo/import-bundle": "^1.3.3",
48
+ "@endo/init": "^1.1.8",
49
+ "@endo/marshal": "^1.6.3",
50
+ "@endo/nat": "^5.0.14",
51
+ "@endo/pass-style": "^1.4.8",
52
+ "@endo/patterns": "^1.4.8",
53
+ "@endo/promise-kit": "^1.1.9",
54
+ "@endo/ses-ava": "^1.2.9",
55
+ "@endo/stream": "^1.2.9",
56
+ "@endo/zip": "^1.0.9",
58
57
  "ansi-styles": "^6.2.1",
59
58
  "anylogger": "^0.21.0",
60
59
  "better-sqlite3": "^9.1.1",
61
- "import-meta-resolve": "^2.2.1",
60
+ "import-meta-resolve": "^4.1.0",
62
61
  "microtime": "^3.1.0",
63
62
  "semver": "^6.3.0",
64
63
  "tmp": "^0.2.1",
65
64
  "yargs-parser": "^21.1.1"
66
65
  },
67
66
  "peerDependencies": {
67
+ "@agoric/xsnap": "^0.14.2",
68
68
  "ava": "^5.3.0"
69
69
  },
70
70
  "files": [
@@ -94,14 +94,13 @@
94
94
  "require": [
95
95
  "@endo/init/debug.js"
96
96
  ],
97
- "timeout": "20m",
98
- "workerThreads": false
97
+ "timeout": "20m"
99
98
  },
100
99
  "publishConfig": {
101
100
  "access": "public"
102
101
  },
103
102
  "typeCoverage": {
104
- "atLeast": 75.82
103
+ "atLeast": 76.28
105
104
  },
106
- "gitHead": "6ddbef09f18c18f205e8d166c363ff4884115ce9"
105
+ "gitHead": "c605745ee6619292b51cec5fc0db0a25ff1b203c"
107
106
  }
@@ -15,6 +15,8 @@ import { initSwingStore } from '@agoric/swing-store';
15
15
 
16
16
  import { mustMatch, M } from '@endo/patterns';
17
17
  import { checkBundle } from '@endo/check-bundle/lite.js';
18
+ import { deepCopyJsonable } from '@agoric/internal/src/js-utils.js';
19
+ import { makeLimitedConsole } from '@agoric/internal/src/ses-utils.js';
18
20
  import engineGC from '@agoric/internal/src/lib-nodejs/engine-gc.js';
19
21
  import { startSubprocessWorker } from '@agoric/internal/src/lib-nodejs/spawnSubprocessWorker.js';
20
22
  import { waitUntilQuiescent } from '@agoric/internal/src/lib-nodejs/waitUntilQuiescent.js';
@@ -34,6 +36,10 @@ import {
34
36
  import { makeStartXSnap } from './startXSnap.js';
35
37
  import { makeStartSubprocessWorkerNode } from './startNodeSubprocess.js';
36
38
 
39
+ /**
40
+ * @import {EReturn} from '@endo/far';
41
+ */
42
+
37
43
  /**
38
44
  * @typedef { import('../types-internal.js').VatID } VatID
39
45
  */
@@ -51,34 +57,32 @@ export function computeSha512(bytes) {
51
57
  return hash.digest().toString('hex');
52
58
  }
53
59
 
54
- /** @param {string | ((args: unknown[]) => string)} tagOrTagCreator */
55
- function makeConsole(tagOrTagCreator) {
56
- /** @type {(level: string) => (args: unknown[]) => void} */
57
- let makeLoggerForLevel;
58
- if (typeof tagOrTagCreator === 'function') {
59
- const tagToLogger = new Map();
60
- makeLoggerForLevel =
61
- level =>
62
- (...args) => {
63
- // Retrieve the logger from cache.
64
- const tag = tagOrTagCreator(args);
65
- let logger = tagToLogger.get(tag);
66
- if (!logger) {
67
- logger = anylogger(tag);
68
- tagToLogger.set(tag, logger);
69
- }
70
- // Actually log the message.
71
- return logger[level](...args);
72
- };
73
- } else {
74
- const logger = anylogger(tagOrTagCreator);
75
- makeLoggerForLevel = level => logger[level];
76
- }
77
- const cons = {};
78
- for (const level of ['debug', 'log', 'info', 'warn', 'error']) {
79
- cons[level] = makeLoggerForLevel(level);
60
+ /**
61
+ * Make logger functions from either a prefix string or a function that receives
62
+ * the first argument of a log-method invocation and returns a replacement that
63
+ * provides more detail for source identification.
64
+ *
65
+ * @param {string | ((originalSource: unknown) => string)} prefixer
66
+ */
67
+ function makeConsole(prefixer) {
68
+ if (typeof prefixer !== 'function') {
69
+ const logger = anylogger(prefixer);
70
+ return makeLimitedConsole(level => logger[level]);
80
71
  }
81
- return harden(cons);
72
+
73
+ const prefixToLogger = new Map();
74
+ return makeLimitedConsole(level => {
75
+ return (source, ...args) => {
76
+ const prefix = prefixer(source);
77
+ let logger = prefixToLogger.get(prefix);
78
+ if (!logger) {
79
+ logger = anylogger(prefix);
80
+ prefixToLogger.set(prefix, logger);
81
+ }
82
+
83
+ return logger[level](...args);
84
+ };
85
+ });
82
86
  }
83
87
 
84
88
  /**
@@ -208,17 +212,23 @@ export async function makeSwingsetController(
208
212
  process.on('unhandledRejection', unhandledRejectionHandler);
209
213
  }
210
214
 
211
- function kernelRequire(what) {
212
- Fail`kernelRequire unprepared to satisfy require(${what})`;
213
- }
215
+ const kernelConsole = makeConsole(`${debugPrefix}SwingSet:kernel`);
216
+ const sloggingKernelConsole = makeLimitedConsole(level => {
217
+ return (...args) => {
218
+ kernelConsole[level](...args);
219
+ writeSlogObject({ type: 'console', source: 'kernel', args });
220
+ };
221
+ });
214
222
  writeSlogObject({ type: 'import-kernel-start' });
215
223
  const kernelNS = await importBundle(kernelBundle, {
216
224
  filePrefix: 'kernel/...',
217
225
  endowments: {
218
- console: makeConsole(`${debugPrefix}SwingSet:kernel`),
226
+ console: sloggingKernelConsole,
219
227
  // See https://github.com/Agoric/agoric-sdk/issues/9515
220
228
  assert: globalThis.assert,
221
- require: kernelRequire,
229
+ require: harden(
230
+ what => Fail`kernelRequire unprepared to satisfy require(${what})`,
231
+ ),
222
232
  URL: globalThis.Base64, // Unavailable only on XSnap
223
233
  Base64: globalThis.Base64, // Available only on XSnap
224
234
  },
@@ -263,13 +273,6 @@ export async function makeSwingsetController(
263
273
 
264
274
  await kernel.start();
265
275
 
266
- /**
267
- * @param {T} x
268
- * @returns {T}
269
- * @template T
270
- */
271
- const defensiveCopy = x => JSON.parse(JSON.stringify(x));
272
-
273
276
  /**
274
277
  * Validate and install a code bundle.
275
278
  *
@@ -304,7 +307,7 @@ export async function makeSwingsetController(
304
307
  writeSlogObject,
305
308
 
306
309
  dump() {
307
- return defensiveCopy(kernel.dump());
310
+ return deepCopyJsonable(kernel.dump());
308
311
  },
309
312
 
310
313
  verboseDebugMode(flag) {
@@ -340,11 +343,11 @@ export async function makeSwingsetController(
340
343
  },
341
344
 
342
345
  getStats() {
343
- return defensiveCopy(kernel.getStats());
346
+ return deepCopyJsonable(kernel.getStats());
344
347
  },
345
348
 
346
349
  getStatus() {
347
- return defensiveCopy(kernel.getStatus());
350
+ return deepCopyJsonable(kernel.getStatus());
348
351
  },
349
352
 
350
353
  getActivityhash() {
@@ -483,7 +486,7 @@ export async function makeSwingsetController(
483
486
  *
484
487
  * The first `controller.run()` after this call will delete all
485
488
  * the old vat's state at once, unless you use a
486
- * [`runPolicy`](../docs/run-policy.md) to rate-limit cleanups.
489
+ * [`runPolicy`](../../docs/run-policy.md) to rate-limit cleanups.
487
490
  *
488
491
  * @param {VatID} vatID
489
492
  * @param {SwingSetCapData} reasonCD
@@ -500,7 +503,7 @@ export async function makeSwingsetController(
500
503
 
501
504
  return controller;
502
505
  }
503
- /** @typedef {Awaited<ReturnType<typeof makeSwingsetController>>} SwingsetController */
506
+ /** @typedef {EReturn<typeof makeSwingsetController>} SwingsetController */
504
507
 
505
508
  /**
506
509
  * NB: To be used only in tests. An app with this may not survive a reboot.
@@ -1,5 +1,3 @@
1
- /* eslint-disable no-use-before-define */
2
-
3
1
  import { assert, Fail } from '@endo/errors';
4
2
  import { makeMarshal } from '@endo/marshal';
5
3
  import { Far } from '@endo/far';
@@ -102,7 +100,6 @@ export async function initializeKernel(config, kernelStorage, options = {}) {
102
100
  // the VatManager, since it isn't available until the bundle is evaluated
103
101
  assertKnownOptions(creationOptions, [
104
102
  'enablePipelining',
105
- 'metered',
106
103
  'managerType',
107
104
  'enableDisavow',
108
105
  'enableSetup',
@@ -2,8 +2,8 @@
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
 
5
- import { assert, Fail } from '@endo/errors';
6
- import { makeTracer } from '@agoric/internal';
5
+ import { assert, b, Fail } from '@endo/errors';
6
+ import { deepCopyJsonable, makeTracer } from '@agoric/internal';
7
7
  import { mustMatch } from '@agoric/store';
8
8
  import bundleSource from '@endo/bundle-source';
9
9
  import { resolve as resolveModuleSpecifier } from 'import-meta-resolve';
@@ -86,16 +86,6 @@ export async function buildKernelBundles() {
86
86
  return harden({ kernel: kernelBundle, ...vdBundles });
87
87
  }
88
88
 
89
- function byName(a, b) {
90
- if (a.name < b.name) {
91
- return -1;
92
- }
93
- if (a.name > b.name) {
94
- return 1;
95
- }
96
- return 0;
97
- }
98
-
99
89
  /**
100
90
  * Scan a directory for files defining the vats to bootstrap for a swingset, and
101
91
  * produce a swingset config object for what was found there. Looks for files
@@ -126,18 +116,18 @@ export function loadBasedir(basedir, options = {}) {
126
116
  const { includeDevDependencies = false, bundleFormat = undefined } = options;
127
117
  /** @type { SwingSetConfigDescriptor } */
128
118
  const vats = {};
129
- const subs = fs.readdirSync(basedir, { withFileTypes: true });
130
- subs.sort(byName);
131
- for (const dirent of subs) {
132
- if (
133
- dirent.name.startsWith('vat-') &&
134
- dirent.name.endsWith('.js') &&
135
- dirent.isFile()
136
- ) {
137
- const name = dirent.name.slice('vat-'.length, -'.js'.length);
138
- const vatSourcePath = path.resolve(basedir, dirent.name);
139
- vats[name] = { sourceSpec: vatSourcePath, parameters: {} };
140
- }
119
+ const rVatName = /^vat-(.*)\.js$/s;
120
+ const files = fs.readdirSync(basedir, { withFileTypes: true });
121
+ const vatFiles = files.flatMap(dirent => {
122
+ const file = dirent.name;
123
+ const m = rVatName.exec(file);
124
+ return m && dirent.isFile() ? [{ file, label: m[1] }] : [];
125
+ });
126
+ // eslint-disable-next-line no-shadow,no-nested-ternary
127
+ vatFiles.sort((a, b) => (a.label < b.label ? -1 : a.label > b.label ? 1 : 0));
128
+ for (const { file, label } of vatFiles) {
129
+ const vatSourcePath = path.resolve(basedir, file);
130
+ vats[label] = { sourceSpec: vatSourcePath, parameters: {} };
141
131
  }
142
132
  /** @type {string | void} */
143
133
  let bootstrapPath = path.resolve(basedir, 'bootstrap.js');
@@ -185,37 +175,42 @@ async function resolveSpecFromConfig(referrer, specPath) {
185
175
  }
186
176
 
187
177
  /**
188
- * For each entry in a config descriptor (i.e, `vats`, `bundles`, etc), convert
189
- * it to normal form: resolve each pathname to a context-insensitive absolute
190
- * path and make sure it has a `parameters` property if it's supposed to.
178
+ * Convert each entry in a config descriptor group (`vats`/`bundles`/etc.) to
179
+ * normal form: resolve each pathname to a context-insensitive absolute path and
180
+ * run any other appropriate fixup.
191
181
  *
192
- * @param {SwingSetConfigDescriptor | void} desc The config descriptor to be normalized.
193
- * @param {string} referrer The pathname of the file or directory in which the
194
- * config file was found
195
- * @param {boolean} expectParameters `true` if the entries should have parameters (for
196
- * example, `true` for `vats` but `false` for bundles).
182
+ * @param {SwingSetConfig} config
183
+ * @param {'vats' | 'bundles' | 'devices'} groupName
184
+ * @param {string | undefined} configPath of the containing config file
185
+ * @param {string} referrer URL
186
+ * @param {(entry: SwingSetConfigProperties, name?: string) => void} [fixupEntry]
187
+ * A function to call on each entry to e.g. add defaults for missing fields
188
+ * such as vat `parameters`.
197
189
  */
198
- async function normalizeConfigDescriptor(desc, referrer, expectParameters) {
199
- const normalizeSpec = async (entry, key) => {
200
- return resolveSpecFromConfig(referrer, entry[key]).then(spec => {
201
- fs.existsSync(spec) ||
202
- Fail`spec for ${entry[key]} does not exist: ${spec}`;
203
- entry[key] = spec;
204
- });
190
+ async function normalizeConfigDescriptor(
191
+ config,
192
+ groupName,
193
+ configPath,
194
+ referrer,
195
+ fixupEntry,
196
+ ) {
197
+ const normalizeSpec = async (entry, specKey, name) => {
198
+ const sourcePath = await resolveSpecFromConfig(referrer, entry[specKey]);
199
+ fs.existsSync(sourcePath) ||
200
+ Fail`${sourcePath} for ${b(groupName)}[${name}].${b(specKey)} in ${configPath} config file does not exist`;
201
+ entry[specKey] = sourcePath;
205
202
  };
206
203
 
207
204
  const jobs = [];
205
+ const desc = config[groupName];
208
206
  if (desc) {
209
- for (const name of Object.keys(desc)) {
210
- const entry = desc[name];
207
+ for (const [name, entry] of Object.entries(desc)) {
208
+ fixupEntry?.(entry, name);
211
209
  if ('sourceSpec' in entry) {
212
- jobs.push(normalizeSpec(entry, 'sourceSpec'));
210
+ jobs.push(normalizeSpec(entry, 'sourceSpec', name));
213
211
  }
214
212
  if ('bundleSpec' in entry) {
215
- jobs.push(normalizeSpec(entry, 'bundleSpec'));
216
- }
217
- if (expectParameters && !entry.parameters) {
218
- entry.parameters = {};
213
+ jobs.push(normalizeSpec(entry, 'bundleSpec', name));
219
214
  }
220
215
  }
221
216
  }
@@ -223,27 +218,41 @@ async function normalizeConfigDescriptor(desc, referrer, expectParameters) {
223
218
  }
224
219
 
225
220
  /**
226
- * Read and parse a swingset config file and return it in normalized form.
227
- *
228
- * @param {string} configPath Path to the config file to be processed
229
- *
230
- * @returns {Promise<SwingSetConfig | null>} the contained config object, in normalized form, or null if the
231
- * requested config file did not exist.
221
+ * @param {SwingSetConfig} config
222
+ * @param {string} [configPath]
223
+ * @returns {Promise<void>}
224
+ * @throws {Error} if the config is invalid
225
+ */
226
+ export async function normalizeConfig(config, configPath) {
227
+ const base = `file://${process.cwd()}/`;
228
+ const referrer = configPath
229
+ ? new URL(configPath, base).href
230
+ : new URL(base).href;
231
+ const fixupVat = vat => (vat.parameters ||= {});
232
+ await Promise.all([
233
+ normalizeConfigDescriptor(config, 'vats', configPath, referrer, fixupVat),
234
+ normalizeConfigDescriptor(config, 'bundles', configPath, referrer),
235
+ // TODO: represent devices
236
+ // normalizeConfigDescriptor(config, 'devices', configPath, referrer),
237
+ ]);
238
+ config.bootstrap ||
239
+ Fail`no designated bootstrap vat in ${configPath} config file`;
240
+ (config.vats && config.vats[/** @type {string} */ (config.bootstrap)]) ||
241
+ Fail`bootstrap vat ${config.bootstrap} not found in ${configPath} config file`;
242
+ }
243
+
244
+ /**
245
+ * Read and normalize a swingset config file.
232
246
  *
233
- * @throws {Error} if the file existed but was inaccessible, malformed, or otherwise
234
- * invalid.
247
+ * @param {string} configPath
248
+ * @returns {Promise<SwingSetConfig | null>} the normalized config,
249
+ * or null if the file did not exist
235
250
  */
236
251
  export async function loadSwingsetConfigFile(configPath) {
237
252
  await null;
238
253
  try {
239
254
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
240
- const referrer = new URL(configPath, `file://${process.cwd()}/`).toString();
241
- await normalizeConfigDescriptor(config.vats, referrer, true);
242
- await normalizeConfigDescriptor(config.bundles, referrer, false);
243
- // await normalizeConfigDescriptor(config.devices, referrer, true); // TODO: represent devices
244
- config.bootstrap || Fail`no designated bootstrap vat in ${configPath}`;
245
- (config.vats && config.vats[config.bootstrap]) ||
246
- Fail`bootstrap vat ${config.bootstrap} not found in ${configPath}`;
255
+ await normalizeConfig(config, configPath);
247
256
  return config;
248
257
  } catch (e) {
249
258
  console.error(`failed to load ${configPath}`);
@@ -314,7 +323,7 @@ export async function initializeSwingset(
314
323
  } = runtimeOptions;
315
324
 
316
325
  // copy config so we can safely mess with it even if it's shared or hardened
317
- config = JSON.parse(JSON.stringify(config));
326
+ config = deepCopyJsonable(config);
318
327
  if (!config.bundles) {
319
328
  config.bundles = {};
320
329
  }
@@ -433,26 +442,24 @@ export async function initializeSwingset(
433
442
  * The host application gives us
434
443
  * config.[vats|devices].NAME.[bundle|bundleSpec|sourceSpec|bundleName] .
435
444
  * The 'bundleName' option points into
436
- * config.bundles.BUNDLENAME.[bundle|bundleSpec|sourceSpec] , which can
445
+ * config.bundles.BUNDLENAME.[bundle|bundleSpec|sourceSpec], which can
437
446
  * also include arbitrary named bundles that will be made available to
438
- * E(vatAdminService).getNamedBundleCap(bundleName) ,and temporarily as
447
+ * E(vatAdminService).getNamedBundleCap(bundleName), and temporarily as
439
448
  * E(vatAdminService).createVatByName(bundleName)
440
449
  *
441
450
  * The 'kconfig' we pass through to initializeKernel has
442
451
  * kconfig.[vats|devices].NAME.bundleID and
443
- * kconfig.namedBundleIDs.BUNDLENAME=bundleID , which both point into
452
+ * kconfig.namedBundleIDs.BUNDLENAME=bundleID, which both point into
444
453
  * kconfig.idToBundle.BUNDLEID=bundle
445
454
  *
446
- * @param {SwingSetConfigProperties | { bundleName: string }} desc
455
+ * @param {SwingSetConfigProperties} desc
447
456
  * @param {Record<string, *>} [nameToBundle]
448
457
  */
449
458
  async function getBundle(desc, nameToBundle) {
450
459
  trace(
451
460
  'getBundle',
452
461
  Object.keys(desc),
453
- // @ts-expect-error optional
454
462
  desc.moduleFormat,
455
- // @ts-expect-error optional
456
463
  desc.endoZipBase64Sha512 || desc.sourceSpec,
457
464
  );
458
465
 
@@ -480,8 +487,9 @@ export async function initializeSwingset(
480
487
  throw Error(`unknown mode in desc`, desc);
481
488
  }
482
489
 
483
- // fires with BundleWithID: { ...bundle, id }
484
490
  /**
491
+ * Returns a bundle record with an "id" property from an input that might be missing it.
492
+ *
485
493
  * @param {EndoZipBase64Bundle & {id?: string}} bundle
486
494
  * @returns {Promise<EndoZipBase64Bundle & {id: string}>} bundle
487
495
  */
@@ -499,11 +507,9 @@ export async function initializeSwingset(
499
507
  };
500
508
  }
501
509
 
502
- // fires with BundleWithID: { ...bundle, id }
503
-
504
510
  /**
505
511
  *
506
- * @param {(SwingSetConfigProperties | { bundleName: string }) & {bundleID?: string }} desc
512
+ * @param {SwingSetConfigProperties & {bundleID?: string}} desc
507
513
  * @param {Record<string, EndoZipBase64Bundle>} [nameToBundle]
508
514
  */
509
515
  async function processDesc(desc, nameToBundle) {
@@ -517,14 +523,14 @@ export async function initializeSwingset(
517
523
  modes.length === 1 ||
518
524
  Fail`need =1 of bundle/bundleSpec/sourceSpec/bundleName, got ${modes}`;
519
525
  const mode = modes[0];
520
- return getBundle(desc, nameToBundle)
521
- .then(addBundleID)
522
- .then(bundleWithID => {
523
- // replace original .sourceSpec/etc with a uniform .bundleID
524
- delete desc[mode];
525
- desc.bundleID = bundleWithID.id;
526
- return bundleWithID;
527
- });
526
+
527
+ // Remove the original mode in favor of a uniform "bundleID" property.
528
+ const bundle = await getBundle(desc, nameToBundle);
529
+ const bundleWithID = await addBundleID(bundle);
530
+ delete desc[mode];
531
+ desc.bundleID = bundleWithID.id;
532
+
533
+ return bundleWithID;
528
534
  }
529
535
 
530
536
  /**