Repository: endojs/endo
Pull request #2321 (2024-06-13 22:18:56)

refactor(ses): Retool for module descriptors
Refs: #400 #2251 

## Description

Pursuant to arriving at parity with XS, this change internally reörients the SES module loader around the concept of a “module descriptor”. This creates some clarity and removes some existing duplication of work for “alias” module descriptors, and prepares the material for a richer module descriptor schema to match those implemented by XS.

### Security Considerations

No changes.

### Scaling Considerations

No changes.

### Documentation Considerations

No changes.

### Testing Considerations

No changes.

### Compatibility Considerations

No changes.

### Upgrade Considerations

No changes.


Patch:
diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js
index 088a17d17..97113f138 100644
--- a/packages/ses/src/compartment.js
+++ b/packages/ses/src/compartment.js
@@ -4,12 +4,10 @@
 
 import {
   Map,
-  ReferenceError,
   TypeError,
   WeakMap,
   assign,
   defineProperties,
-  entries,
   promiseThen,
   toStringTagSymbol,
   weakmapGet,
@@ -25,12 +23,9 @@ import { sharedGlobalPropertyNames } from './permits.js';
 import { load, loadNow } from './module-load.js';
 import { link } from './module-link.js';
 import { getDeferredExports } from './module-proxy.js';
-import { assert } from './error/assert.js';
 import { compartmentEvaluate } from './compartment-evaluate.js';
 import { makeSafeEvaluator } from './make-safe-evaluator.js';
 
-const { quote: q } = assert;
-
 // moduleAliases associates every public module exports namespace with its
 // corresponding compartment and specifier so they can be used to link modules
 // across compartments.
@@ -42,19 +37,6 @@ const moduleAliases = new WeakMap();
 // privateFields captures the private state for each compartment.
 const privateFields = new WeakMap();
 
-// Compartments do not need an importHook or resolveHook to be useful
-// as a vessel for evaluating programs.
-// However, any method that operates the module system will throw an exception
-// if these hooks are not available.
-const assertModuleHooks = compartment => {
-  const { importHook, resolveHook } = weakmapGet(privateFields, compartment);
-  if (typeof importHook !== 'function' || typeof resolveHook !== 'function') {
-    throw TypeError(
-      'Compartment must be constructed with an importHook and a resolveHook for it to be able to load modules',
-    );
-  }
-};
-
 export const InertCompartment = function Compartment(
   _endowments = {},
   _modules = {},
@@ -111,8 +93,6 @@ export const CompartmentPrototype = {
       throw TypeError('first argument of module() must be a string');
     }
 
-    assertModuleHooks(this);
-
     const { exportsProxy } = getDeferredExports(
       this,
       weakmapGet(privateFields, this),
@@ -128,8 +108,6 @@ export const CompartmentPrototype = {
       throw TypeError('first argument of import() must be a string');
     }
 
-    assertModuleHooks(this);
-
     return promiseThen(
       load(privateFields, moduleAliases, this, specifier),
       () => {
@@ -149,8 +127,6 @@ export const CompartmentPrototype = {
       throw TypeError('first argument of load() must be a string');
     }
 
-    assertModuleHooks(this);
-
     return load(privateFields, moduleAliases, this, specifier);
   },
 
@@ -159,7 +135,6 @@ export const CompartmentPrototype = {
       throw TypeError('first argument of importNow() must be a string');
     }
 
-    assertModuleHooks(this);
     loadNow(privateFields, moduleAliases, this, specifier);
     return compartmentImportNow(/** @type {Compartment} */ (this), specifier);
   },
@@ -222,30 +197,6 @@ export const makeCompartmentConstructor = (
     // Map<FullSpecifier, {ExportsProxy, ProxiedExports, activate()}>
     const deferredExports = new Map();
 
-    // Validate given moduleMap.
-    // The module map gets translated on-demand in module-load.js and the
-    // moduleMap can be invalid in ways that cannot be detected in the
-    // constructor, but these checks allow us to throw early for a better
-    // developer experience.
-    for (const [specifier, aliasNamespace] of entries(moduleMap || {})) {
-      if (typeof aliasNamespace === 'string') {
-        // TODO implement parent module record retrieval.
-        throw TypeError(
-          `Cannot map module ${q(specifier)} to ${q(
-            aliasNamespace,
-          )} in parent compartment`,
-        );
-      } else if (weakmapGet(moduleAliases, aliasNamespace) === undefined) {
-        // TODO create and link a synthetic module instance from the given
-        // namespace object.
-        throw ReferenceError(
-          `Cannot map module ${q(
-            specifier,
-          )} because it has no known compartment in this realm`,
-        );
-      }
-    }
-
     const globalObject = {};
 
     setGlobalObjectSymbolUnscopables(globalObject);
diff --git a/packages/ses/src/error/assert.js b/packages/ses/src/error/assert.js
index 7b2bb2163..1e642a526 100644
--- a/packages/ses/src/error/assert.js
+++ b/packages/ses/src/error/assert.js
@@ -564,3 +564,7 @@ export { makeAssert };
 /** @type {Assert} */
 const assert = makeAssert();
 export { assert };
+
+// Internal, to obviate polymorphic dispatch, but may become rigorously
+// consistent with @endo/error.
+export { makeError, note as annotateError, redactedDetails as X, quote as q };
diff --git a/packages/ses/src/module-instance.js b/packages/ses/src/module-instance.js
index a4bbdd8b8..38a008b88 100644
--- a/packages/ses/src/module-instance.js
+++ b/packages/ses/src/module-instance.js
@@ -24,9 +24,9 @@ import { compartmentEvaluate } from './compartment-evaluate.js';
 
 const { quote: q } = assert;
 
-export const makeThirdPartyModuleInstance = (
+export const makeVirtualModuleInstance = (
   compartmentPrivateFields,
-  staticModuleRecord,
+  moduleSource,
   compartment,
   moduleAliases,
   moduleSpecifier,
@@ -41,16 +41,16 @@ export const makeThirdPartyModuleInstance = (
 
   const notifiers = create(null);
 
-  if (staticModuleRecord.exports) {
+  if (moduleSource.exports) {
     if (
-      !isArray(staticModuleRecord.exports) ||
-      arraySome(staticModuleRecord.exports, name => typeof name !== 'string')
+      !isArray(moduleSource.exports) ||
+      arraySome(moduleSource.exports, name => typeof name !== 'string')
     ) {
       throw TypeError(
-        `SES third-party static module record "exports" property must be an array of strings for module ${moduleSpecifier}`,
+        `SES virtual module source "exports" property must be an array of strings for module ${moduleSpecifier}`,
       );
     }
-    arrayForEach(staticModuleRecord.exports, name => {
+    arrayForEach(moduleSource.exports, name => {
       let value = exportsTarget[name];
       const updaters = [];
 
@@ -96,11 +96,7 @@ export const makeThirdPartyModuleInstance = (
         localState.activated = true;
         try {
           // eslint-disable-next-line @endo/no-polymorphic-call
-          staticModuleRecord.execute(
-            exportsTarget,
-            compartment,
-            resolvedImports,
-          );
+          moduleSource.execute(exportsTarget, compartment, resolvedImports);
         } catch (err) {
           localState.errorFromExecute = err;
           throw err;
@@ -126,7 +122,7 @@ export const makeModuleInstance = (
   const {
     compartment,
     moduleSpecifier,
-    staticModuleRecord,
+    moduleSource,
     importMeta: moduleRecordMeta,
   } = moduleRecord;
   const {
@@ -137,7 +133,7 @@ export const makeModuleInstance = (
     __reexportMap__: reexportMap = {},
     __needsImportMeta__: needsImportMeta = false,
     __syncModuleFunctor__,
-  } = staticModuleRecord;
+  } = moduleSource;
 
   const compartmentFields = weakmapGet(privateFields, compartment);
 
diff --git a/packages/ses/src/module-link.js b/packages/ses/src/module-link.js
index 280ab015b..76acce16c 100644
--- a/packages/ses/src/module-link.js
+++ b/packages/ses/src/module-link.js
@@ -1,17 +1,9 @@
 /* eslint-disable no-underscore-dangle */
 
-// For brevity, in this file, as in module-load.js, the term "moduleRecord"
-// without qualification means "module compartment record".
-// This is a super-set of the "static module record", that is reusable between
-// compartments with different hooks.
-// The "module compartment record" captures the compartment and overlays the
-// module's "imports" with the more specific "resolvedImports" as inferred from
-// the particular compartment's "resolveHook".
-
 import { assert } from './error/assert.js';
 import {
   makeModuleInstance,
-  makeThirdPartyModuleInstance,
+  makeVirtualModuleInstance,
 } from './module-instance.js';
 import {
   Map,
@@ -61,56 +53,50 @@ export const link = (
   return instantiate(compartmentPrivateFields, moduleAliases, moduleRecord);
 };
 
-function isPrecompiled(staticModuleRecord) {
-  return typeof staticModuleRecord.__syncModuleProgram__ === 'string';
+function mayBePrecompiledModuleSource(moduleSource) {
+  return typeof moduleSource.__syncModuleProgram__ === 'string';
 }
 
-function validatePrecompiledStaticModuleRecord(
-  staticModuleRecord,
-  moduleSpecifier,
-) {
-  const { __fixedExportMap__, __liveExportMap__ } = staticModuleRecord;
+function validatePrecompiledModuleSource(moduleSource, moduleSpecifier) {
+  const { __fixedExportMap__, __liveExportMap__ } = moduleSource;
   isObject(__fixedExportMap__) ||
-    Fail`Property '__fixedExportMap__' of a precompiled module record must be an object, got ${q(
+    Fail`Property '__fixedExportMap__' of a precompiled module source must be an object, got ${q(
       __fixedExportMap__,
     )}, for module ${q(moduleSpecifier)}`;
   isObject(__liveExportMap__) ||
-    Fail`Property '__liveExportMap__' of a precompiled module record must be an object, got ${q(
+    Fail`Property '__liveExportMap__' of a precompiled module source must be an object, got ${q(
       __liveExportMap__,
     )}, for module ${q(moduleSpecifier)}`;
 }
 
-function isThirdParty(staticModuleRecord) {
-  return typeof staticModuleRecord.execute === 'function';
+function mayBeVirtualModuleSource(moduleSource) {
+  return typeof moduleSource.execute === 'function';
 }
 
-function validateThirdPartyStaticModuleRecord(
-  staticModuleRecord,
-  moduleSpecifier,
-) {
-  const { exports } = staticModuleRecord;
+function validateVirtualModuleSource(moduleSource, moduleSpecifier) {
+  const { exports } = moduleSource;
   isArray(exports) ||
-    Fail`Property 'exports' of a third-party static module record must be an array, got ${q(
+    Fail`Property 'exports' of a third-party module source must be an array, got ${q(
       exports,
     )}, for module ${q(moduleSpecifier)}`;
 }
 
-function validateStaticModuleRecord(staticModuleRecord, moduleSpecifier) {
-  isObject(staticModuleRecord) ||
-    Fail`Static module records must be of type object, got ${q(
-      staticModuleRecord,
+function validateModuleSource(moduleSource, moduleSpecifier) {
+  isObject(moduleSource) ||
+    Fail`Module sources must be of type object, got ${q(
+      moduleSource,
     )}, for module ${q(moduleSpecifier)}`;
-  const { imports, exports, reexports = [] } = staticModuleRecord;
+  const { imports, exports, reexports = [] } = moduleSource;
   isArray(imports) ||
-    Fail`Property 'imports' of a static module record must be an array, got ${q(
+    Fail`Property 'imports' of a module source must be an array, got ${q(
       imports,
     )}, for module ${q(moduleSpecifier)}`;
   isArray(exports) ||
-    Fail`Property 'exports' of a precompiled module record must be an array, got ${q(
+    Fail`Property 'exports' of a precompiled module source must be an array, got ${q(
       exports,
     )}, for module ${q(moduleSpecifier)}`;
   isArray(reexports) ||
-    Fail`Property 'reexports' of a precompiled module record must be an array if present, got ${q(
+    Fail`Property 'reexports' of a precompiled module source must be an array if present, got ${q(
       reexports,
     )}, for module ${q(moduleSpecifier)}`;
 }
@@ -120,7 +106,7 @@ export const instantiate = (
   moduleAliases,
   moduleRecord,
 ) => {
-  const { compartment, moduleSpecifier, resolvedImports, staticModuleRecord } =
+  const { compartment, moduleSpecifier, resolvedImports, moduleSource } =
     moduleRecord;
   const { instances } = weakmapGet(compartmentPrivateFields, compartment);
 
@@ -129,23 +115,23 @@ export const instantiate = (
     return mapGet(instances, moduleSpecifier);
   }
 
-  validateStaticModuleRecord(staticModuleRecord, moduleSpecifier);
+  validateModuleSource(moduleSource, moduleSpecifier);
 
   const importedInstances = new Map();
   let moduleInstance;
-  if (isPrecompiled(staticModuleRecord)) {
-    validatePrecompiledStaticModuleRecord(staticModuleRecord, moduleSpecifier);
+  if (mayBePrecompiledModuleSource(moduleSource)) {
+    validatePrecompiledModuleSource(moduleSource, moduleSpecifier);
     moduleInstance = makeModuleInstance(
       compartmentPrivateFields,
       moduleAliases,
       moduleRecord,
       importedInstances,
     );
-  } else if (isThirdParty(staticModuleRecord)) {
-    validateThirdPartyStaticModuleRecord(staticModuleRecord, moduleSpecifier);
-    moduleInstance = makeThirdPartyModuleInstance(
+  } else if (mayBeVirtualModuleSource(moduleSource)) {
+    validateVirtualModuleSource(moduleSource, moduleSpecifier);
+    moduleInstance = makeVirtualModuleInstance(
       compartmentPrivateFields,
-      staticModuleRecord,
+      moduleSource,
       compartment,
       moduleAliases,
       moduleSpecifier,
@@ -153,9 +139,7 @@ export const instantiate = (
     );
   } else {
     throw TypeError(
-      `importHook must return a static module record, got ${q(
-        staticModuleRecord,
-      )}`,
+      `importHook must provide a module source, got ${q(moduleSource)}`,
     );
   }
 
diff --git a/packages/ses/src/module-load.js b/packages/ses/src/module-load.js
index 7388fab97..5ba805f6e 100644
--- a/packages/ses/src/module-load.js
+++ b/packages/ses/src/module-load.js
@@ -1,39 +1,30 @@
-// For brevity, in this file, as in module-link.js, the term "moduleRecord"
-// without qualification means "module compartment record".
-// This is a super-set of the "static module record", that is reusable between
-// compartments with different hooks.
-// The "module compartment record" captures the compartment and overlays the
-// module's "imports" with the more specific "resolvedImports" as inferred from
-// the particular compartment's "resolveHook".
-
 import { getEnvironmentOption as getenv } from '@endo/env-options';
 import {
-  ReferenceError,
-  TypeError,
   Map,
   Set,
+  TypeError,
   arrayJoin,
   arrayMap,
   arrayPush,
   create,
   freeze,
+  generatorNext,
+  generatorThrow,
+  isObject,
   mapGet,
   mapHas,
   mapSet,
-  setAdd,
   promiseThen,
+  setAdd,
   values,
   weakmapGet,
-  generatorNext,
-  generatorThrow,
+  weakmapHas,
 } from './commons.js';
-import { assert } from './error/assert.js';
-
-const { Fail, details: X, quote: q, note: annotateError } = assert;
+import { makeError, annotateError, q, X } from './error/assert.js';
 
 const noop = () => {};
 
-async function asyncTrampoline(generatorFunc, args, errorWrapper) {
+const asyncTrampoline = async (generatorFunc, args, errorWrapper) => {
   await null;
   const iterator = generatorFunc(...args);
   let result = generatorNext(iterator);
@@ -47,9 +38,9 @@ async function asyncTrampoline(generatorFunc, args, errorWrapper) {
     }
   }
   return result.value;
-}
+};
 
-function syncTrampoline(generatorFunc, args) {
+const syncTrampoline = (generatorFunc, args) => {
   const iterator = generatorFunc(...args);
   let result = generatorNext(iterator);
   while (!result.done) {
@@ -60,7 +51,8 @@ function syncTrampoline(generatorFunc, args) {
     }
   }
   return result.value;
-}
+};
+
 // `makeAlias` constructs compartment specifier tuples for the `aliases`
 // private field of compartments.
 // These aliases allow a compartment to alias an internal module specifier to a
@@ -84,12 +76,12 @@ const resolveAll = (imports, resolveHook, fullReferrerSpecifier) => {
   return freeze(resolvedImports);
 };
 
-const loadRecord = (
+const loadModuleSource = (
   compartmentPrivateFields,
   moduleAliases,
   compartment,
   moduleSpecifier,
-  staticModuleRecord,
+  moduleSource,
   enqueueJob,
   selectImplementation,
   moduleLoads,
@@ -102,13 +94,13 @@ const loadRecord = (
 
   // resolve all imports relative to this referrer module.
   const resolvedImports = resolveAll(
-    staticModuleRecord.imports,
+    moduleSource.imports,
     resolveHook,
     moduleSpecifier,
   );
   const moduleRecord = freeze({
     compartment,
-    staticModuleRecord,
+    moduleSource,
     moduleSpecifier,
     resolvedImports,
     importMeta,
@@ -146,83 +138,74 @@ function* loadWithoutErrorAnnotation(
   const { importHook, importNowHook, moduleMap, moduleMapHook, moduleRecords } =
     weakmapGet(compartmentPrivateFields, compartment);
 
+  if (mapHas(moduleRecords, moduleSpecifier)) {
+    return mapGet(moduleRecords, moduleSpecifier);
+  }
+
   // Follow moduleMap, or moduleMapHook if present.
-  let aliasNamespace = moduleMap[moduleSpecifier];
-  if (aliasNamespace === undefined && moduleMapHook !== undefined) {
-    aliasNamespace = moduleMapHook(moduleSpecifier);
+  let moduleDescriptor = moduleMap[moduleSpecifier];
+  if (moduleDescriptor === undefined && moduleMapHook !== undefined) {
+    moduleDescriptor = moduleMapHook(moduleSpecifier);
   }
-  if (typeof aliasNamespace === 'string') {
+  if (moduleDescriptor === undefined) {
+    const moduleHook = selectImplementation(importHook, importNowHook);
+    if (moduleHook === undefined) {
+      const moduleHookName = selectImplementation(
+        'importHook',
+        'importNowHook',
+      );
+      throw makeError(
+        X`${moduleHookName} needed to load module ${q(
+          moduleSpecifier,
+        )} in compartment ${q(compartment.name)}`,
+      );
+    }
+    moduleDescriptor = moduleHook(moduleSpecifier);
+    // Uninitialized module namespaces throw if we attempt to coerce them into
+    // promises.
+    if (!weakmapHas(moduleAliases, moduleDescriptor)) {
+      moduleDescriptor = yield moduleDescriptor;
+    }
+  }
+
+  if (typeof moduleDescriptor === 'string') {
     // eslint-disable-next-line @endo/no-polymorphic-call
-    assert.fail(
+    throw makeError(
       X`Cannot map module ${q(moduleSpecifier)} to ${q(
-        aliasNamespace,
+        moduleDescriptor,
       )} in parent compartment, not yet implemented`,
       TypeError,
     );
-  } else if (aliasNamespace !== undefined) {
-    const alias = weakmapGet(moduleAliases, aliasNamespace);
-    if (alias === undefined) {
-      // eslint-disable-next-line @endo/no-polymorphic-call
-      assert.fail(
-        X`Cannot map module ${q(
-          moduleSpecifier,
-        )} because the value is not a module exports namespace, or is from another realm`,
-        ReferenceError,
-      );
+  } else if (isObject(moduleDescriptor)) {
+    // In this shim (and not in XS, and not in the standard we imagine), we
+    // allow a module namespace object to stand in for a module descriptor that
+    // describes its original {compartment, specifier} so that it can be used
+    // to create a link.
+    const aliasDescriptor = weakmapGet(moduleAliases, moduleDescriptor);
+    if (aliasDescriptor !== undefined) {
+      moduleDescriptor = aliasDescriptor;
     }
-    // Behold: recursion.
-    // eslint-disable-next-line no-use-before-define
-    const aliasRecord = yield memoizedLoadWithErrorAnnotation(
-      compartmentPrivateFields,
-      moduleAliases,
-      alias.compartment,
-      alias.specifier,
-      enqueueJob,
-      selectImplementation,
-      moduleLoads,
-    );
-    mapSet(moduleRecords, moduleSpecifier, aliasRecord);
-    return aliasRecord;
-  }
-
-  if (mapHas(moduleRecords, moduleSpecifier)) {
-    return mapGet(moduleRecords, moduleSpecifier);
-  }
-
-  const staticModuleRecord = yield selectImplementation(
-    importHook,
-    importNowHook,
-  )(moduleSpecifier);
-
-  if (staticModuleRecord === null || typeof staticModuleRecord !== 'object') {
-    Fail`importHook must return a promise for an object, for module ${q(
-      moduleSpecifier,
-    )} in compartment ${q(compartment.name)}`;
-  }
 
-  // check if record is a RedirectStaticModuleInterface
-  if (staticModuleRecord.specifier !== undefined) {
-    // check if this redirect with an explicit record
-    if (staticModuleRecord.record !== undefined) {
-      // ensure expected record shape
-      if (staticModuleRecord.compartment !== undefined) {
-        throw TypeError(
-          'Cannot redirect to an explicit record with a specified compartment',
-        );
-      }
+    // A (legacy) module descriptor for when we find the module source (record)
+    // but at a different specifier than requested.
+    // Providing this {specifier, record} descriptor serves as an ergonomic
+    // short-hand for stashing the record, returning a {compartment, specifier}
+    // reference, bouncing the module hook, then producing the source (record)
+    // when module hook receives the response specifier.
+    if (moduleDescriptor.record !== undefined) {
       const {
         compartment: aliasCompartment = compartment,
         specifier: aliasSpecifier = moduleSpecifier,
-        record: aliasModuleRecord,
+        record: moduleSource,
         importMeta,
-      } = staticModuleRecord;
+      } = moduleDescriptor;
 
-      const aliasRecord = loadRecord(
+      const aliasRecord = loadModuleSource(
         compartmentPrivateFields,
         moduleAliases,
         aliasCompartment,
         aliasSpecifier,
-        aliasModuleRecord,
+        moduleSource,
         enqueueJob,
         selectImplementation,
         moduleLoads,
@@ -232,12 +215,19 @@ function* loadWithoutErrorAnnotation(
       return aliasRecord;
     }
 
-    // check if this redirect with an explicit compartment
-    if (staticModuleRecord.compartment !== undefined) {
-      // ensure expected record shape
-      if (staticModuleRecord.importMeta !== undefined) {
-        throw TypeError(
-          'Cannot redirect to an implicit record with a specified importMeta',
+    // A (legacy) module descriptor that describes a link to a module instance
+    // in a specified compartment.
+    if (
+      moduleDescriptor.compartment !== undefined &&
+      moduleDescriptor.specifier !== undefined
+    ) {
+      if (
+        !isObject(moduleDescriptor.compartment) ||
+        !weakmapHas(compartmentPrivateFields, moduleDescriptor.compartment) ||
+        typeof moduleDescriptor.specifier !== 'string'
+      ) {
+        throw makeError(
+          X`Invalid compartment in module descriptor for specifier ${q(moduleSpecifier)} in compartment ${q(compartment.name)}`,
         );
       }
       // Behold: recursion.
@@ -245,8 +235,8 @@ function* loadWithoutErrorAnnotation(
       const aliasRecord = yield memoizedLoadWithErrorAnnotation(
         compartmentPrivateFields,
         moduleAliases,
-        staticModuleRecord.compartment,
-        staticModuleRecord.specifier,
+        moduleDescriptor.compartment,
+        moduleDescriptor.specifier,
         enqueueJob,
         selectImplementation,
         moduleLoads,
@@ -255,19 +245,26 @@ function* loadWithoutErrorAnnotation(
       return aliasRecord;
     }
 
-    throw TypeError('Unnexpected RedirectStaticModuleInterface record shape');
+    // A (legacy) behavior: If we do not recognize the module descriptor as a
+    // module descriptor, we assume that it is a module source (record):
+    const moduleSource = moduleDescriptor;
+    return loadModuleSource(
+      compartmentPrivateFields,
+      moduleAliases,
+      compartment,
+      moduleSpecifier,
+      moduleSource,
+      enqueueJob,
+      selectImplementation,
+      moduleLoads,
+    );
+  } else {
+    throw makeError(
+      X`module descriptor must be a string or object for specifier ${q(
+        moduleSpecifier,
+      )} in compartment ${q(compartment.name)}`,
+    );
   }
-
-  return loadRecord(
-    compartmentPrivateFields,
-    moduleAliases,
-    compartment,
-    moduleSpecifier,
-    staticModuleRecord,
-    enqueueJob,
-    selectImplementation,
-    moduleLoads,
-  );
 }
 
 const memoizedLoadWithErrorAnnotation = (
@@ -323,7 +320,7 @@ const memoizedLoadWithErrorAnnotation = (
   return moduleLoading;
 };
 
-function asyncJobQueue() {
+const asyncJobQueue = () => {
   /** @type {Set<Promise<undefined>>} */
   const pendingJobs = new Set();
   /** @type {Array<Error>} */
@@ -358,14 +355,14 @@ function asyncJobQueue() {
     return errors;
   };
   return { enqueueJob, drainQueue };
-}
+};
 
 /**
  * @param {object} options
  * @param {Array<Error>} options.errors
  * @param {string} options.errorPrefix
  */
-function throwAggregateError({ errors, errorPrefix }) {
+const throwAggregateError = ({ errors, errorPrefix }) => {
   // Throw an aggregate error if there were any errors.
   if (errors.length > 0) {
     const verbose =
@@ -377,13 +374,13 @@ function throwAggregateError({ errors, errorPrefix }) {
       )}`,
     );
   }
-}
+};
 
 const preferSync = (_asyncImpl, syncImpl) => syncImpl;
 const preferAsync = (asyncImpl, _syncImpl) => asyncImpl;
 
 /*
- * `load` asynchronously gathers the `StaticModuleRecord`s for a module and its
+ * `load` asynchronously gathers the module records for a module and its
  * transitive dependencies.
  * The module records refer to each other by a reference to the dependency's
  * compartment and the specifier of the module within its own compartment.
@@ -427,8 +424,8 @@ export const load = async (
 };
 
 /*
- * `loadNow` synchronously gathers the `StaticModuleRecord`s for a module and its
- * transitive dependencies.
+ * `loadNow` synchronously gathers the module records for a specified module
+ * and its transitive dependencies.
  * The module records refer to each other by a reference to the dependency's
  * compartment and the specifier of the module within its own compartment.
  * This graph is then ready to be synchronously linked and executed.
diff --git a/packages/ses/types.d.ts b/packages/ses/types.d.ts
index 4423d23b4..d95ae4c7b 100644
--- a/packages/ses/types.d.ts
+++ b/packages/ses/types.d.ts
@@ -43,10 +43,12 @@ export type RepairIntrinsics = (options?: LockdownOptions) => void;
 export type HardenIntrinsics = () => void;
 export type Lockdown = (options?: LockdownOptions) => void;
 
+export type ModuleExportsNamespace = Record<string, any>;
+
 export type __LiveExportMap__ = Record<string, [string, boolean]>;
 export type __FixedExportMap__ = Record<string, [string]>;
 
-export interface PrecompiledStaticModuleInterface {
+export interface PrecompiledModuleSource {
   imports: Array<string>;
   exports: Array<string>;
   reexports: Array<string>;
@@ -55,7 +57,7 @@ export interface PrecompiledStaticModuleInterface {
   __fixedExportMap__: __FixedExportMap__;
 }
 
-export interface ThirdPartyStaticModuleInterface {
+export interface VirtualModuleSource {
   imports: Array<string>;
   exports: Array<string>;
   /**
@@ -68,34 +70,51 @@ export interface ThirdPartyStaticModuleInterface {
   ): void;
 }
 
-export type FinalStaticModuleType =
-  | PrecompiledStaticModuleInterface
-  | ThirdPartyStaticModuleInterface;
+export type ModuleSource = PrecompiledModuleSource | VirtualModuleSource;
 
-export interface RedirectStaticModuleInterface {
+export interface RedirectModuleDescriptor {
   specifier: string;
-  record?: FinalStaticModuleType;
+  record?: ModuleSource;
   importMeta?: any;
   compartment?: Compartment;
 }
 
-export type StaticModuleType =
-  | RedirectStaticModuleInterface
-  | FinalStaticModuleType;
+export interface ReferenceModuleDescriptor {
+  specifier: string;
+  record?: ModuleSource;
+  importMeta?: any;
+  compartment?: Compartment;
+}
 
-export type ModuleExportsNamespace = Record<string, any>;
+export type ModuleDescriptor =
+  // These descriptor shapes are needed for XS parity:
+  // | SourceModuleDescriptor
+  // | NamespaceModuleDescriptor
+  // To be deprecated:
+  | RedirectModuleDescriptor
+  | ReferenceModuleDescriptor
+  | ModuleExportsNamespace
+  | VirtualModuleSource
+  | PrecompiledModuleSource;
+
+// Deprecated type aliases:
+export type PrecompiledStaticModuleInterface = PrecompiledModuleSource;
+export type ThirdPartyStaticModuleInterface = VirtualModuleSource;
+export type RedirectStaticModuleInterface = RedirectModuleDescriptor;
+export type FinalStaticModuleType = ModuleSource;
+export type StaticModuleType = RedirectStaticModuleInterface | ModuleSource;
 
 export type Transform = (source: string) => string;
 export type ResolveHook = (
   importSpecifier: string,
   referrerSpecifier: string,
 ) => string;
-export type ModuleMap = Record<string, string | ModuleExportsNamespace>;
+export type ModuleMap = Record<string, string | ModuleDescriptor>;
 export type ModuleMapHook = (
   moduleSpecifier: string,
-) => string | ModuleExportsNamespace | void;
-export type ImportHook = (moduleSpecifier: string) => Promise<StaticModuleType>;
-export type ImportNowHook = (moduleSpecifier: string) => StaticModuleType;
+) => ModuleDescriptor | undefined;
+export type ImportHook = (moduleSpecifier: string) => Promise<ModuleDescriptor>;
+export type ImportNowHook = (moduleSpecifier: string) => ModuleDescriptor;
 
 export interface CompartmentOptions {
   name?: string;


Test patch:
diff --git a/packages/ses/test/import.test.js b/packages/ses/test/import.test.js
index 29a78e78a..5180f2d1d 100644
--- a/packages/ses/test/import.test.js
+++ b/packages/ses/test/import.test.js
@@ -4,10 +4,271 @@
 /* eslint max-lines: 0 */
 
 import test from 'ava';
+import { StaticModuleRecord } from '@endo/static-module-record';
 import '../index.js';
 import { resolveNode, makeNodeImporter } from './node.js';
 import { makeImporter, makeStaticRetriever } from './import-commons.js';
 
+test('module map primed with module source', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {
+      './index.js': new StaticModuleRecord('export default 42'),
+    },
+    // options:
+    {
+      resolveHook: specifier => specifier,
+    },
+  );
+  const { namespace: index } = await compartment.import('./index.js');
+  t.is(index.default, 42);
+});
+
+test('module map primed with module record descriptor', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {
+      './index.js': {
+        record: new StaticModuleRecord('export default 42'),
+      },
+    },
+    // options:
+    {
+      resolveHook: specifier => specifier,
+    },
+  );
+  const { namespace: index } = await compartment.import('./index.js');
+  t.is(index.default, 42);
+});
+
+test('module map primed with virtual module source', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {
+      './index.js': {
+        imports: [],
+        exports: ['default'],
+        execute(env) {
+          env.default = 42;
+        },
+      },
+    },
+    // options:
+    {
+      resolveHook: specifier => specifier,
+    },
+  );
+  const { namespace: index } = await compartment.import('./index.js');
+  t.is(index.default, 42);
+});
+
+test('module map primed with virtual module record descriptor', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {
+      './index.js': {
+        record: {
+          imports: [],
+          exports: ['default'],
+          execute(env) {
+            env.default = 42;
+          },
+        },
+      },
+    },
+    // options:
+    {
+      resolveHook: specifier => specifier,
+    },
+  );
+  const { namespace: index } = await compartment.import('./index.js');
+  t.is(index.default, 42);
+});
+
+test('module map hook returns module source', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {},
+    // options:
+    {
+      resolveHook: specifier => specifier,
+      moduleMapHook(specifier) {
+        if (specifier === './index.js') {
+          return new StaticModuleRecord('export default 42');
+        }
+        return undefined;
+      },
+    },
+  );
+  const { namespace: index } = await compartment.import('./index.js');
+  t.is(index.default, 42);
+});
+
+test('importHook returns module source', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {},
+    // options:
+    {
+      resolveHook: specifier => specifier,
+      importHook(specifier) {
+        if (specifier === './index.js') {
+          return new StaticModuleRecord('export default 42');
+        }
+        return undefined;
+      },
+    },
+  );
+  const { namespace: index } = await compartment.import('./index.js');
+  t.is(index.default, 42);
+});
+
+// This case requires the module loader to take care not to attempt to
+// coerce the module namespace object to a promise.
+test('importHook returns module namespace', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {},
+    // options:
+    {
+      resolveHook: specifier => specifier,
+      importHook(specifier) {
+        if (specifier === '.') {
+          return compartment.module('./index.js');
+        }
+        if (specifier === './index.js') {
+          return new StaticModuleRecord('export default 42');
+        }
+        return undefined;
+      },
+    },
+  );
+  // Unlike import, importNow does not box the namespace.
+  const { namespace: index } = await compartment.import('.');
+  t.is(index.default, 42);
+});
+
+test('importNowHook returns namespace', t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {},
+    // options:
+    {
+      resolveHook: specifier => specifier,
+      importNowHook(specifier) {
+        if (specifier === '.') {
+          return compartment.module('./index.js');
+        }
+        if (specifier === './index.js') {
+          return new StaticModuleRecord('export default 42');
+        }
+        return undefined;
+      },
+    },
+  );
+  // Unlike import, importNow does not box the namespace.
+  const index = compartment.importNow('.');
+  t.is(index.default, 42);
+});
+
+test('importHook returns compartment and specifier module descriptor', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {},
+    // options:
+    {
+      resolveHook: specifier => specifier,
+      importHook(specifier) {
+        if (specifier === '.') {
+          return { compartment, specifier: './index.js' };
+        }
+        if (specifier === './index.js') {
+          return new StaticModuleRecord('export default 42');
+        }
+        return undefined;
+      },
+    },
+  );
+  const { namespace: index } = await compartment.import('.');
+  t.is(index.default, 42);
+});
+
+test('importHook returns record and specifier module descriptor', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {},
+    // options:
+    {
+      resolveHook: specifier => specifier,
+      importHook(specifier) {
+        if (specifier === '.') {
+          return {
+            record: new StaticModuleRecord('export default 42'),
+            specifier: './index.js',
+          };
+        }
+        return undefined;
+      },
+    },
+  );
+  const { namespace: index } = await compartment.import('.');
+  t.is(index.default, 42);
+});
+
+test('importHook returns record and specifier module descriptor and import specifiers resolve from response specifier', async t => {
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // modules:
+    {
+      './src/peer.js': new StaticModuleRecord('export const number = 42'),
+    },
+    // options:
+    {
+      resolveHook(importSpecifier, moduleSpecifier) {
+        if (
+          moduleSpecifier === './src/index.js' &&
+          importSpecifier === './peer.js'
+        ) {
+          return './src/peer.js';
+        }
+        return importSpecifier;
+      },
+      importHook(specifier) {
+        if (specifier === '.') {
+          return {
+            record: new StaticModuleRecord('export * from "./peer.js"'),
+            specifier: './src/index.js',
+          };
+        }
+        return undefined;
+      },
+    },
+  );
+  const { namespace: index } = await compartment.import('.');
+  t.is(index.number, 42);
+});
+
 // This test demonstrates a system of modules in a single Compartment
 // that uses fully qualified URLs as module specifiers and module locations,
 // not distinguishing one from the other.
@@ -421,7 +682,7 @@ test('mutual dependency between compartments', async t => {
   await compartment.import('./main.js');
 });
 
-test('module alias', async t => {
+test('import redirect shorthand', async t => {
   // The following use of Math.random() is informative but does not
   // affect the outcome of the test, just makes the nature of the error
   // obvious in test output.
@@ -617,6 +878,7 @@ test('importMetaHook', async t => {
   } = await compartment.import('./index.js');
   t.is(metaurl, 'https://example.com/index.js');
 });
+
 test('importMetaHook and meta from record', async t => {
   t.plan(1);