From 81eb95630727664dacfeb8b1444356a41e286fac Mon Sep 17 00:00:00 2001
From: Kris Kowal <kris@agoric.com>
Date: Thu, 13 Jun 2024 20:27:38 -0700
Subject: [PATCH 1/4] feat(ses): Capture Compartment endowments and modules
 options

---
 packages/ses/src/compartment.js | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js
index 8728a0b11f..0af5dc28ca 100644
--- a/packages/ses/src/compartment.js
+++ b/packages/ses/src/compartment.js
@@ -172,7 +172,11 @@ export const makeCompartmentConstructor = (
   markVirtualizedNativeFunction,
   parentCompartment = undefined,
 ) => {
-  function Compartment(endowments = {}, moduleMap = {}, options = {}) {
+  function Compartment(
+    endowmentsOption = {},
+    moduleMapOption = {},
+    options = {},
+  ) {
     if (new.target === undefined) {
       throw TypeError(
         "Class constructor Compartment cannot be invoked without 'new'",
@@ -191,6 +195,8 @@ export const makeCompartmentConstructor = (
       importMetaHook,
     } = options;
     const globalTransforms = [...transforms, ...__shimTransforms__];
+    const endowments = { __proto__: null, ...endowmentsOption };
+    const moduleMap = { __proto__: null, ...moduleMapOption };
 
     // Map<FullSpecifier, ModuleCompartmentRecord>
     const moduleRecords = new Map();

From d996bad715b54f77be1a2b3de74e85dd55fce246 Mon Sep 17 00:00:00 2001
From: Kris Kowal <kris@agoric.com>
Date: Tue, 2 Jul 2024 16:43:31 -0700
Subject: [PATCH 2/4] feat(ses): Option __noNamespaceBox__

---
 packages/ses/src/compartment.js | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js
index 0af5dc28ca..c309778a2f 100644
--- a/packages/ses/src/compartment.js
+++ b/packages/ses/src/compartment.js
@@ -104,6 +104,8 @@ export const CompartmentPrototype = {
   },
 
   async import(specifier) {
+    const { noNamespaceBox } = weakmapGet(privateFields, this);
+
     if (typeof specifier !== 'string') {
       throw TypeError('first argument of import() must be a string');
     }
@@ -117,6 +119,11 @@ export const CompartmentPrototype = {
           /** @type {Compartment} */ (this),
           specifier,
         );
+        if (noNamespaceBox) {
+          return namespace;
+        }
+        // Legacy behavior: box the namespace object so that thenable modules
+        // do not get coerced into a promise accidentally.
         return { namespace };
       },
     );
@@ -193,6 +200,7 @@ export const makeCompartmentConstructor = (
       importNowHook,
       moduleMapHook,
       importMetaHook,
+      noNamespaceBox = false,
     } = options;
     const globalTransforms = [...transforms, ...__shimTransforms__];
     const endowments = { __proto__: null, ...endowmentsOption };
@@ -255,6 +263,7 @@ export const makeCompartmentConstructor = (
       deferredExports,
       instances,
       parentCompartment,
+      noNamespaceBox,
     });
   }
 

From 078ad5a6732389cc6cf6c3fda176ce2019b43724 Mon Sep 17 00:00:00 2001
From: Kris Kowal <kris@agoric.com>
Date: Wed, 24 Jul 2024 17:08:58 -0700
Subject: [PATCH 3/4] test(ses): Test __noNamespaceBox__ option

---
 packages/ses/src/compartment.js               |   2 +-
 .../ses/test/compartment-transforms.test.js   |  17 +-
 packages/ses/test/import-cjs.test.js          | 104 +--
 packages/ses/test/import-gauntlet.test.js     |  18 +-
 packages/ses/test/import-hook.test.js         |  93 ++-
 packages/ses/test/import-legacy.test.js       | 651 ++++++++++++++++++
 packages/ses/test/import-non-esm.test.js      |  76 +-
 packages/ses/test/import.test.js              | 107 ++-
 packages/ses/test/module-map-hook.test.js     |  32 +-
 packages/ses/test/module-map.test.js          |  70 +-
 10 files changed, 933 insertions(+), 237 deletions(-)
 create mode 100644 packages/ses/test/import-legacy.test.js

diff --git a/packages/ses/src/compartment.js b/packages/ses/src/compartment.js
index c309778a2f..270c8203cb 100644
--- a/packages/ses/src/compartment.js
+++ b/packages/ses/src/compartment.js
@@ -200,7 +200,7 @@ export const makeCompartmentConstructor = (
       importNowHook,
       moduleMapHook,
       importMetaHook,
-      noNamespaceBox = false,
+      __noNamespaceBox__: noNamespaceBox = false,
     } = options;
     const globalTransforms = [...transforms, ...__shimTransforms__];
     const endowments = { __proto__: null, ...endowmentsOption };
diff --git a/packages/ses/test/compartment-transforms.test.js b/packages/ses/test/compartment-transforms.test.js
index 04115b5158..2e18ff7081 100644
--- a/packages/ses/test/compartment-transforms.test.js
+++ b/packages/ses/test/compartment-transforms.test.js
@@ -52,9 +52,13 @@ test('transforms do not apply to imported modules', async t => {
   const resolveHook = () => '';
   const importHook = () =>
     new ModuleSource('export default "Farewell, World!";');
-  const c = new Compartment({}, {}, { transforms, resolveHook, importHook });
+  const c = new Compartment(
+    {},
+    {},
+    { transforms, resolveHook, importHook, __noNamespaceBox__: true },
+  );
 
-  const { namespace } = await c.import('any-string-here');
+  const namespace = await c.import('any-string-here');
   const { default: greeting } = namespace;
 
   t.is(greeting, 'Farewell, World!');
@@ -82,10 +86,15 @@ test('__shimTransforms__ do apply to imported modules', async t => {
   const c = new Compartment(
     {},
     {},
-    { __shimTransforms__: transforms, resolveHook, importHook },
+    {
+      __shimTransforms__: transforms,
+      __noNamespaceBox__: true,
+      resolveHook,
+      importHook,
+    },
   );
 
-  const { namespace } = await c.import('any-string-here');
+  const namespace = await c.import('any-string-here');
   const { default: greeting } = namespace;
 
   t.is(greeting, 'Hello, World!');
diff --git a/packages/ses/test/import-cjs.test.js b/packages/ses/test/import-cjs.test.js
index efea862a4e..b80b47e92f 100644
--- a/packages/ses/test/import-cjs.test.js
+++ b/packages/ses/test/import-cjs.test.js
@@ -86,11 +86,13 @@ test('import a CommonJS module with exports assignment', async t => {
     );
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
   const module = compartment.module('.');
-  const {
-    namespace: { meaning },
-  } = await compartment.import('.');
+  const { meaning } = await compartment.import('.');
 
   t.is(meaning, 42, 'exports seen');
   t.is(module.meaning, 42, 'exports seen through deferred proxy');
@@ -109,11 +111,13 @@ test('import a CommonJS module with exports replacement', async t => {
     );
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
   const module = compartment.module('.');
-  const {
-    namespace: { default: meaning },
-  } = await compartment.import('.');
+  const { default: meaning } = await compartment.import('.');
 
   t.is(meaning, 42, 'exports seen');
   t.is(module.default, 42, 'exports seen through deferred proxy');
@@ -144,10 +148,12 @@ test('CommonJS module imports CommonJS module by name', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -178,8 +184,12 @@ test('CommonJS module imports CommonJS module as default', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const { namespace } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const namespace = await compartment.import('./odd');
   const { default: odd } = namespace;
 
   t.is(odd(1), true);
@@ -211,10 +221,12 @@ test('ESM imports CommonJS module as default', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -245,10 +257,12 @@ test('ESM imports CommonJS module as star', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -281,10 +295,12 @@ test('ESM imports CommonJS module with replaced exports as star', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -315,10 +331,12 @@ test('ESM imports CommonJS module by name', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -349,10 +367,12 @@ test('CommonJS module imports ESM as default', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -385,10 +405,12 @@ test('CommonJS module imports ESM by name', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -440,7 +462,11 @@ test('cross import ESM and CommonJS modules', async t => {
     throw Error(`Cannot load module for specifier ${specifier}`);
   };
 
-  const compartment = new Compartment({ t }, {}, { resolveHook, importHook });
+  const compartment = new Compartment(
+    { t },
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
   await compartment.import('./src/main.js');
 });
 
diff --git a/packages/ses/test/import-gauntlet.test.js b/packages/ses/test/import-gauntlet.test.js
index 0135fd546e..14bdc61dad 100644
--- a/packages/ses/test/import-gauntlet.test.js
+++ b/packages/ses/test/import-gauntlet.test.js
@@ -49,10 +49,11 @@ test('import all from module', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.default.a, 10);
   t.is(namespace.default.b, 20);
@@ -78,10 +79,11 @@ test('import named exports from me', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.default.fizz, 10);
   t.is(namespace.default.buzz, 20);
@@ -106,10 +108,11 @@ test('import color from module', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.color, 'blue');
 });
@@ -132,10 +135,11 @@ test('import and reexport', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.qux, 42);
 });
@@ -159,10 +163,11 @@ test('import and export all', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.alpha, 0);
   t.is(namespace.omega, 23);
@@ -189,10 +194,11 @@ test('live binding', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.default, 'Hello, World!');
 });
diff --git a/packages/ses/test/import-hook.test.js b/packages/ses/test/import-hook.test.js
index 6a3cb4c8cf..12b5870008 100644
--- a/packages/ses/test/import-hook.test.js
+++ b/packages/ses/test/import-hook.test.js
@@ -23,9 +23,10 @@ test('import hook returns module source descriptor with precompiled module sourc
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -52,9 +53,10 @@ test('import hook returns module source descriptor with virtual module source',
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -100,9 +102,10 @@ test('import hook returns parent compartment module source descriptor with strin
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -182,9 +185,10 @@ test('import hook returns parent compartment module source reference with differ
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -205,12 +209,11 @@ test('import hook returns module source descriptor for parent compartment with s
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: parentObject },
-  } = await parent.import('./object.js');
+  const { default: parentObject } = await parent.import('./object.js');
   t.is(parentObject.meaning, 42);
 
   const compartment = new parent.globalThis.Compartment(
@@ -230,12 +233,11 @@ test('import hook returns module source descriptor for parent compartment with s
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: childObject },
-  } = await compartment.import('./index.js');
+  const { default: childObject } = await compartment.import('./index.js');
   t.is(childObject.meaning, 42);
   // Separate instances
   t.not(childObject, parentObject);
@@ -258,12 +260,11 @@ test('import hook returns parent compartment module namespace descriptor', async
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: parentObject },
-  } = await parent.import('./object.js');
+  const { default: parentObject } = await parent.import('./object.js');
   t.is(parentObject.meaning, 42);
 
   const compartment = new parent.globalThis.Compartment(
@@ -283,12 +284,11 @@ test('import hook returns parent compartment module namespace descriptor', async
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: childObject },
-  } = await compartment.import('./index.js');
+  const { default: childObject } = await compartment.import('./index.js');
   t.is(childObject.meaning, 42);
   // Same instances
   t.is(childObject, parentObject);
@@ -311,12 +311,11 @@ test('import hook returns module source descriptor with string reference to pare
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment1.import('./object.js');
+  const { default: object1 } = await compartment1.import('./object.js');
   t.is(object1.meaning, 42);
 
   const compartment2 = new Compartment(
@@ -336,12 +335,11 @@ test('import hook returns module source descriptor with string reference to pare
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object2 },
-  } = await compartment2.import('./index.js');
+  const { default: object2 } = await compartment2.import('./index.js');
   t.is(object2.meaning, 42);
   // Separate instances
   t.not(object1, object2);
@@ -364,12 +362,11 @@ test('import hook returns other compartment module namespace descriptor', async
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment1.import('./object.js');
+  const { default: object1 } = await compartment1.import('./object.js');
   t.is(object1.meaning, 42);
 
   const compartment2 = new Compartment(
@@ -389,12 +386,11 @@ test('import hook returns other compartment module namespace descriptor', async
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object2 },
-  } = await compartment2.import('./index.js');
+  const { default: object2 } = await compartment2.import('./index.js');
   t.is(object2.meaning, 42);
   // Same instances
   t.is(object1, object2);
@@ -413,9 +409,10 @@ test('import hook returns module namespace descriptor and namespace object', asy
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: namespace1 } = await compartment1.import('a');
+  const namespace1 = await compartment1.import('a');
   const compartment2 = new Compartment(
     {},
     {},
@@ -426,9 +423,10 @@ test('import hook returns module namespace descriptor and namespace object', asy
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: namespace2 } = await compartment2.import('z');
+  const namespace2 = await compartment2.import('z');
   t.is(namespace2.default, 42);
   t.is(namespace1, namespace2);
 });
@@ -444,9 +442,10 @@ test('import hook returns module namespace descriptor and non-namespace object',
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace } = await compartment.import('1');
+  const namespace = await compartment.import('1');
   t.is(namespace.meaning, 42);
 });
 
@@ -471,16 +470,13 @@ test('import hook returns module source descriptor for specifier in own compartm
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment.import('./object.js');
+  const { default: object1 } = await compartment.import('./object.js');
   t.is(object1.meaning, 42);
-  const {
-    namespace: { default: object2 },
-  } = await compartment.import('./index.js');
+  const { default: object2 } = await compartment.import('./index.js');
   t.is(object2.meaning, 42);
   // Separate instances
   t.not(object1, object2);
@@ -508,16 +504,13 @@ test('import hook returns module source descriptor for specifier in own compartm
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment.import('./object.js');
+  const { default: object1 } = await compartment.import('./object.js');
   t.is(object1.meaning, 42);
-  const {
-    namespace: { default: object2 },
-  } = await compartment.import('./index.js');
+  const { default: object2 } = await compartment.import('./index.js');
   t.is(object2.meaning, 42);
   // Fails to obtain separate instance due to specifier collison.
   t.is(object1, object2);
@@ -544,16 +537,13 @@ test('import hook returns module namespace descriptor for specifier in own compa
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment.import('./object.js');
+  const { default: object1 } = await compartment.import('./object.js');
   t.is(object1.meaning, 42);
-  const {
-    namespace: { default: object2 },
-  } = await compartment.import('./index.js');
+  const { default: object2 } = await compartment.import('./index.js');
   t.is(object2.meaning, 42);
   // Same instances
   t.is(object1, object2);
@@ -580,11 +570,10 @@ test('module map hook precedes import hook', async t => {
       importHook() {
         throw new Error('not reached');
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: meaning },
-  } = await compartment.import('./index.js');
+  const { default: meaning } = await compartment.import('./index.js');
   t.is(meaning, 42);
 });
diff --git a/packages/ses/test/import-legacy.test.js b/packages/ses/test/import-legacy.test.js
new file mode 100644
index 0000000000..fa08cb231f
--- /dev/null
+++ b/packages/ses/test/import-legacy.test.js
@@ -0,0 +1,651 @@
+// These tests exercise the Compartment import interface and linkage
+// between compartments, and Compartment endowments.
+
+/* eslint max-lines: 0 */
+
+import test from 'ava';
+import '../index.js';
+import { resolveNode, makeNodeImporter } from './node.js';
+import { makeImporter, makeStaticRetriever } from './import-commons.js';
+
+// 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.
+test('import within one compartment, web resolution', async t => {
+  t.plan(1);
+
+  const retrieve = makeStaticRetriever({
+    'https://example.com/packages/example/half.js': `
+      export default 21;
+    `,
+    'https://example.com/packages/example/': `
+      import half from 'half.js';
+      export const meaning = double(half);
+    `,
+  });
+  const locate = moduleSpecifier => moduleSpecifier;
+  const resolveHook = (spec, referrer) => new URL(spec, referrer).toString();
+  const importHook = makeImporter(locate, retrieve);
+
+  const compartment = new Compartment(
+    // endowments:
+    {
+      double: n => n * 2,
+    },
+    // module map:
+    {},
+    // options:
+    {
+      resolveHook,
+      importHook,
+    },
+  );
+
+  const { namespace } = await compartment.import(
+    'https://example.com/packages/example/',
+  );
+
+  t.is(namespace.meaning, 42, 'dynamically imports the meaning');
+});
+
+// This case demonstrates the same arrangement except that the Compartment uses
+// Node.js module specifier resolution.
+test('import within one compartment, node resolution', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/packages/example/half.js': `
+      export default 21;
+    `,
+    'https://example.com/packages/example/main.js': `
+      import half from './half.js';
+      export const meaning = double(half);
+    `,
+  });
+
+  const compartment = new Compartment(
+    // endowments:
+    {
+      double: n => n * 2,
+    },
+    // module map:
+    {},
+    // options:
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/example'),
+    },
+  );
+
+  const { namespace } = await compartment.import('./main.js');
+
+  t.is(namespace.meaning, 42, 'dynamically imports the meaning');
+});
+
+// This demonstrates a pair of linked Node.js compartments.
+test('two compartments, three modules, one endowment', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/packages/example/half.js': `
+      if (typeof double !== 'undefined') {
+        throw Error('Unexpected leakage of double(n) endowment: ' + typeof double);
+      }
+      export default 21;
+    `,
+    'https://example.com/packages/example/main.js': `
+      import half from './half.js';
+      import double from 'double';
+      export const meaning = double(half);
+    `,
+    'https://example.com/packages/double/main.js': `
+      export default double;
+    `,
+  });
+
+  const doubleCompartment = new Compartment(
+    // endowments:
+    {
+      double: n => n * 2,
+    },
+    // module map:
+    {},
+    // options:
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/double'),
+    },
+  );
+
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // module map:
+    {
+      // Notably, this is the first case where we thread a depencency between
+      // two compartments, using the sigil of one's namespace to indicate
+      // linkage before the module has been loaded.
+      double: doubleCompartment.module('./main.js'),
+    },
+    // options:
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/example'),
+    },
+  );
+
+  const { namespace } = await compartment.import('./main.js');
+
+  t.is(namespace.meaning, 42, 'dynamically imports the meaning');
+});
+
+test('module exports namespace as an object', async t => {
+  t.plan(7);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/packages/meaning/main.js': `
+      export const meaning = 42;
+    `,
+  });
+
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // module map:
+    {},
+    // options:
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/meaning'),
+    },
+  );
+
+  const { namespace } = await compartment.import('./main.js');
+
+  t.is(
+    namespace.meaning,
+    42,
+    'exported constant must have a namespace property',
+  );
+
+  t.throws(
+    () => {
+      namespace.alternateMeaning = 10;
+    },
+    { message: /^Cannot set property/ },
+  );
+
+  // The first should not throw.
+  t.truthy(
+    Reflect.preventExtensions(namespace),
+    'extensions must be preventable',
+  );
+  // The second should agree.
+  t.truthy(
+    Reflect.preventExtensions(namespace),
+    'preventing extensions must be idempotent',
+  );
+
+  const desc = Object.getOwnPropertyDescriptor(namespace, 'meaning');
+  t.is(
+    typeof desc,
+    'object',
+    'property descriptor for defined export must be an object',
+  );
+  t.is(desc.set, undefined, 'constant export must not be writeable');
+
+  t.is(
+    Object.getPrototypeOf(namespace),
+    null,
+    'module exports namespace prototype must be null',
+  );
+});
+
+test('modules are memoized', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/packages/example/c-s-lewis.js': `
+      export const entity = {};
+    `,
+    'https://example.com/packages/example/clive-hamilton.js': `
+      import { entity } from './c-s-lewis.js';
+      export default entity;
+    `,
+    'https://example.com/packages/example/n-w-clerk.js': `
+      import { entity } from './c-s-lewis.js';
+      export default entity;
+    `,
+    'https://example.com/packages/example/main.js': `
+      import clive from './clive-hamilton.js';
+      import clerk from './n-w-clerk.js';
+      export default { clerk, clive };
+    `,
+  });
+
+  const compartment = new Compartment(
+    // endowments:
+    {},
+    // module map:
+    {},
+    // options:
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/example'),
+    },
+  );
+
+  const { namespace } = await compartment.import('./main.js');
+  const { clive, clerk } = namespace;
+
+  t.truthy(clive === clerk, 'diamond dependency must refer to the same module');
+});
+
+test('compartments with same sources do not share instances', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/packages/arm/main.js': `
+      export default {};
+    `,
+  });
+
+  const leftCompartment = new Compartment(
+    {}, // endowments
+    {}, // module map
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/arm'),
+    },
+  );
+
+  const rightCompartment = new Compartment(
+    {}, // endowments
+    {}, // module map
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/packages/arm'),
+    },
+  );
+
+  const [
+    {
+      namespace: { default: leftArm },
+    },
+    {
+      namespace: { default: rightArm },
+    },
+  ] = await Promise.all([
+    leftCompartment.import('./main.js'),
+    rightCompartment.import('./main.js'),
+  ]);
+
+  t.truthy(
+    leftArm !== rightArm,
+    'different compartments with same sources do not share instances',
+  );
+});
+
+const trimModuleSpecifierPrefix = (moduleSpecifier, prefix) => {
+  if (moduleSpecifier === prefix) {
+    return './index.js';
+  }
+  if (moduleSpecifier.startsWith(`${prefix}/`)) {
+    return `./${moduleSpecifier.slice(prefix.length + 1)}`;
+  }
+  return undefined;
+};
+
+test('module map hook', async t => {
+  t.plan(2);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/main.js': `
+      import dependency from 'dependency';
+      import utility from 'dependency/utility.js';
+
+      t.is(dependency, "dependency");
+      t.is(utility, "utility");
+    `,
+    'https://example.com/dependency/index.js': `
+      export default "dependency";
+    `,
+    'https://example.com/dependency/utility.js': `
+      export default "utility";
+    `,
+  });
+
+  const dependency = new Compartment(
+    {},
+    {},
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/dependency'),
+    },
+  );
+
+  const compartment = new Compartment(
+    { t },
+    {},
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com'),
+      moduleMapHook: moduleSpecifier => {
+        const remainder = trimModuleSpecifierPrefix(
+          moduleSpecifier,
+          'dependency',
+        );
+        if (remainder) {
+          return dependency.module(remainder);
+        }
+        return undefined;
+      },
+    },
+  );
+
+  await compartment.import('./main.js');
+});
+
+test('mutual dependency between compartments', async t => {
+  t.plan(12);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/main.js': `
+      import isEven from "even";
+      import isOdd from "odd";
+
+      for (const n of [0, 2, 4]) {
+        t.truthy(isEven(n), \`\${n} should be even\`);
+        t.truthy(!isOdd(n), \`\${n} should not be odd\`);
+      }
+      for (const n of [1, 3, 5]) {
+        t.truthy(isOdd(n), \`\${n} should be odd\`);
+        t.truthy(!isEven(n), \`\${n} should not be even\`);
+      }
+    `,
+    'https://example.com/even/index.js': `
+      import isOdd from "odd";
+      export default n => n === 0 || isOdd(n - 1);
+    `,
+    'https://example.com/odd/index.js': `
+      import isEven from "even";
+      export default n => n !== 0 && isEven(n - 1);
+    `,
+  });
+
+  const moduleMapHook = moduleSpecifier => {
+    // Mutual dependency ahead:
+    // eslint-disable-next-line no-use-before-define
+    for (const [prefix, compartment] of Object.entries({ even, odd })) {
+      const remainder = trimModuleSpecifierPrefix(moduleSpecifier, prefix);
+      if (remainder) {
+        return compartment.module(remainder);
+      }
+    }
+    return undefined;
+  };
+
+  const even = new Compartment(
+    {},
+    {},
+    {
+      name: 'https://example.com/even',
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/even'),
+      moduleMapHook,
+    },
+  );
+
+  const odd = new Compartment(
+    {},
+    {},
+    {
+      name: 'https://example.com/odd',
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/odd'),
+      moduleMapHook,
+    },
+  );
+
+  const compartment = new Compartment(
+    { t },
+    {},
+    {
+      name: 'https://example.com',
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com'),
+      moduleMapHook,
+    },
+  );
+
+  await compartment.import('./main.js');
+});
+
+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.
+  // The containing objects should be identical.
+  // The contained value should incidentally be identical.
+  // The test depends on the former.
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/main/index.js': `
+      export const unique = {n: Math.random()};
+      export const meaning = 42;
+    `,
+  });
+
+  const wrappedImportHook = makeImportHook('https://example.com');
+
+  const importHook = async specifier => {
+    await null;
+    const candidates = [specifier, `${specifier}.js`, `${specifier}/index.js`];
+    for (const candidate of candidates) {
+      // eslint-disable-next-line no-await-in-loop
+      const record = await wrappedImportHook(candidate).catch(_ => undefined);
+      // return a RedirectStaticModuleInterface with an explicit record
+      if (record !== undefined) {
+        return { record, specifier };
+      }
+    }
+    throw Error(`Cannot find module ${specifier}`);
+  };
+
+  const compartment = new Compartment(
+    {
+      Math,
+    },
+    {},
+    {
+      resolveHook: resolveNode,
+      importHook,
+    },
+  );
+
+  const { namespace } = await compartment.import('./main');
+  t.is(
+    namespace.meaning,
+    42,
+    'dynamically imports the meaning through a redirect',
+  );
+
+  // TODO The following commented test does not pass, and might not be valid.
+  // Web browsers appear to have taken the stance that they will load a static
+  // module record once per *response url* and create unique a unique module
+  // instance per *request url*.
+  //
+  // const { namespace: aliasNamespace } = await compartment.import(
+  //   './main/index.js',
+  // );
+  // t.strictEqual(
+  //   namespace.unique,
+  //   aliasNamespace.unique,
+  //   'alias modules have identical instance',
+  // );
+});
+
+test('import reflexive module alias', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/index.js': `
+      import self from 'self';
+      export default 10;
+      t.is(self, 10);
+    `,
+  });
+
+  const wrappedImportHook = makeImportHook('https://example.com');
+
+  const importHook = async specifier => {
+    await null;
+    const candidates = [specifier, `${specifier}.js`, `${specifier}/index.js`];
+    for (const candidate of candidates) {
+      // eslint-disable-next-line no-await-in-loop
+      const record = await wrappedImportHook(candidate).catch(_ => undefined);
+      if (record !== undefined) {
+        // return a RedirectStaticModuleInterface with an explicit record
+        return { record, specifier };
+      }
+    }
+    throw Error(`Cannot find module ${specifier}`);
+  };
+
+  const moduleMapHook = specifier => {
+    if (specifier === 'self') {
+      // eslint-disable-next-line no-use-before-define
+      return compartment.module('./index.js');
+    }
+    return undefined;
+  };
+
+  const compartment = new Compartment(
+    {
+      t,
+    },
+    {},
+    {
+      resolveHook: resolveNode,
+      importHook,
+      moduleMapHook,
+    },
+  );
+
+  await compartment.import('./index.js');
+});
+
+test('child compartments are modular', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/index.js': `
+      export default 42;
+    `,
+  });
+
+  const parent = new Compartment();
+  const compartment = new parent.globalThis.Compartment(
+    {}, // endowments
+    {}, // module map
+    {
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com/'),
+    },
+  );
+
+  const {
+    namespace: { default: meaning },
+  } = await compartment.import('./index.js');
+
+  t.is(meaning, 42, 'child compartments have module support');
+});
+
+test('import.meta populated from module record', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/index.js': `
+      const myloc = import.meta.url;
+      export default myloc;
+    `,
+  });
+
+  const compartment = new Compartment(
+    { t },
+    {},
+    {
+      name: 'https://example.com',
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com', {
+        meta: { url: 'https://example.com/index.js' },
+      }),
+    },
+  );
+
+  const {
+    namespace: { default: metaurl },
+  } = await compartment.import('./index.js');
+  t.is(metaurl, 'https://example.com/index.js');
+});
+
+test('importMetaHook', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/index.js': `
+      const myloc = import.meta.url;
+      export default myloc;
+    `,
+  });
+
+  const compartment = new Compartment(
+    { t },
+    {},
+    {
+      name: 'https://example.com',
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com'),
+      importMetaHook: (_moduleSpecifier, meta) => {
+        meta.url = 'https://example.com/index.js';
+      },
+    },
+  );
+
+  const {
+    namespace: { default: metaurl },
+  } = await compartment.import('./index.js');
+  t.is(metaurl, 'https://example.com/index.js');
+});
+
+test('importMetaHook and meta from record', async t => {
+  t.plan(1);
+
+  const makeImportHook = makeNodeImporter({
+    'https://example.com/index.js': `
+      const myloc = import.meta.url;
+      export default myloc;
+    `,
+  });
+
+  const compartment = new Compartment(
+    { t },
+    {},
+    {
+      name: 'https://example.com',
+      resolveHook: resolveNode,
+      importHook: makeImportHook('https://example.com', {
+        meta: { url: 'https://example.com/index.js' },
+      }),
+      importMetaHook: (_moduleSpecifier, meta) => {
+        meta.url += '?foo';
+        meta.isStillMutableHopefully = 1;
+      },
+    },
+  );
+
+  const {
+    namespace: { default: metaurl },
+  } = await compartment.import('./index.js');
+  t.is(metaurl, 'https://example.com/index.js?foo');
+});
diff --git a/packages/ses/test/import-non-esm.test.js b/packages/ses/test/import-non-esm.test.js
index 6cf0fe7d14..bb9e5211f4 100644
--- a/packages/ses/test/import-non-esm.test.js
+++ b/packages/ses/test/import-non-esm.test.js
@@ -17,11 +17,13 @@ test('import a non-ESM', async t => {
     };
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
   const module = compartment.module('.');
-  const {
-    namespace: { meaning },
-  } = await compartment.import('.');
+  const { meaning } = await compartment.import('.');
 
   t.is(meaning, 42, 'exports seen');
   t.is(module.meaning, 42, 'exports seen through deferred proxy');
@@ -54,10 +56,12 @@ test('non-ESM imports non-ESM by name', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -90,10 +94,12 @@ test('non-ESM imports non-ESM as default', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -125,10 +131,12 @@ test('ESM imports non-ESM as default', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -160,10 +168,12 @@ test('ESM imports non-ESM by name', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -195,10 +205,12 @@ test('non-ESM imports ESM as default', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { default: odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { default: odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -230,10 +242,12 @@ test('non-ESM imports ESM by name', async t => {
     throw Error(`Cannot load module ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
-  const {
-    namespace: { odd },
-  } = await compartment.import('./odd');
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
+  const { odd } = await compartment.import('./odd');
 
   t.is(odd(1), true);
   t.is(odd(2), false);
@@ -290,6 +304,10 @@ test('cross import ESM and non-ESMs', async t => {
     throw Error(`Cannot load module for specifier ${specifier}`);
   };
 
-  const compartment = new Compartment({}, {}, { resolveHook, importHook });
+  const compartment = new Compartment(
+    {},
+    {},
+    { resolveHook, importHook, __noNamespaceBox__: true },
+  );
   await compartment.import('./src/main.js');
 });
diff --git a/packages/ses/test/import.test.js b/packages/ses/test/import.test.js
index fa08cb231f..d2bc5365a9 100644
--- a/packages/ses/test/import.test.js
+++ b/packages/ses/test/import.test.js
@@ -28,20 +28,18 @@ test('import within one compartment, web resolution', async t => {
   const importHook = makeImporter(locate, retrieve);
 
   const compartment = new Compartment(
-    // endowments:
     {
       double: n => n * 2,
     },
-    // module map:
     {},
-    // options:
     {
       resolveHook,
       importHook,
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import(
+  const namespace = await compartment.import(
     'https://example.com/packages/example/',
   );
 
@@ -64,20 +62,18 @@ test('import within one compartment, node resolution', async t => {
   });
 
   const compartment = new Compartment(
-    // endowments:
     {
       double: n => n * 2,
     },
-    // module map:
     {},
-    // options:
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/example'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.meaning, 42, 'dynamically imports the meaning');
 });
@@ -104,13 +100,10 @@ test('two compartments, three modules, one endowment', async t => {
   });
 
   const doubleCompartment = new Compartment(
-    // endowments:
     {
       double: n => n * 2,
     },
-    // module map:
     {},
-    // options:
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/double'),
@@ -118,23 +111,24 @@ test('two compartments, three modules, one endowment', async t => {
   );
 
   const compartment = new Compartment(
-    // endowments:
     {},
-    // module map:
     {
       // Notably, this is the first case where we thread a depencency between
       // two compartments, using the sigil of one's namespace to indicate
       // linkage before the module has been loaded.
-      double: doubleCompartment.module('./main.js'),
+      double: {
+        namespace: './main.js',
+        compartment: doubleCompartment,
+      },
     },
-    // options:
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/example'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(namespace.meaning, 42, 'dynamically imports the meaning');
 });
@@ -149,18 +143,16 @@ test('module exports namespace as an object', async t => {
   });
 
   const compartment = new Compartment(
-    // endowments:
     {},
-    // module map:
     {},
-    // options:
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/meaning'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
 
   t.is(
     namespace.meaning,
@@ -224,18 +216,16 @@ test('modules are memoized', async t => {
   });
 
   const compartment = new Compartment(
-    // endowments:
     {},
-    // module map:
     {},
-    // options:
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/example'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main.js');
+  const namespace = await compartment.import('./main.js');
   const { clive, clerk } = namespace;
 
   t.truthy(clive === clerk, 'diamond dependency must refer to the same module');
@@ -251,31 +241,26 @@ test('compartments with same sources do not share instances', async t => {
   });
 
   const leftCompartment = new Compartment(
-    {}, // endowments
-    {}, // module map
+    {},
+    {},
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/arm'),
+      __noNamespaceBox__: true,
     },
   );
 
   const rightCompartment = new Compartment(
-    {}, // endowments
-    {}, // module map
+    {},
+    {},
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/packages/arm'),
+      __noNamespaceBox__: true,
     },
   );
 
-  const [
-    {
-      namespace: { default: leftArm },
-    },
-    {
-      namespace: { default: rightArm },
-    },
-  ] = await Promise.all([
+  const [{ default: leftArm }, { default: rightArm }] = await Promise.all([
     leftCompartment.import('./main.js'),
     rightCompartment.import('./main.js'),
   ]);
@@ -321,6 +306,7 @@ test('module map hook', async t => {
     {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/dependency'),
+      __noNamespaceBox__: true,
     },
   );
 
@@ -336,10 +322,14 @@ test('module map hook', async t => {
           'dependency',
         );
         if (remainder) {
-          return dependency.module(remainder);
+          return {
+            namespace: remainder,
+            compartment: dependency,
+          };
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
@@ -379,7 +369,10 @@ test('mutual dependency between compartments', async t => {
     for (const [prefix, compartment] of Object.entries({ even, odd })) {
       const remainder = trimModuleSpecifierPrefix(moduleSpecifier, prefix);
       if (remainder) {
-        return compartment.module(remainder);
+        return {
+          compartment,
+          namespace: remainder,
+        };
       }
     }
     return undefined;
@@ -393,6 +386,7 @@ test('mutual dependency between compartments', async t => {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/even'),
       moduleMapHook,
+      __noNamespaceBox__: true,
     },
   );
 
@@ -404,6 +398,8 @@ test('mutual dependency between compartments', async t => {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com/odd'),
       moduleMapHook,
+      [Symbol.for('options')]: true,
+      __noNamespaceBox__: true,
     },
   );
 
@@ -415,6 +411,7 @@ test('mutual dependency between compartments', async t => {
       resolveHook: resolveNode,
       importHook: makeImportHook('https://example.com'),
       moduleMapHook,
+      __noNamespaceBox__: true,
     },
   );
 
@@ -453,17 +450,16 @@ test('import redirect shorthand', async t => {
   };
 
   const compartment = new Compartment(
-    {
-      Math,
-    },
+    { Math },
     {},
     {
       resolveHook: resolveNode,
       importHook,
+      __noNamespaceBox__: true,
     },
   );
 
-  const { namespace } = await compartment.import('./main');
+  const namespace = await compartment.import('./main');
   t.is(
     namespace.meaning,
     42,
@@ -514,21 +510,23 @@ test('import reflexive module alias', async t => {
 
   const moduleMapHook = specifier => {
     if (specifier === 'self') {
-      // eslint-disable-next-line no-use-before-define
-      return compartment.module('./index.js');
+      return {
+        // eslint-disable-next-line no-use-before-define
+        compartment,
+        namespace: './index.js',
+      };
     }
     return undefined;
   };
 
   const compartment = new Compartment(
-    {
-      t,
-    },
+    { t },
     {},
     {
       resolveHook: resolveNode,
       importHook,
       moduleMapHook,
+      __noNamespaceBox__: true,
     },
   );
 
@@ -580,12 +578,11 @@ test('import.meta populated from module record', async t => {
       importHook: makeImportHook('https://example.com', {
         meta: { url: 'https://example.com/index.js' },
       }),
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: metaurl },
-  } = await compartment.import('./index.js');
+  const { default: metaurl } = await compartment.import('./index.js');
   t.is(metaurl, 'https://example.com/index.js');
 });
 
@@ -609,12 +606,11 @@ test('importMetaHook', async t => {
       importMetaHook: (_moduleSpecifier, meta) => {
         meta.url = 'https://example.com/index.js';
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: metaurl },
-  } = await compartment.import('./index.js');
+  const { default: metaurl } = await compartment.import('./index.js');
   t.is(metaurl, 'https://example.com/index.js');
 });
 
@@ -641,11 +637,10 @@ test('importMetaHook and meta from record', async t => {
         meta.url += '?foo';
         meta.isStillMutableHopefully = 1;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: metaurl },
-  } = await compartment.import('./index.js');
+  const { default: metaurl } = await compartment.import('./index.js');
   t.is(metaurl, 'https://example.com/index.js?foo');
 });
diff --git a/packages/ses/test/module-map-hook.test.js b/packages/ses/test/module-map-hook.test.js
index 90eab3e45f..38c1238195 100644
--- a/packages/ses/test/module-map-hook.test.js
+++ b/packages/ses/test/module-map-hook.test.js
@@ -21,9 +21,10 @@ test('module map hook returns module source descriptor with precompiled module s
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -50,9 +51,10 @@ test('module map hook returns  module source descriptor with virtual module sour
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -98,9 +100,10 @@ test('module map hook returns parent compartment module source descriptor with s
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -180,9 +183,10 @@ test('module map hook returns parent compartment module source reference with di
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -203,12 +207,11 @@ test('module map hook returns module source descriptor for parent compartment wi
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: parentObject },
-  } = await parent.import('./object.js');
+  const { default: parentObject } = await parent.import('./object.js');
   t.is(parentObject.meaning, 42);
 
   const compartment = new parent.globalThis.Compartment(
@@ -228,12 +231,11 @@ test('module map hook returns module source descriptor for parent compartment wi
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: childObject },
-  } = await compartment.import('./index.js');
+  const { default: childObject } = await compartment.import('./index.js');
   t.is(childObject.meaning, 42);
   // Separate instances
   t.not(childObject, parentObject);
@@ -256,12 +258,11 @@ test('module map hook returns parent compartment module namespace descriptor', a
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: parentObject },
-  } = await parent.import('./object.js');
+  const { default: parentObject } = await parent.import('./object.js');
   t.is(parentObject.meaning, 42);
 
   const compartment = new parent.globalThis.Compartment(
@@ -281,12 +282,11 @@ test('module map hook returns parent compartment module namespace descriptor', a
         }
         return undefined;
       },
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: childObject },
-  } = await compartment.import('./index.js');
+  const { default: childObject } = await compartment.import('./index.js');
   t.is(childObject.meaning, 42);
   // Same instances
   t.is(childObject, parentObject);
diff --git a/packages/ses/test/module-map.test.js b/packages/ses/test/module-map.test.js
index fc4de21cb0..e69ce3d27e 100644
--- a/packages/ses/test/module-map.test.js
+++ b/packages/ses/test/module-map.test.js
@@ -17,9 +17,10 @@ test('module map primed with module source descriptor with precompiled module so
     // options:
     {
       resolveHook: specifier => specifier,
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -42,9 +43,10 @@ test('module map primed with module source descriptor with virtual module source
     // options:
     {
       resolveHook: specifier => specifier,
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -82,9 +84,10 @@ test('module map primed with parent compartment module source descriptor with st
     // options:
     {
       resolveHook: specifier => specifier,
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -154,9 +157,10 @@ test('module map primed with parent compartment module source reference with dif
         t.is(specifier, './lib/meaning.js');
         return './lib/meaningful.js';
       },
+      __noNamespaceBox__: true,
     },
   );
-  const { namespace: index } = await compartment.import('./index.js');
+  const index = await compartment.import('./index.js');
   t.is(index.default, 42);
 });
 
@@ -173,12 +177,11 @@ test('module map primed with module source descriptor for parent compartment wit
     // options:
     {
       name: 'parent',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: parentObject },
-  } = await parent.import('./object.js');
+  const { default: parentObject } = await parent.import('./object.js');
   t.is(parentObject.meaning, 42);
 
   const compartment = new parent.globalThis.Compartment(
@@ -194,12 +197,11 @@ test('module map primed with module source descriptor for parent compartment wit
     // options:
     {
       name: 'child',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: childObject },
-  } = await compartment.import('./index.js');
+  const { default: childObject } = await compartment.import('./index.js');
   t.is(childObject.meaning, 42);
   // Separate instances
   t.not(childObject, parentObject);
@@ -218,12 +220,11 @@ test('module map primed with parent compartment module namespace descriptor', as
     // options:
     {
       name: 'parent',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: parentObject },
-  } = await parent.import('./object.js');
+  const { default: parentObject } = await parent.import('./object.js');
   t.is(parentObject.meaning, 42);
 
   const compartment = new parent.globalThis.Compartment(
@@ -239,12 +240,11 @@ test('module map primed with parent compartment module namespace descriptor', as
     // options:
     {
       name: 'child',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: childObject },
-  } = await compartment.import('./index.js');
+  const { default: childObject } = await compartment.import('./index.js');
   t.is(childObject.meaning, 42);
   // Same instances
   t.is(childObject, parentObject);
@@ -263,12 +263,11 @@ test('module map primed with module source descriptor with string reference to p
     // options:
     {
       name: 'compartment1',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment1.import('./object.js');
+  const { default: object1 } = await compartment1.import('./object.js');
   t.is(object1.meaning, 42);
 
   const compartment2 = new Compartment(
@@ -284,12 +283,11 @@ test('module map primed with module source descriptor with string reference to p
     // options:
     {
       name: 'child',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object2 },
-  } = await compartment2.import('./index.js');
+  const { default: object2 } = await compartment2.import('./index.js');
   t.is(object2.meaning, 42);
   // Separate instances
   t.not(object1, object2);
@@ -308,12 +306,11 @@ test('module map primed with other compartment module namespace descriptor', asy
     // options:
     {
       name: 'compartment1',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object1 },
-  } = await compartment1.import('./object.js');
+  const { default: object1 } = await compartment1.import('./object.js');
   t.is(object1.meaning, 42);
 
   const compartment2 = new Compartment(
@@ -329,12 +326,11 @@ test('module map primed with other compartment module namespace descriptor', asy
     // options:
     {
       name: 'child',
+      __noNamespaceBox__: true,
     },
   );
 
-  const {
-    namespace: { default: object2 },
-  } = await compartment2.import('./index.js');
+  const { default: object2 } = await compartment2.import('./index.js');
   t.is(object2.meaning, 42);
   // Same instances
   t.is(object1, object2);
@@ -348,17 +344,21 @@ test('module map primed with module namespace descriptor and namespace object',
         source: new ModuleSource(`export default 42`),
       },
     },
-    {},
+    {
+      __noNamespaceBox__: true,
+    },
   );
-  const { namespace: namespace1 } = await compartment1.import('a');
+  const namespace1 = await compartment1.import('a');
   const compartment2 = new Compartment(
     {},
     {
       z: { namespace: namespace1 },
     },
-    {},
+    {
+      __noNamespaceBox__: true,
+    },
   );
-  const { namespace: namespace2 } = await compartment2.import('z');
+  const namespace2 = await compartment2.import('z');
   t.is(namespace2.default, 42);
   t.is(namespace1, namespace2);
 });
@@ -369,9 +369,11 @@ test('module map primed with module namespace descriptor and non-namespace objec
     {
       1: { namespace: { meaning: 42 } },
     },
-    {},
+    {
+      __noNamespaceBox__: true,
+    },
   );
-  const { namespace } = await compartment.import('1');
+  const namespace = await compartment.import('1');
   t.is(namespace.meaning, 42);
 });
 

From 82a315e82a7c54773d4c04127dcf521b43c2775f Mon Sep 17 00:00:00 2001
From: Kris Kowal <kris@agoric.com>
Date: Wed, 24 Jul 2024 17:15:47 -0700
Subject: [PATCH 4/4] docs(ses): News of namespace box opt-out

---
 packages/ses/NEWS.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/packages/ses/NEWS.md b/packages/ses/NEWS.md
index 49604228ad..10a93aa9af 100644
--- a/packages/ses/NEWS.md
+++ b/packages/ses/NEWS.md
@@ -25,6 +25,12 @@ User-visible changes in SES:
   the stacktrace line-numbers point back into the original
   source, as they do on Node without SES.
   
+- Adds a `__noNamespaceBox__` option that aligns the behavior of the `import`
+  method on SES `Compartment` with the behavior of XS and the behavior we will
+  champion for compartment standards.
+  All use of `Compartment` should migrate to use this option as the standard
+  behavior will be enabled by default with the next major version of SES.
+
 # v1.5.0 (2024-05-06)
 
 - Adds `importNowHook` to the `Compartment` options. The compartment will invoke the hook whenever it encounters a missing dependency while running `compartmentInstance.importNow(specifier)`, which cannot use an asynchronous `importHook`.