From fa494066d5b61a2b3d0a5e0066cb471f5d58083c Mon Sep 17 00:00:00 2001 From: J-Dog Date: Wed, 16 Sep 2026 07:12:20 -0700 Subject: [PATCH 1/3] fix(mutation): key mutation-report tiers on the repo-relative path scripts/mutation-report.js keyed its tier map on the bare filename, so the split parts under src/index/, src/gateway/ and src/gateway_emit/ fell through to the Unknown/80 default instead of their entry's tier. Key on the repo-relative path instead, every existing tier assignment unchanged, and pin it with a case in the existing static-contract test. --- scripts/mutation-report.js | 47 +++++++++++++------ .../unit/meta/scripts_mutation_report.test.js | 46 ++++++++++++++++++ 2 files changed, 78 insertions(+), 15 deletions(-) diff --git a/scripts/mutation-report.js b/scripts/mutation-report.js index dd669f2..75f0725 100644 --- a/scripts/mutation-report.js +++ b/scripts/mutation-report.js @@ -36,20 +36,37 @@ const DEFAULT_CUSTOM_JSON = path.join(ROOT, 'reports', 'mutation', 'custom-muta const OUTPUT_MD = path.join(ROOT, 'reports', 'mutation', 'MUTATION_SUMMARY.md'); // Tier classification matching XCHAIN_VM_MUTATION_TESTING_PLAN.md +// +// Keyed by repo-relative path (not bare filename): the entry-file splits +// under src/index/, src/gateway/ and src/gateway_emit/ report at the same +// tier as the entry they were carved out of, instead of falling through to +// the Unknown/80 default a bare-filename key would give every part file. const TIERS = { - 'index.js': { tier: 'Critical', target: 95 }, - 'metering.js': { tier: 'Critical', target: 95 }, - 'sandbox.js': { tier: 'Critical', target: 95 }, - 'gas.js': { tier: 'Critical', target: 95 }, - 'gateway.js': { tier: 'High', target: 90 }, - 'gateway_emit.js': { tier: 'High', target: 90 }, - 'state.js': { tier: 'High', target: 90 }, - 'math.js': { tier: 'High', target: 90 }, - 'collector.js': { tier: 'Medium', target: 85 }, - 'validator.js': { tier: 'Medium', target: 85 }, - 'syntax.js': { tier: 'Medium', target: 85 }, - 'isolate.js': { tier: 'Low', target: 80 }, - 'errors.js': { tier: 'Low', target: 80 } + 'src/index.js': { tier: 'Critical', target: 95 }, + 'src/index/block_lifecycle.js': { tier: 'Critical', target: 95 }, + 'src/index/constants.js': { tier: 'Critical', target: 95 }, + 'src/index/contract_wrapper.js': { tier: 'Critical', target: 95 }, + 'src/index/error_results.js': { tier: 'Critical', target: 95 }, + 'src/index/gateway_injection.js': { tier: 'Critical', target: 95 }, + 'src/index/install_methods.js': { tier: 'Critical', target: 95 }, + 'src/index/lint_and_metering.js': { tier: 'Critical', target: 95 }, + 'src/index/manifest.js': { tier: 'Critical', target: 95 }, + 'src/metering.js': { tier: 'Critical', target: 95 }, + 'src/sandbox.js': { tier: 'Critical', target: 95 }, + 'src/gas.js': { tier: 'Critical', target: 95 }, + 'src/gateway.js': { tier: 'High', target: 90 }, + 'src/gateway/accessors.js': { tier: 'High', target: 90 }, + 'src/gateway/contract_stake.js': { tier: 'High', target: 90 }, + 'src/gateway_emit.js': { tier: 'High', target: 90 }, + 'src/gateway_emit/param_validation.js': { tier: 'High', target: 90 }, + 'src/gateway_emit/same_chain.js': { tier: 'High', target: 90 }, + 'src/state.js': { tier: 'High', target: 90 }, + 'src/math.js': { tier: 'High', target: 90 }, + 'src/collector.js': { tier: 'Medium', target: 85 }, + 'src/validator.js': { tier: 'Medium', target: 85 }, + 'src/syntax.js': { tier: 'Medium', target: 85 }, + 'src/isolate.js': { tier: 'Low', target: 80 }, + 'src/errors.js': { tier: 'Low', target: 80 } }; const TIER_ORDER = { Critical: 0, High: 1, Medium: 2, Low: 3, Unknown: 4 }; @@ -156,7 +173,7 @@ function computeFileStats(filePath, fileData) { const scorable = killed + survived + timeout + noCoverage; const score = scorable > 0 ? ((killed + timeout) / scorable * 100) : 0; - const tierInfo = TIERS[filename] || { tier: 'Unknown', target: 80 }; + const tierInfo = TIERS[filePath] || { tier: 'Unknown', target: 80 }; const status = score >= tierInfo.target ? 'PASS' : 'FAIL'; return { filename, filePath, scorable, killed, survived, timeout, noCoverage, ignored, compileError, score, tierInfo, status }; @@ -299,7 +316,7 @@ function generateMarkdown(mergedFiles) { const survivedList = []; for (const [filePath, fileData] of Object.entries(mergedFiles)) { const filename = path.basename(filePath); - const tierInfo = TIERS[filename] || { tier: 'Unknown', target: 80 }; + const tierInfo = TIERS[filePath] || { tier: 'Unknown', target: 80 }; for (const m of fileData.mutants) { if (m.status === 'Survived' || m.status === 'NoCoverage') { survivedList.push({ filename, filePath, tierInfo, mutant: m }); diff --git a/test/unit/meta/scripts_mutation_report.test.js b/test/unit/meta/scripts_mutation_report.test.js index 2e77b71..2a8ebb3 100644 --- a/test/unit/meta/scripts_mutation_report.test.js +++ b/test/unit/meta/scripts_mutation_report.test.js @@ -27,6 +27,22 @@ const vm = require('vm'); const SRC = path.join(__dirname, '../../../scripts/mutation-report.js'); const source = fs.readFileSync(SRC, 'utf8').replace(/^#!.*\n/, ''); +// main() self-executes on load and can process.exit(1), so the +// static-contract tests below never require() this file. To exercise the +// real TIERS lookup and computeFileStats() without that risk (or any +// fs.writeFileSync side effect), patch out the trailing main() call and run +// the rest of the source in a fresh vm context. +function loadReportInternals() { + const patched = source.replace(/\nmain\(\);\s*$/, '\nmodule.exports = { TIERS, computeFileStats };\n'); + assert.notStrictEqual(patched, source, 'expected to patch out the trailing main() call'); + + const sandbox = { module: { exports: {} }, require, __dirname: path.dirname(SRC), __filename: SRC, console, process }; + sandbox.exports = sandbox.module.exports; + vm.createContext(sandbox); + new vm.Script(patched, { filename: 'mutation-report.js' }).runInContext(sandbox); + return sandbox.module.exports; +} + describe('scripts/mutation-report (static contract)', function () { it('is syntactically valid JavaScript (compiles without executing)', function () { assert.doesNotThrow(() => new vm.Script(source, { filename: 'mutation-report.js' })); @@ -47,4 +63,34 @@ describe('scripts/mutation-report (static contract)', function () { `report generator must not require ${mod}`); } }); + + it('keeps every pre-existing entry file at its declared tier', function () { + const { TIERS } = loadReportInternals(); + assert.strictEqual(TIERS['src/index.js'].tier, 'Critical'); + assert.strictEqual(TIERS['src/index.js'].target, 95); + assert.strictEqual(TIERS['src/gateway.js'].tier, 'High'); + assert.strictEqual(TIERS['src/gateway.js'].target, 90); + assert.strictEqual(TIERS['src/gateway_emit.js'].tier, 'High'); + assert.strictEqual(TIERS['src/gateway_emit.js'].target, 90); + }); + + it('keys mutation tiers on the repo-relative path, so an entry-file split part inherits its entry tier', function () { + const { computeFileStats } = loadReportInternals(); + + // A part file carved out of the vm entry (src/index/*.js) inherits + // the entry's Critical tier instead of falling to the Unknown/80 + // default a bare-filename lookup would give it. + const indexPart = computeFileStats('src/index/install_methods.js', { mutants: [] }); + assert.strictEqual(indexPart.tierInfo.tier, 'Critical'); + assert.strictEqual(indexPart.tierInfo.target, 95); + + // A part file carved out of the gateway entries (src/gateway/*.js, + // src/gateway_emit/*.js) inherits the gateway High tier. + const gatewayPart = computeFileStats('src/gateway/accessors.js', { mutants: [] }); + assert.strictEqual(gatewayPart.tierInfo.tier, 'High'); + assert.strictEqual(gatewayPart.tierInfo.target, 90); + const gatewayEmitPart = computeFileStats('src/gateway_emit/same_chain.js', { mutants: [] }); + assert.strictEqual(gatewayEmitPart.tierInfo.tier, 'High'); + assert.strictEqual(gatewayEmitPart.tierInfo.target, 90); + }); }); From d06f23bd41852b118ec5682e603edd18d4185571 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Wed, 16 Sep 2026 07:12:24 -0700 Subject: [PATCH 2/3] chore(vm): drop the unused fs require from the entry src/index.js required fs but never called it (measured: no fs. call anywhere in the file, including inside template literals). Removing the one require line moves no other byte in the consensus entry. --- src/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.js b/src/index.js index 801e786..8a2bc5d 100644 --- a/src/index.js +++ b/src/index.js @@ -25,7 +25,6 @@ const crypto = require('crypto'); const ivm = require('isolated-vm'); -const fs = require('fs'); const IsolateManager = require('./isolate.js'); const GasTracker = require('./gas.js'); From f1c471c2fabe0bd843b971c9485fbc656b416858 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Thu, 17 Sep 2026 13:58:55 -0700 Subject: [PATCH 3/3] chore(release): bump to 0.20.0, refresh the README badge and record the changelog --- CHANGELOG.md | 8 ++++++++ README.md | 2 +- package-lock.json | 4 ++-- package.json | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 367f2c9..6271e98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.20.0] - 2026-09-17 + +### Changed +- Mutation-report tiers are keyed by repository-relative paths. + +### Fixed +- The VM entry point no longer loads the unused `fs` module. + ## [0.19.0] - 2026-09-16 ### Changed diff --git a/README.md b/README.md index 31b5406..24416a0 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # XChain Platform Virtual Machine (VM)

- Version + Version Tests Node License diff --git a/package-lock.json b/package-lock.json index c3ff780..159fcbd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xchain-vm", - "version": "0.19.0", + "version": "0.20.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "xchain-vm", - "version": "0.19.0", + "version": "0.20.0", "license": "AGPL-3.0-or-later", "dependencies": { "acorn": "8.16.0", diff --git a/package.json b/package.json index 6e0dab3..4eaf011 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xchain-vm", "description": "XChain Platform Virtual Machine (VM) runtime - deterministic smart contract execution in sandboxed V8 isolates", - "version": "0.19.0", + "version": "0.20.0", "ciPlatform": "linux", "license": "AGPL-3.0-or-later", "main": "src/index.js",