[GH-ISSUE #4201] [Bug] WASM optional-file failures identify the primary module argument #1649

Closed
opened 2026-10-16 21:08:27 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @coygeek on GitHub (Oct 15, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4201

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

inspect-wasm-artifact and inspect_wasm_artifact identify a missing or directory-valued selected glue/candidate file, or selected glue containing invalid UTF-8, as an error in the primary module's path argument. The primary module is readable and remains unchanged. The returned invalid_request has details.issues[0].path: ["path"] instead of the rejected glue_paths or candidate_paths array entry, so callers receive incorrect structured argument-correction metadata. Missing/directory diagnostics name the rejected file; the encoding diagnostic supplies no filename.

All five CLI missing/directory/invalid-UTF-8 optional-selection cases exited 1 with empty stderr and the same incorrect details.issues[0].path. Missing glue, missing candidate and both invalid-UTF-8 glue positions also returned this incorrect metadata through the public stdio MCP tool, as did the missing second-glue case. The following excerpts are observed output; only the generated fixture-root prefix is replaced by <fixture>.

{"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"ENOENT: no such file or directory, lstat '<fixture>/missing-glue.js'"}]}}
{"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"ENOENT: no such file or directory, lstat '<fixture>/missing-candidate.wasm'"}]}}
{"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"Expected a regular file without a symlink: <fixture>/glue-dir"}]}}
{"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"The encoded data was not valid for encoding utf-8"}]}}

For invalid UTF-8, the error names neither glue.bin nor the selected field/index. The script records only the three version probes for each encoding failure; no module validation, objdump or WAT decoding command ran. This is a rejected selected-input diagnostic, not evidence that completed module analysis was discarded.

The missing-module controls correctly reported ["path"] through both entries. MCP relative-glue and relative-candidate controls correctly reported their respective array field at index 0 with reason invalid_format. Valid glue/candidate controls succeeded through both entries, and the CLI exited 0. Their parsed reference was ./module.wasm at line 1, column 6, with association local-path-candidate and only the selected module as its candidate. The valid-UTF-8 syntax-error controls also succeeded through both entries, retaining the selected glue byte digest, parse_status: "failed" and no references. Those success controls used simulated WABT output. All selected input digests remained unchanged, the isolated runtime directory was empty after MCP shutdown, and no cleanup failure masked the primary errors.

Reproduction

Use the baseline and pinned toolchain under REA version and Runtime and platform, with locked dependencies installed. From the checkout root, run npm run build:cached, then run the following complete local fixture. It creates executable fakes supplying the admitted version banners and simulated empty-module WABT output. It exercises actual REA CLI, stdio MCP, application validation, subprocess ownership, file reads and error projection. It does not run a real WABT engine or execute WASM. The script isolates HOME/config/cache/runtime, verifies the fixture bytes with WebAssembly.validate, checks selected-file digests, and removes its exact fixture directory. The baseline assertions deliberately check the observed incorrect metadata.

work_dir="$(mktemp -d)"
trap 'rmdir "$work_dir"' EXIT
node --input-type=module - "$work_dir/files" <<'JS'
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { spawnSync } from 'node:child_process';
import { createRequire } from 'node:module';
import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';

// Run from the built REA checkout and pass a new fixture directory outside it.
// All WABT output is simulated. No real WABT engine or WASM code runs.
const repo = process.cwd();
assert.ok(process.argv[2], 'Pass a new fixture directory');
const root = resolve(process.argv[2]);
await mkdir(root);
const digest = bytes => createHash('sha256').update(bytes).digest('hex');
const emit = value => console.log(JSON.stringify(value).split(root).join('<fixture>'));
let client;
try {
  for (const directory of ['tools', 'home', 'config', 'cache', 'runtime', 'glue-dir', 'candidate-dir']) await mkdir(join(root, directory));
  const bytes = Buffer.from('0061736d01000000', 'hex');
  assert.equal(WebAssembly.validate(bytes), true);
  await writeFile(join(root, 'module.wasm'), bytes);
  await writeFile(join(root, 'candidate.wasm'), bytes);
  await writeFile(join(root, 'glue.js'), 'fetch("./module.wasm");\n');
  await writeFile(join(root, 'unparseable.js'), 'const = ;\n');
  await writeFile(join(root, 'glue.bin'), Buffer.from('fffe00626164', 'hex'));
  const inputs = ['module.wasm', 'candidate.wasm', 'glue.js', 'unparseable.js', 'glue.bin'];
  const toolLog = join(root, 'tool-calls.jsonl');
  await writeFile(toolLog, '');
  const versionCalls = [
    { tool: 'wasm-validate', args: ['--version'] },
    { tool: 'wasm-objdump', args: ['--version'] },
    { tool: 'wasm2wat', args: ['--version'] },
  ];
  const encodingMessage = 'The encoded data was not valid for encoding utf-8';
  const checkEncodingFailure = async error => {
    assert.equal(error.details.issues[0].message, encodingMessage);
    const calls = (await readFile(toolLog, 'utf8')).trim().split('\n').map(line => JSON.parse(line));
    assert.deepEqual(calls, versionCalls);
    return calls;
  };
  const before = await Promise.all(inputs.map(async name => digest(await readFile(join(root, name)))));
  for (const tool of ['wasm-validate', 'wasm-objdump', 'wasm2wat']) {
    const text = tool === 'wasm-objdump' ? '\nmodule.wasm:\tfile format wasm 0x1\n\nSections:\n\n\nSection Details:\n' : tool === 'wasm2wat' ? '(module)\n' : '';
    const source = '#!/usr/bin/env node\n// Simulated WABT producer output.\nconst { appendFileSync } = require("node:fs");\nappendFileSync(' + JSON.stringify(toolLog) + ', JSON.stringify({tool:' + JSON.stringify(tool) + ',args:process.argv.slice(2)}) + "\\n");\nif (process.argv[2] === "--version") process.stdout.write("1.0.42\\n"); else process.stdout.write(' + JSON.stringify(text) + ');\n';
    await writeFile(join(root, 'tools', tool), source, { mode: 0o700 });
  }
  const env = { PATH: process.env.PATH, HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'config'), XDG_CACHE_HOME: join(root, 'cache'), XDG_RUNTIME_DIR: join(root, 'runtime'), UV_THREADPOOL_SIZE: '2', TMPDIR: join(root, 'runtime'), REA_WABT_BIN_DIRECTORY: join(root, 'tools'), REA_IDA_MCP_CONFIG: join(root, 'no-ida.json'), REA_LOG_LEVEL: 'silent' };
  const selected = join(root, 'module.wasm');
  const cases = [
    ['missing-glue', selected, ['--glue_paths', join(root, 'missing-glue.js')]],
    ['missing-candidate', selected, ['--candidate_paths', join(root, 'missing-candidate.wasm')]],
    ['directory-glue', selected, ['--glue_paths', join(root, 'glue-dir')]],
    ['directory-candidate', selected, ['--candidate_paths', join(root, 'candidate-dir')]],
    ['invalid-utf8-glue', selected, ['--glue_paths', join(root, 'glue.bin')]],
    ['syntax-error-control', selected, ['--glue_paths', join(root, 'unparseable.js')]],
    ['missing-module-control', join(root, 'missing-module.wasm'), []],
    ['valid-glue-control', selected, ['--glue_paths', join(root, 'glue.js'), '--candidate_paths', join(root, 'candidate.wasm')]],
  ];
  for (const [name, path, options] of cases) {
    await writeFile(toolLog, '');
    const args = [join(repo, 'scripts/rea.mjs'), 'inspect-wasm-artifact', path, ...options, '--format', 'json'];
    const result = spawnSync(process.execPath, args, { env, encoding: 'utf8', timeout: 60000 });
    if (result.error) throw result.error;
    assert.equal(result.stderr, '');
    const reply = JSON.parse(result.stdout);
    if (name === 'syntax-error-control') {
      assert.equal(result.status, 0);
      const report = reply.normalized_result;
      assert.equal(report.glue[0].parse_status, 'failed');
      assert.deepEqual(report.glue[0].references, []);
      assert.equal(report.glue[0].artifact.sha256, digest(await readFile(join(root, 'unparseable.js'))));
      emit({ interface: 'CLI', case: name, exit: result.status, validation: report.validation, glue: report.glue[0] });
    } else if (name === 'valid-glue-control') {
      assert.equal(result.status, 0);
      const report = reply.normalized_result;
      assert.equal(report.glue[0].parse_status, 'complete');
      assert.equal(report.glue[0].artifact.sha256, digest(await readFile(join(root, 'glue.js'))));
      assert.equal(report.glue[0].references[0].association, 'local-path-candidate');
      assert.deepEqual(report.glue[0].references[0].candidate_paths, [selected]);
      emit({ interface: 'CLI', case: name, exit: result.status, validation: report.validation, reference: report.glue[0].references[0] });
    } else {
      assert.equal(result.status, 1);
      assert.equal(reply.code, 'invalid_request');
      assert.deepEqual(reply.details.issues[0].path, ['path']);
      const calls = name === 'invalid-utf8-glue' ? await checkEncodingFailure(reply) : undefined;
      emit({ interface: 'CLI', case: name, exit: result.status, error: reply, tool_calls: calls });
    }
  }
  const require = createRequire(join(repo, 'package.json'));
  const { Client } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client')).href);
  const { StdioClientTransport } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client/stdio')).href);
  client = new Client({ name: 'wasm-field-verifier', version: '1' });
  await client.connect(new StdioClientTransport({ command: process.execPath, args: [join(repo, 'scripts/rea.mjs'), 'mcp'], env, stderr: 'pipe' }));
  for (const [name, arguments_, expected] of [
    ['missing-glue', { path: selected, glue_paths: [join(root, 'missing-glue.js')] }, ['path']],
    ['missing-candidate', { path: selected, candidate_paths: [join(root, 'missing-candidate.wasm')] }, ['path']],
    ['second-glue', { path: selected, glue_paths: [join(root, 'glue.js'), join(root, 'missing-glue.js')] }, ['path']],
    ['invalid-utf8-glue', { path: selected, glue_paths: [join(root, 'glue.bin')] }, ['path']],
    ['second-invalid-utf8-glue', { path: selected, glue_paths: [join(root, 'glue.js'), join(root, 'glue.bin')] }, ['path']],
    ['relative-glue-control', { path: selected, glue_paths: ['relative.js'] }, ['glue_paths', 0]],
    ['relative-candidate-control', { path: selected, candidate_paths: ['relative.wasm'] }, ['candidate_paths', 0]],
    ['missing-module-control', { path: join(root, 'missing-module.wasm') }, ['path']],
  ]) {
    await writeFile(toolLog, '');
    const reply = await client.callTool({ name: 'inspect_wasm_artifact', arguments: arguments_ });
    assert.equal(reply.isError, true);
    const error = JSON.parse(reply.content.find(item => item.type === 'text').text).error;
    assert.equal(error.code, 'invalid_request');
    assert.deepEqual(error.details.issues[0].path, expected);
    const calls = name.includes('invalid-utf8') ? await checkEncodingFailure(error) : undefined;
    emit({ interface: 'stdio MCP', case: name, error, tool_calls: calls });
  }
  const valid = await client.callTool({ name: 'inspect_wasm_artifact', arguments: { path: selected, glue_paths: [join(root, 'glue.js')], candidate_paths: [join(root, 'candidate.wasm')] } });
  assert.notEqual(valid.isError, true);
  assert.equal(valid.structuredContent.normalized_result.glue[0].references[0].association, 'local-path-candidate');
  emit({ interface: 'stdio MCP', case: 'valid-glue-control', reference: valid.structuredContent.normalized_result.glue[0].references[0] });
  const syntax = await client.callTool({ name: 'inspect_wasm_artifact', arguments: { path: selected, glue_paths: [join(root, 'unparseable.js')] } });
  assert.notEqual(syntax.isError, true);
  const syntaxReport = syntax.structuredContent.normalized_result;
  assert.equal(syntaxReport.glue[0].parse_status, 'failed');
  assert.deepEqual(syntaxReport.glue[0].references, []);
  assert.equal(syntaxReport.glue[0].artifact.sha256, digest(await readFile(join(root, 'unparseable.js'))));
  emit({ interface: 'stdio MCP', case: 'syntax-error-control', validation: syntaxReport.validation, glue: syntaxReport.glue[0] });
  await client.ping();
  await client.close();
  client = undefined;
  const after = await Promise.all(inputs.map(async name => digest(await readFile(join(root, name)))));
  assert.deepEqual(after, before);
  assert.deepEqual(await readdir(join(root, 'runtime')), []);
  emit({ inputs_unchanged: true, temporary_runtime_empty: true });
} finally {
  if (client) await client.close();
  await rm(root, { recursive: true, force: true });
}
JS

The failing CLI calls are inspect-wasm-artifact with a valid module and either --glue_paths selecting missing-glue.js, glue-dir or the six-byte glue.bin fixture, or --candidate_paths selecting missing-candidate.wasm or candidate-dir, followed by --format json. The exact underscore flag names are advertised by node scripts/rea.mjs inspect-wasm-artifact --help. The script also calls public MCP with typed arrays, including [existingGlue, missingGlue] and [existingGlue, invalidUtf8Glue] to reject the second glue entry. The unparseable.js control contains valid UTF-8 with invalid JavaScript syntax.

Expected behavior

Attribute each rejected selected file to its caller argument. Missing, non-regular or invalid-UTF-8 first glue selections should report ["glue_paths", 0]. Missing or non-regular first candidate selections should report ["candidate_paths", 0]. A rejected second glue selection should report ["glue_paths", 1]. The same operation already returns those field/index paths when rejecting relative selections before provider work. A rejected primary module should continue to report ["path"], and valid glue/candidate selections should continue to succeed. Rejection of the selected file is allowed; the defect is which argument the structured issue identifies. No partial-success response is required for undecodable glue. The valid-UTF-8 syntax-error control should retain its existing successful result with parse_status: "failed".

The input-issue contract defines correction metadata for one rejected caller argument. This operation already attributes relative selections to their array field/index in its application validation and MCP contract test.

Impact and scope

P2, supported. Missing/directory optional selections and invalid-UTF-8 glue return structured correction metadata naming a valid primary-module argument. The encoding message also omits the rejected filename, leaving callers without either the correct field/index or a named file. This is a bounded functional diagnostic defect; no automated-client repair loop or corruption of valid inspection Evidence was observed. No contract requiring partial success for undecodable glue was established.

Overall workaround status is unverified. Missing/directory messages identify the rejected file, and selecting existing intended files succeeds through both public entries without changing the module. That demonstrated recovery does not cover the full report: the encoding message names no file. Checking each selected file independently with a fatal UTF-8 decoder may locate undecodable glue, but that recovery procedure was not executed here and no general automatic repair was demonstrated. Omitting glue would remove selected evidence; re-encoding would change its bytes and was not tested. Neither is established as a workaround preserving the requested original glue evidence.

REA version

Tested source revision 62bf09809ef7bb224028916424f8f07652a20f4f, freshly built with the locked dependencies and pinned toolchain. The launcher prints 6.5.0; this report does not claim reproduction against the published package or release tag.

Runtime and platform

Node.js v24.18.0; npm 11.16.0; macOS arm64. All WABT banners and producer output are simulated by executable fakes. No real WABT engine or WASM execution occurred.

Target and artifact context

An eight-byte valid empty core-WASM module, an identical candidate, neutral selected glue, absent paths, directory selections, and six-byte invalid-UTF-8 glue. The reproduction validates the WASM bytes without instantiating them and checks unchanged selected-file digests.

Interface

CLI and stdio MCP

Earliest failure stage

Transport or result projection. Selected-file acquisition or decoding fails first as expected; the defect becomes observable when its error identifies the primary module argument.

Relevant output or evidence

Observed public-entry evidence, reproduced with complete inline fixtures above. This publication review reran the exact fixture in the assigned baseline checkout and independently observed all eight CLI cases and ten MCP calls, unchanged selected bytes, and an empty isolated runtime after MCP shutdown. The earlier verifier also passed 29 existing focused provider/MCP tests across two files; those tests alone do not establish the encoding case.

Current main 1b828dbb1011d80f44b3ae99efb9554991d71261 was fetched before publication. The 16 inspected contract, entry, composition, reader, provider, documentation and test files are byte-identical to the tested baseline. Current-main applicability is source-inspected, not a runtime reproduction on that revision.

Typed outcome or recovery expected

Rerun the same public inputs and require missing/directory glue and candidate issues, plus invalid-UTF-8 glue issues, to identify their selected array field and index. Both missing and invalid-UTF-8 second-glue selections must identify ["glue_paths", 1]; the first invalid-UTF-8 selection must identify ["glue_paths", 0]. Preserve invalid_request, the actual acquisition/encoding diagnostic, and the absence of cleanup masking. Keep the missing-module controls at ["path"], the relative-path controls at their existing field/index paths, successful valid-glue controls with the same literal association, and successful valid-UTF-8 syntax-error controls with failed parser status and retained byte identity. Keep all selected input digests unchanged. A successful partial module/glue response for invalid UTF-8 is not part of this restoration check. The reproduction script's assertions of incorrect ["path"] optional-selection metadata describe the failing baseline; restoration must satisfy the expected paths above instead.

Analysis or suspected cause

Source-derived explanation: the provider artifact phase reads candidates and glue and fatally decodes glue before module-processing commands. Its catch supplies the primary module path, and the artifact-failure projection hard-codes ["path"]. This explains the observed selected-file attribution failure without requiring partial success or prescribing an implementation.

Additional context

Related open reports #4001 and #3402 concern successful glue-reference retention and path-association ambiguity. Closed #2618 concerned relative-path validation before provider invocation. Its current guard passes the relative-selection controls above; this report concerns admitted absolute selections rejected later during acquisition or decoding. Open #3935 requires repeated cleanup failure; cleanup succeeds in this report. These reports have distinct triggers and restoration checks. Real-WABT compatibility, other platforms, access-denial and changing-file variants were not verified. NOT_ENOUGH_INFO: no last-known-good revision or population frequency is established.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4201 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `inspect-wasm-artifact` and `inspect_wasm_artifact` identify a missing or directory-valued selected glue/candidate file, or selected glue containing invalid UTF-8, as an error in the primary module's `path` argument. The primary module is readable and remains unchanged. The returned `invalid_request` has `details.issues[0].path: ["path"]` instead of the rejected `glue_paths` or `candidate_paths` array entry, so callers receive incorrect structured argument-correction metadata. Missing/directory diagnostics name the rejected file; the encoding diagnostic supplies no filename. All five CLI missing/directory/invalid-UTF-8 optional-selection cases exited 1 with empty stderr and the same incorrect `details.issues[0].path`. Missing glue, missing candidate and both invalid-UTF-8 glue positions also returned this incorrect metadata through the public stdio MCP tool, as did the missing second-glue case. The following excerpts are observed output; only the generated fixture-root prefix is replaced by `<fixture>`. ```json {"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"ENOENT: no such file or directory, lstat '<fixture>/missing-glue.js'"}]}} {"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"ENOENT: no such file or directory, lstat '<fixture>/missing-candidate.wasm'"}]}} {"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"Expected a regular file without a symlink: <fixture>/glue-dir"}]}} {"code":"invalid_request","category":"invalid_input","details":{"operation":"inspect_wasm_artifact","issues":[{"path":["path"],"reason":"invalid_value","message":"The encoded data was not valid for encoding utf-8"}]}} ``` For invalid UTF-8, the error names neither `glue.bin` nor the selected field/index. The script records only the three version probes for each encoding failure; no module validation, objdump or WAT decoding command ran. This is a rejected selected-input diagnostic, not evidence that completed module analysis was discarded. The missing-module controls correctly reported `["path"]` through both entries. MCP relative-glue and relative-candidate controls correctly reported their respective array field at index 0 with reason `invalid_format`. Valid glue/candidate controls succeeded through both entries, and the CLI exited 0. Their parsed reference was `./module.wasm` at line 1, column 6, with association `local-path-candidate` and only the selected module as its candidate. The valid-UTF-8 syntax-error controls also succeeded through both entries, retaining the selected glue byte digest, `parse_status: "failed"` and no references. Those success controls used simulated WABT output. All selected input digests remained unchanged, the isolated runtime directory was empty after MCP shutdown, and no cleanup failure masked the primary errors. ### Reproduction Use the baseline and pinned toolchain under REA version and Runtime and platform, with locked dependencies installed. From the checkout root, run `npm run build:cached`, then run the following complete local fixture. It creates executable fakes supplying the admitted version banners and simulated empty-module WABT output. It exercises actual REA CLI, stdio MCP, application validation, subprocess ownership, file reads and error projection. It does not run a real WABT engine or execute WASM. The script isolates HOME/config/cache/runtime, verifies the fixture bytes with `WebAssembly.validate`, checks selected-file digests, and removes its exact fixture directory. The baseline assertions deliberately check the observed incorrect metadata. ```sh work_dir="$(mktemp -d)" trap 'rmdir "$work_dir"' EXIT node --input-type=module - "$work_dir/files" <<'JS' import assert from 'node:assert/strict'; import { createHash } from 'node:crypto'; import { spawnSync } from 'node:child_process'; import { createRequire } from 'node:module'; import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; // Run from the built REA checkout and pass a new fixture directory outside it. // All WABT output is simulated. No real WABT engine or WASM code runs. const repo = process.cwd(); assert.ok(process.argv[2], 'Pass a new fixture directory'); const root = resolve(process.argv[2]); await mkdir(root); const digest = bytes => createHash('sha256').update(bytes).digest('hex'); const emit = value => console.log(JSON.stringify(value).split(root).join('<fixture>')); let client; try { for (const directory of ['tools', 'home', 'config', 'cache', 'runtime', 'glue-dir', 'candidate-dir']) await mkdir(join(root, directory)); const bytes = Buffer.from('0061736d01000000', 'hex'); assert.equal(WebAssembly.validate(bytes), true); await writeFile(join(root, 'module.wasm'), bytes); await writeFile(join(root, 'candidate.wasm'), bytes); await writeFile(join(root, 'glue.js'), 'fetch("./module.wasm");\n'); await writeFile(join(root, 'unparseable.js'), 'const = ;\n'); await writeFile(join(root, 'glue.bin'), Buffer.from('fffe00626164', 'hex')); const inputs = ['module.wasm', 'candidate.wasm', 'glue.js', 'unparseable.js', 'glue.bin']; const toolLog = join(root, 'tool-calls.jsonl'); await writeFile(toolLog, ''); const versionCalls = [ { tool: 'wasm-validate', args: ['--version'] }, { tool: 'wasm-objdump', args: ['--version'] }, { tool: 'wasm2wat', args: ['--version'] }, ]; const encodingMessage = 'The encoded data was not valid for encoding utf-8'; const checkEncodingFailure = async error => { assert.equal(error.details.issues[0].message, encodingMessage); const calls = (await readFile(toolLog, 'utf8')).trim().split('\n').map(line => JSON.parse(line)); assert.deepEqual(calls, versionCalls); return calls; }; const before = await Promise.all(inputs.map(async name => digest(await readFile(join(root, name))))); for (const tool of ['wasm-validate', 'wasm-objdump', 'wasm2wat']) { const text = tool === 'wasm-objdump' ? '\nmodule.wasm:\tfile format wasm 0x1\n\nSections:\n\n\nSection Details:\n' : tool === 'wasm2wat' ? '(module)\n' : ''; const source = '#!/usr/bin/env node\n// Simulated WABT producer output.\nconst { appendFileSync } = require("node:fs");\nappendFileSync(' + JSON.stringify(toolLog) + ', JSON.stringify({tool:' + JSON.stringify(tool) + ',args:process.argv.slice(2)}) + "\\n");\nif (process.argv[2] === "--version") process.stdout.write("1.0.42\\n"); else process.stdout.write(' + JSON.stringify(text) + ');\n'; await writeFile(join(root, 'tools', tool), source, { mode: 0o700 }); } const env = { PATH: process.env.PATH, HOME: join(root, 'home'), XDG_CONFIG_HOME: join(root, 'config'), XDG_CACHE_HOME: join(root, 'cache'), XDG_RUNTIME_DIR: join(root, 'runtime'), UV_THREADPOOL_SIZE: '2', TMPDIR: join(root, 'runtime'), REA_WABT_BIN_DIRECTORY: join(root, 'tools'), REA_IDA_MCP_CONFIG: join(root, 'no-ida.json'), REA_LOG_LEVEL: 'silent' }; const selected = join(root, 'module.wasm'); const cases = [ ['missing-glue', selected, ['--glue_paths', join(root, 'missing-glue.js')]], ['missing-candidate', selected, ['--candidate_paths', join(root, 'missing-candidate.wasm')]], ['directory-glue', selected, ['--glue_paths', join(root, 'glue-dir')]], ['directory-candidate', selected, ['--candidate_paths', join(root, 'candidate-dir')]], ['invalid-utf8-glue', selected, ['--glue_paths', join(root, 'glue.bin')]], ['syntax-error-control', selected, ['--glue_paths', join(root, 'unparseable.js')]], ['missing-module-control', join(root, 'missing-module.wasm'), []], ['valid-glue-control', selected, ['--glue_paths', join(root, 'glue.js'), '--candidate_paths', join(root, 'candidate.wasm')]], ]; for (const [name, path, options] of cases) { await writeFile(toolLog, ''); const args = [join(repo, 'scripts/rea.mjs'), 'inspect-wasm-artifact', path, ...options, '--format', 'json']; const result = spawnSync(process.execPath, args, { env, encoding: 'utf8', timeout: 60000 }); if (result.error) throw result.error; assert.equal(result.stderr, ''); const reply = JSON.parse(result.stdout); if (name === 'syntax-error-control') { assert.equal(result.status, 0); const report = reply.normalized_result; assert.equal(report.glue[0].parse_status, 'failed'); assert.deepEqual(report.glue[0].references, []); assert.equal(report.glue[0].artifact.sha256, digest(await readFile(join(root, 'unparseable.js')))); emit({ interface: 'CLI', case: name, exit: result.status, validation: report.validation, glue: report.glue[0] }); } else if (name === 'valid-glue-control') { assert.equal(result.status, 0); const report = reply.normalized_result; assert.equal(report.glue[0].parse_status, 'complete'); assert.equal(report.glue[0].artifact.sha256, digest(await readFile(join(root, 'glue.js')))); assert.equal(report.glue[0].references[0].association, 'local-path-candidate'); assert.deepEqual(report.glue[0].references[0].candidate_paths, [selected]); emit({ interface: 'CLI', case: name, exit: result.status, validation: report.validation, reference: report.glue[0].references[0] }); } else { assert.equal(result.status, 1); assert.equal(reply.code, 'invalid_request'); assert.deepEqual(reply.details.issues[0].path, ['path']); const calls = name === 'invalid-utf8-glue' ? await checkEncodingFailure(reply) : undefined; emit({ interface: 'CLI', case: name, exit: result.status, error: reply, tool_calls: calls }); } } const require = createRequire(join(repo, 'package.json')); const { Client } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client')).href); const { StdioClientTransport } = await import(pathToFileURL(require.resolve('@modelcontextprotocol/client/stdio')).href); client = new Client({ name: 'wasm-field-verifier', version: '1' }); await client.connect(new StdioClientTransport({ command: process.execPath, args: [join(repo, 'scripts/rea.mjs'), 'mcp'], env, stderr: 'pipe' })); for (const [name, arguments_, expected] of [ ['missing-glue', { path: selected, glue_paths: [join(root, 'missing-glue.js')] }, ['path']], ['missing-candidate', { path: selected, candidate_paths: [join(root, 'missing-candidate.wasm')] }, ['path']], ['second-glue', { path: selected, glue_paths: [join(root, 'glue.js'), join(root, 'missing-glue.js')] }, ['path']], ['invalid-utf8-glue', { path: selected, glue_paths: [join(root, 'glue.bin')] }, ['path']], ['second-invalid-utf8-glue', { path: selected, glue_paths: [join(root, 'glue.js'), join(root, 'glue.bin')] }, ['path']], ['relative-glue-control', { path: selected, glue_paths: ['relative.js'] }, ['glue_paths', 0]], ['relative-candidate-control', { path: selected, candidate_paths: ['relative.wasm'] }, ['candidate_paths', 0]], ['missing-module-control', { path: join(root, 'missing-module.wasm') }, ['path']], ]) { await writeFile(toolLog, ''); const reply = await client.callTool({ name: 'inspect_wasm_artifact', arguments: arguments_ }); assert.equal(reply.isError, true); const error = JSON.parse(reply.content.find(item => item.type === 'text').text).error; assert.equal(error.code, 'invalid_request'); assert.deepEqual(error.details.issues[0].path, expected); const calls = name.includes('invalid-utf8') ? await checkEncodingFailure(error) : undefined; emit({ interface: 'stdio MCP', case: name, error, tool_calls: calls }); } const valid = await client.callTool({ name: 'inspect_wasm_artifact', arguments: { path: selected, glue_paths: [join(root, 'glue.js')], candidate_paths: [join(root, 'candidate.wasm')] } }); assert.notEqual(valid.isError, true); assert.equal(valid.structuredContent.normalized_result.glue[0].references[0].association, 'local-path-candidate'); emit({ interface: 'stdio MCP', case: 'valid-glue-control', reference: valid.structuredContent.normalized_result.glue[0].references[0] }); const syntax = await client.callTool({ name: 'inspect_wasm_artifact', arguments: { path: selected, glue_paths: [join(root, 'unparseable.js')] } }); assert.notEqual(syntax.isError, true); const syntaxReport = syntax.structuredContent.normalized_result; assert.equal(syntaxReport.glue[0].parse_status, 'failed'); assert.deepEqual(syntaxReport.glue[0].references, []); assert.equal(syntaxReport.glue[0].artifact.sha256, digest(await readFile(join(root, 'unparseable.js')))); emit({ interface: 'stdio MCP', case: 'syntax-error-control', validation: syntaxReport.validation, glue: syntaxReport.glue[0] }); await client.ping(); await client.close(); client = undefined; const after = await Promise.all(inputs.map(async name => digest(await readFile(join(root, name))))); assert.deepEqual(after, before); assert.deepEqual(await readdir(join(root, 'runtime')), []); emit({ inputs_unchanged: true, temporary_runtime_empty: true }); } finally { if (client) await client.close(); await rm(root, { recursive: true, force: true }); } JS ``` The failing CLI calls are `inspect-wasm-artifact` with a valid module and either `--glue_paths` selecting `missing-glue.js`, `glue-dir` or the six-byte `glue.bin` fixture, or `--candidate_paths` selecting `missing-candidate.wasm` or `candidate-dir`, followed by `--format json`. The exact underscore flag names are advertised by `node scripts/rea.mjs inspect-wasm-artifact --help`. The script also calls public MCP with typed arrays, including `[existingGlue, missingGlue]` and `[existingGlue, invalidUtf8Glue]` to reject the second glue entry. The `unparseable.js` control contains valid UTF-8 with invalid JavaScript syntax. ### Expected behavior Attribute each rejected selected file to its caller argument. Missing, non-regular or invalid-UTF-8 first glue selections should report `["glue_paths", 0]`. Missing or non-regular first candidate selections should report `["candidate_paths", 0]`. A rejected second glue selection should report `["glue_paths", 1]`. The same operation already returns those field/index paths when rejecting relative selections before provider work. A rejected primary module should continue to report `["path"]`, and valid glue/candidate selections should continue to succeed. Rejection of the selected file is allowed; the defect is which argument the structured issue identifies. No partial-success response is required for undecodable glue. The valid-UTF-8 syntax-error control should retain its existing successful result with `parse_status: "failed"`. The [input-issue contract](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/analysisErrorCore.ts#L69-L83) defines correction metadata for one rejected caller argument. This operation already attributes relative selections to their array field/index in its [application validation](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/application/wasm/WasmArtifactService.ts#L45-L66) and [MCP contract test](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/tests/boundary/mcp/wasmArtifactMcp.test.ts#L218-L315). ### Impact and scope P2, supported. Missing/directory optional selections and invalid-UTF-8 glue return structured correction metadata naming a valid primary-module argument. The encoding message also omits the rejected filename, leaving callers without either the correct field/index or a named file. This is a bounded functional diagnostic defect; no automated-client repair loop or corruption of valid inspection Evidence was observed. No contract requiring partial success for undecodable glue was established. Overall workaround status is unverified. Missing/directory messages identify the rejected file, and selecting existing intended files succeeds through both public entries without changing the module. That demonstrated recovery does not cover the full report: the encoding message names no file. Checking each selected file independently with a fatal UTF-8 decoder may locate undecodable glue, but that recovery procedure was not executed here and no general automatic repair was demonstrated. Omitting glue would remove selected evidence; re-encoding would change its bytes and was not tested. Neither is established as a workaround preserving the requested original glue evidence. ### REA version Tested source revision `62bf09809ef7bb224028916424f8f07652a20f4f`, freshly built with the locked dependencies and pinned toolchain. The launcher prints `6.5.0`; this report does not claim reproduction against the published package or release tag. ### Runtime and platform Node.js v24.18.0; npm 11.16.0; macOS arm64. All WABT banners and producer output are simulated by executable fakes. No real WABT engine or WASM execution occurred. ### Target and artifact context An eight-byte valid empty core-WASM module, an identical candidate, neutral selected glue, absent paths, directory selections, and six-byte invalid-UTF-8 glue. The reproduction validates the WASM bytes without instantiating them and checks unchanged selected-file digests. ### Interface CLI and stdio MCP ### Earliest failure stage Transport or result projection. Selected-file acquisition or decoding fails first as expected; the defect becomes observable when its error identifies the primary module argument. ### Relevant output or evidence Observed public-entry evidence, reproduced with complete inline fixtures above. This publication review reran the exact fixture in the assigned baseline checkout and independently observed all eight CLI cases and ten MCP calls, unchanged selected bytes, and an empty isolated runtime after MCP shutdown. The earlier verifier also passed 29 existing focused provider/MCP tests across two files; those tests alone do not establish the encoding case. Current main `1b828dbb1011d80f44b3ae99efb9554991d71261` was fetched before publication. The 16 inspected contract, entry, composition, reader, provider, documentation and test files are byte-identical to the tested baseline. Current-main applicability is source-inspected, not a runtime reproduction on that revision. ### Typed outcome or recovery expected Rerun the same public inputs and require missing/directory glue and candidate issues, plus invalid-UTF-8 glue issues, to identify their selected array field and index. Both missing and invalid-UTF-8 second-glue selections must identify `["glue_paths", 1]`; the first invalid-UTF-8 selection must identify `["glue_paths", 0]`. Preserve `invalid_request`, the actual acquisition/encoding diagnostic, and the absence of cleanup masking. Keep the missing-module controls at `["path"]`, the relative-path controls at their existing field/index paths, successful valid-glue controls with the same literal association, and successful valid-UTF-8 syntax-error controls with failed parser status and retained byte identity. Keep all selected input digests unchanged. A successful partial module/glue response for invalid UTF-8 is not part of this restoration check. The reproduction script's assertions of incorrect `["path"]` optional-selection metadata describe the failing baseline; restoration must satisfy the expected paths above instead. ### Analysis or suspected cause Source-derived explanation: the [provider artifact phase](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/wasm/WabtArtifactProvider.ts#L232-L284) reads candidates and glue and fatally decodes glue before module-processing commands. Its [catch](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/wasm/WabtArtifactProvider.ts#L433-L446) supplies the primary module path, and the [artifact-failure projection](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/wasm/WabtFailures.ts#L163-L170) hard-codes `["path"]`. This explains the observed selected-file attribution failure without requiring partial success or prescribing an implementation. ### Additional context Related open reports [#4001](https://github.com/morluto/rea/issues/4001) and [#3402](https://github.com/morluto/rea/issues/3402) concern successful glue-reference retention and path-association ambiguity. Closed [#2618](https://github.com/morluto/rea/issues/2618) concerned relative-path validation before provider invocation. Its current guard passes the relative-selection controls above; this report concerns admitted absolute selections rejected later during acquisition or decoding. Open [#3935](https://github.com/morluto/rea/issues/3935) requires repeated cleanup failure; cleanup succeeds in this report. These reports have distinct triggers and restoration checks. Real-WABT compatibility, other platforms, access-denial and changing-file variants were not verified. NOT_ENOUGH_INFO: no last-known-good revision or population frequency is established.
Author
Owner

@Abdooo2235 commented on GitHub (Oct 15, 2026):

I am working on selected-file error attribution. The change will preserve the caller field and original array index through candidate reads and glue decoding, with controls for primary-module errors and existing cleanup behavior.

<!-- gh-comment-id:6201802717 --> @Abdooo2235 commented on GitHub (Oct 15, 2026): I am working on selected-file error attribution. The change will preserve the caller field and original array index through candidate reads and glue decoding, with controls for primary-module errors and existing cleanup behavior.
Author
Owner

@Abdooo2235 commented on GitHub (Oct 15, 2026):

@coygeek the fix is pushed to Abdooo2235:fix/wasm-selected-file-errors (8a2830d). The new regression passed after failing on the baseline; 10 MCP tests, static/docs checks, and real WABT CLI/stdio MCP attribution checks passed. The existing provider suite still has 12 pre-existing Windows fixture failures, recorded in the prepared PR description. GitHub rejected PR creation with insufficient permissions. Compare: https://github.com/morluto/rea/compare/main...Abdooo2235:rea:fix/wasm-selected-file-errors

<!-- gh-comment-id:6201889578 --> @Abdooo2235 commented on GitHub (Oct 15, 2026): @coygeek the fix is pushed to Abdooo2235:fix/wasm-selected-file-errors (8a2830d). The new regression passed after failing on the baseline; 10 MCP tests, static/docs checks, and real WABT CLI/stdio MCP attribution checks passed. The existing provider suite still has 12 pre-existing Windows fixture failures, recorded in the prepared PR description. GitHub rejected PR creation with insufficient permissions. Compare: https://github.com/morluto/rea/compare/main...Abdooo2235:rea:fix/wasm-selected-file-errors
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

WabtArtifactProvider now tracks the selected artifact path and input field throughout candidate reads and glue decoding. Candidate selections use their first original array index, glue selections use entries() index, and the artifact catch passes that field into wabtFailure, which uses it for invalid_request issues. The primary module retains [path].

Implementation: WabtArtifactProvider.ts, WabtArtifactProvider.ts.

The relevant regression checks in WabtFailures.test.ts, wasmArtifactMcp.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380759367 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. WabtArtifactProvider now tracks the selected artifact path and input field throughout candidate reads and glue decoding. Candidate selections use their first original array index, glue selections use entries() index, and the artifact catch passes that field into wabtFailure, which uses it for invalid_request issues. The primary module retains [path]. Implementation: [WabtArtifactProvider.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/wasm/WabtArtifactProvider.ts#L294), [WabtArtifactProvider.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/wasm/WabtArtifactProvider.ts#L318). The relevant regression checks in [WabtFailures.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/wasm/WabtFailures.test.ts), [wasmArtifactMcp.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/mcp/wasmArtifactMcp.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#1649
No description provided.