Skip to content

fix(ses) exported namespace consistency between VirtualModuleInstance and ModuleInstance#3246

Draft
naugtur wants to merge 8 commits into
masterfrom
naugtur/ses-namespace-mutation
Draft

fix(ses) exported namespace consistency between VirtualModuleInstance and ModuleInstance#3246
naugtur wants to merge 8 commits into
masterfrom
naugtur/ses-namespace-mutation

Conversation

@naugtur

@naugtur naugtur commented May 6, 2026

Copy link
Copy Markdown
Member

TBD

Closes: #XXXX
Refs: #XXXX

Description

Add a description of the changes that this PR introduces and the files that are the most critical to review.

Security Considerations

Does this change introduce new assumptions or dependencies that, if violated, could introduce security vulnerabilities? How does this PR change the boundaries between mutually-suspicious components? What new authorities are introduced by this change, perhaps by new API calls?

Scaling Considerations

Does this change require or encourage significant increase in consumption of CPU cycles, RAM, on-chain storage, message exchanges, or other scarce resources? If so, can that be prevented or mitigated?

Documentation Considerations

Give our docs folks some hints about what needs to be described to downstream users. Backwards compatibility: what happens to existing data or deployments when this code is shipped? Do we need to instruct users to do something to upgrade their saved data? If there is no upgrade path possible, how bad will that be for users?

Testing Considerations

Every PR should of course come with tests of its own functionality. What additional tests are still needed beyond those unit tests? How does this affect CI, other test automation, or the testnet?

Compatibility Considerations

Does this change break any prior usage patterns? Does this change allow usage patterns to evolve?

Upgrade Considerations

What aspects of this PR are relevant to upgrading live production systems, and how should they be addressed?

For any breaking change, add a changeset (yarn changeset) with a major bump and migration instructions in the description. See the "Using Changesets" section in CONTRIBUTING.md.

Delete guidance from pull request description before merge (including this!)

naugtur and others added 5 commits May 5, 2026 18:10
Add a test that compares Node.js native ESM and a SES Compartment when
one module does `import * as foo from './a'; foo.x = 'bar'` and another
module later imports `x` from the same module.

Both runtimes prevent the override (assignment throws TypeError, later
imports see the original value), but the protection mechanism differs
in observable ways: Node.js exposes data descriptors with writable: true
on a non-frozen, non-extensible namespace, while SES exposes accessor
descriptors on a frozen target. The test pins down both the parity
behaviors and the structural differences.
These differences are known discrepancies the authors have found no way
to emulate faithfully.

The fixture is loaded once from disk and fed to both runtimes (Node.js
via subprocess, the Compartment via @endo/module-source) so the two
sides cannot drift.
@changeset-bot

changeset-bot Bot commented May 6, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: bffb57b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@naugtur

naugtur commented May 6, 2026

Copy link
Copy Markdown
Member Author

A few cjs-compat tests are failing - their fixtures are documented with comments that Node.js is not supporting finding the named export. With this change we no longer find it either. I think we should drop these tests

One set of attenuator tests is failing because it's now missing the default - should probably be introduced manually.

The test finding inconsistencies in live bindings between endo and node should probably be turned into a snapshot test and issues to fix compatibility if possible. Fixing one of the inconsistencies feels like it could be a side effect of this fix, but I didn't hit that target with my current attempt.


Failing tests:

cjs-compat › fixtures-cjs-compat
cjs-compat › fixtures-cjs-compat-exit-module

Error: Expected 2.cjs to to have a named export called even

  • tests needs to be adjusted

dynamic-require › dynamic require of ancestor

  • not sure, needs debugging

import-live-bindings-interop › import mutability compared with node.js

  • probably best to convert to a snapshot test. deepEqual is easier to read for now.

policy › policy - *

missing "default" in snapshots of module namespaces

  • "default" needs to be added to the list of expected exports in the implementation instead of being implicitly added always (or test needs to be updated - not 100% sure if the default should be there TBH)

