diff --git a/packages/compiler/src/executable/prepare.ts b/packages/compiler/src/executable/prepare.ts index 90504d0ef..5f3e45c9a 100644 --- a/packages/compiler/src/executable/prepare.ts +++ b/packages/compiler/src/executable/prepare.ts @@ -461,10 +461,13 @@ function preflightIslands( /** Packages compiled natively with runtime fences (refusals deferred to the * operation that reaches them) that should run in islands instead: those - * whose island class is runtime (R1) or object-model (R2), by NI-2's report - * over the pre-scan markers and the fences, or with a fence that itself - * needs the runtime. Gap-only packages keep their fences until decision 8 - * (D6) retires them. `--node-islands=auto` only; null for none. */ + * with a fence that surely fires (one in top-level module code, which runs + * when the module evaluates) or that itself needs the runtime (R1). Other + * fences stay deferred refusals until decision 8 (D6) retires them, so + * typed packages like jose stay native. With `requireFence` false (after a + * failed build) the package's island class by NI-2's report decides + * instead: runtime (R1) or object-model (R2). `--node-islands=auto` only; + * null for none. */ function fenceIslands( entryPath: string, opts: CompileRequestOptions, @@ -515,6 +518,11 @@ function fenceIslands( if (runtimeFence === undefined || island.path === null) continue; if (!compiled.has(island.package) || known.has(island.package)) continue; if (policy.forcedNative.has(island.package) || policy.unloaded.has(island.package)) continue; + // With fences (requireFence), only fences that surely fire count: a + // gap fence inside a function body may never run (jose's typed paths + // stay native, DECISIONS.md 2026-10-21). After a failed build + // (!requireFence), the package's report class decides. + if (requireFence && !runtimeFence) continue; if (!runtimeFence && island.class !== "runtime" && island.class !== "object-model") continue; grown.set(island.package, { name: island.package, diff --git a/packages/libnode-common/bridge/scr_node_bridge.cc b/packages/libnode-common/bridge/scr_node_bridge.cc index d63a3b6f1..45430e4a7 100644 --- a/packages/libnode-common/bridge/scr_node_bridge.cc +++ b/packages/libnode-common/bridge/scr_node_bridge.cc @@ -1444,6 +1444,11 @@ Local ExportValue(Isolate *isolate, Local context, const ScrNode } else { obj = ExportTemplate(isolate)->NewInstance(context).ToLocalChecked(); obj->SetInternalField(0, data); + // A template instance inherits from its implicit constructor's own + // prototype object; a native plain object is a plain object to island + // code (`Object.getPrototypeOf(o) === Object.prototype`, as jose's + // isObject checks), so it inherits Object.prototype directly. + obj->SetPrototypeV2(context, v8::Object::New(isolate)->GetPrototypeV2()).FromMaybe(false); } if (!obj->IsProxy()) obj->SetPrivate(context, ExportKey(isolate), data).Check(); Global &slot = g_exports[id]; diff --git a/tests/corpus/6331-jose-root-entry/package.json b/tests/corpus/6331-jose-root-entry/package.json deleted file mode 100644 index 30d3e0af0..000000000 --- a/tests/corpus/6331-jose-root-entry/package.json +++ /dev/null @@ -1 +0,0 @@ -{ "type": "module", "scriptc": { "packages": { "jose": "native" } } } diff --git a/tests/corpus/6610-npm-execa-child-mixin/package.json b/tests/corpus/6610-npm-execa-child-mixin/package.json deleted file mode 100644 index 5a418a30c..000000000 --- a/tests/corpus/6610-npm-execa-child-mixin/package.json +++ /dev/null @@ -1 +0,0 @@ -{ "type": "module", "scriptc": { "packages": { "run-node": "native" } } } diff --git a/tests/corpus/6826-node-island-undefined-args/main.ts b/tests/corpus/6826-node-island-undefined-args/main.ts index 94a350e67..5f6fb53e9 100644 --- a/tests/corpus/6826-node-island-undefined-args/main.ts +++ b/tests/corpus/6826-node-island-undefined-args/main.ts @@ -12,6 +12,7 @@ import { nodeVersionFor, cwd, envValue, + plain, type Config, } from "argprobe"; import { mkdtempSync, writeFileSync, rmSync } from "node:fs"; @@ -54,6 +55,8 @@ const none = projectSettings(false); console.log(version("/work", "unused", { nodeVersion: project?.nodeVersion }, undefined, available)); console.log(version("/work", "unused", { nodeVersion: none?.nodeVersion }, undefined, available)); console.log(probe("p", { nodeVersion: project?.nodeVersion }), probe("n", { nodeVersion: none?.nodeVersion })); +const claims = { role: "admin" }; +console.log("plain", plain({ sub: "alice" }), plain(claims)); async function main(): Promise { // A package.json the program writes just before the island reads it. diff --git a/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.d.ts b/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.d.ts index 72cde09b4..6ce43de4d 100644 --- a/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.d.ts +++ b/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.d.ts @@ -21,3 +21,4 @@ export declare function nodeVersionFor( ): Promise; export declare function cwd(): string; export declare function envValue(name: string): string | undefined; +export declare function plain(v: object): string; diff --git a/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.js b/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.js index 5a9e4245e..5cf8f9301 100644 --- a/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.js +++ b/tests/corpus/6826-node-island-undefined-args/node_modules/argprobe/index.js @@ -68,3 +68,11 @@ exports.nodeVersionFor = async function nodeVersionFor( exports.cwd = () => process.cwd(); exports.envValue = (name) => process.env[name]; + +// A native plain object is a plain object here (jose's isObject). +exports.plain = (v) => + [ + Object.getPrototypeOf(v) === Object.prototype, + Object.prototype.toString.call(v), + Object.getPrototypeOf(Object.getPrototypeOf(v)) === null, + ].join(" "); diff --git a/tests/harness/npm.test.ts b/tests/harness/npm.test.ts index d08a4c0fb..3643a9859 100644 --- a/tests/harness/npm.test.ts +++ b/tests/harness/npm.test.ts @@ -66,46 +66,42 @@ const KNOWN: Record = { divergence: "Timeout handles are numbers (typeof) and lose their ref flag once fired or cleared", }, - "hot-shots-udp": { - divergence: - "as a Node island (its runtime fences pick one), output stops after the socket-not-running error; the built-in hot-shots shim (#1836) compiles it natively", - }, "island-web-plumbing": { run: "SC2020" }, "lazy-traps": { island: true }, - "misc-shims": { island: true }, + "misc-shims": { run: "SC2020" }, namespace: { island: true }, "node24-async-events": { run: "SC2020" }, "node24-commonjs-metadata": { island: true }, "node24-crypto-kdf": { run: "SC2020" }, - "node24-deep-equality": { island: true }, + "node24-deep-equality": { run: "SC2020" }, "node24-diagnostics-async": { run: "SC1090" }, "node24-diagnostics-tracing": { run: "SC1090" }, - "node24-env": { island: true }, + "node24-env": { run: "SC1090" }, "node24-esm-metadata": { island: true }, "node24-event-emitter": { run: "SC2020" }, "node24-file-url-conversion": { island: true }, "node24-fs-descriptors": { run: "SC2020" }, - "node24-mime": { island: true }, - "node24-mime-errors": { island: true }, + "node24-mime": { run: "SC2020" }, + "node24-mime-errors": { run: "SC1090" }, "node24-no-error-assertions": { run: "SC2020" }, - "node24-parseargs": { island: true }, + "node24-parseargs": { run: "SC1090" }, "node24-promise-timers": { run: "SC2020" }, "node24-string-decoder": { run: "SC2020" }, - "node24-style-text": { island: true }, + "node24-style-text": { run: "SC2020" }, "node24-system-errors": { run: "SC1090" }, "node24-text-encoding": { run: "SC2020" }, "node24-text-utilities": { run: "SC2020" }, "node24-timers-unref": { run: "SC2020" }, "node24-url-factories": { run: "SC2020" }, "node24-url-mutation": { run: "SC1090" }, - "node24-util-adapters": { island: true }, - "node24-util-types": { island: true }, + "node24-util-adapters": { run: "Object.create with this value is not supported yet" }, + "node24-util-types": { run: "SC1090" }, "node24-web-stream-encoding": { run: "SC1090" }, "node24-web-stream-errors": { run: "SC1090" }, "node24-web-stream-lifecycle": { run: "SC1090" }, "node24-web-stream-pipe-abort": { run: "SC2020" }, "node24-web-stream-pipe-errors": { run: "SC1090" }, - "node24-web-stream-pipes": { island: true }, + "node24-web-stream-pipes": { run: "SC1090" }, "node24-web-stream-queues": { run: "SC1090" }, "node24-web-stream-readable-lifecycle": { run: "SC2020" }, "node24-web-stream-readers": { run: "SC1090" }, @@ -114,8 +110,8 @@ const KNOWN: Record = { "package-imports": { island: true }, "promise-bridge": { build: "SC1090" }, "self-name": { build: "SC2013" }, - "stream-shims": { island: true }, - "util-shims": { island: true }, + "stream-shims": { run: "SC1090" }, + "util-shims": { run: "SC2020" }, "web-streams": { run: "SC1090" }, "workspace-copied": { island: true }, "workspace-unclean": { build: "SC2013" },