kriscendobot pushed a commit to endojs/endo-but-for-bots that referenced this pull request May 8, 2026
Mechanical cleanups noted in the panel review for the mirror PR; no
substance changes to naugtur's fix.

- Run prettier on packages/ses/src/module-instance.js (drops a stray
  blank line) and on import-live-bindings-interop.test.js.
- Add trailing newlines to three new fixture files under
  fixtures-esm-imports-cjs-define/node_modules/.
- Add `/* global process */` to namespace-mutation.test.js and
  _namespace-mutation/main.js so eslint's no-undef rule passes.
- Annotate _namespace-mutation/b.js with `// @ts-nocheck` and
  `eslint-disable no-import-assign` because the fixture intentionally
  attempts to write to an imported namespace member to verify that the
  runtime rejects the mutation.
- Hoist the first three `await readSource(...)` calls out of the
  object literal in namespace-mutation.test.js to satisfy
  @jessie.js/safe-await-separator.
- Cast the result of `compartment.import('./main.js')` so tsc accepts
  the namespace shape (the compartment is constructed with
  `__noNamespaceBox__: true`, so import resolves to the namespace
  directly, not `{ namespace }`, but the type signature still reflects
  the legacy boxed shape).

These changes touch only mirror-side mechanics; the test still passes
locally on Node 22 and the eslint/typecheck failures observed in CI
are eliminated. The Node 18.x test failure in
import-live-bindings-interop is independent (Node 18 does not support
`require()` of an `.mjs` file) and is also failing on upstream
endojs/endo#3246 — substance for naugtur to address upstream.
kriscendobot pushed a commit to endojs/endo-but-for-bots that referenced this pull request May 8, 2026
Mechanical cleanups noted in the panel review for the mirror PR; no
substance changes to naugtur's fix.

- Run prettier on packages/ses/src/module-instance.js (drops a stray
  blank line) and on import-live-bindings-interop.test.js.
- Add trailing newlines to three new fixture files under
  fixtures-esm-imports-cjs-define/node_modules/.
- Add `/* global process */` to namespace-mutation.test.js and
  _namespace-mutation/main.js so eslint's no-undef rule passes.
- Annotate _namespace-mutation/b.js with `// @ts-nocheck` and
  `eslint-disable no-import-assign` because the fixture intentionally
  attempts to write to an imported namespace member to verify that the
  runtime rejects the mutation.
- Hoist the first three `await readSource(...)` calls out of the
  object literal in namespace-mutation.test.js to satisfy
  @jessie.js/safe-await-separator.
- Cast the result of `compartment.import('./main.js')` so tsc accepts
  the namespace shape (the compartment is constructed with
  `__noNamespaceBox__: true`, so import resolves to the namespace
  directly, not `{ namespace }`, but the type signature still reflects
  the legacy boxed shape).

These changes touch only mirror-side mechanics; the test still passes
locally on Node 22 and the eslint/typecheck failures observed in CI
are eliminated. The Node 18.x test failure in
import-live-bindings-interop is independent (Node 18 does not support
`require()` of an `.mjs` file) and is also failing on upstream
endojs/endo#3246 — substance for naugtur to address upstream.
' ESM importing ESM/namespaceNamedLet: both LIVE',
' ESM importing ESM/namespaceNamedVar: both LIVE',
' ESM importing ESM/namespaceNamedConstValue: both LIVE',
'[!] ESM importing CJS (exports.*)/namedExport_namedA: node=STATIC endo=LIVE ',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a nice report.


export const getSummary = async () => {
// Wait for mutations (50ms)
await new Promise(resolve => setTimeout(resolve, 100));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can almost certainly solve this coordination problem without timers. Consider injecting promise and resolver pairs into the environment instead and coordinating on those.

Comment on lines +44 to +45
'[!] CJS importing ESM (require())/namespace_namedLet: node=LIVE endo=STATIC ',
'[!] CJS importing ESM (require())/namespace_namedVar: node=LIVE endo=STATIC ',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, but defensible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants