[GH-ISSUE #3992] [Bug] Failed IDA decompilation drops the known profile from partial Evidence #1586

Closed
opened 2026-10-16 21:07:29 +00:00 by bsakaguchi · 4 comments
Owner

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

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

After an IDA headless session successfully returns Evidence with a concrete analysis profile, a failed decompilation returns partial Evidence with analysis_profile: null. The partial record retains the completed function lookup but loses the session's known compatibility, mode and registration commitment. This was observed through REA's production stdio MCP entry point with a simulated database-supervisor provider.

Reproduction

Use the already built baseline checkout and pinned Node.js stated in the REA version and Runtime and platform fields. This reproduction uses no real IDA engine or external service. Save the following as profile-repro.mjs in a disposable directory outside the checkout, set REPRO to that file, and run node "$REPRO" "$PWD" from the checkout root. The script creates its fixture, isolated HOME and provider workspace beside itself, closes the session, and removes those generated resources.

import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
import { dirname, join, resolve } from 'node:path';
import { createInterface } from 'node:readline';
import { fileURLToPath } from 'node:url';

const providerSource = `
import { createInterface } from 'node:readline';
const names = ['idb_open', 'idb_list', 'idb_close', 'server_health',
  'list_funcs', 'lookup_funcs', 'find_regex', 'decompile', 'disasm', 'callees', 'xrefs_to'];
let session, input;
for await (const line of createInterface({ input: process.stdin })) {
  const request = JSON.parse(line);
  if (request.id === undefined) continue;
  const reply = result => process.stdout.write(JSON.stringify({
    jsonrpc: '2.0', id: request.id, result }) + '\\n');
  if (request.method === 'initialize') {
    reply({ protocolVersion: request.params.protocolVersion,
      capabilities: { tools: {} }, serverInfo: { name: 'profile-fixture', version: '1' } });
    continue;
  }
  if (request.method === 'tools/list') {
    reply({ tools: names.map(name => ({ name, inputSchema: { type: 'object' } })) });
    continue;
  }
  const { name, arguments: args } = request.params;
  let value;
  switch (name) {
    case 'idb_open':
      session = args.preferred_session_id;
      input = args.input_path;
      value = { success: true, session: { session_id: session, input_path: input } };
      break;
    case 'idb_list':
      value = { sessions: session ? [{ session_id: session, input_path: input,
        backend: 'worker', owned: true, is_active: true }] : [] };
      break;
    case 'idb_close':
      value = { success: true, session_id: session, owned: true, backend: 'worker', saved: false };
      session = undefined;
      break;
    case 'server_health':
      value = { status: 'ok', input_path: input, idb_path: input + '.i64',
        module: 'input.elf', imagebase: '0x1000', auto_analysis_ready: true };
      break;
    case 'lookup_funcs':
      value = args.queries.map(query => ({ query,
        fn: { addr: '0x1000', name: 'main', size: '0x20' }, error: null }));
      break;
    case 'decompile':
      reply({ isError: true, content: [{ type: 'text', text: 'Deliberate fixture failure' }] });
      continue;
    default:
      throw new Error('Unexpected upstream tool: ' + name);
  }
  reply({ content: [{ type: 'text', text: JSON.stringify(value) }] });
}
`;
const root = await mkdtemp(join(dirname(fileURLToPath(import.meta.url)), 'profile-case-'));
const repo = resolve(process.argv[2]);
const home = join(root, 'home');
await mkdir(home);
const target = join(root, 'input.elf');
const bytes = Buffer.alloc(64);
bytes.set([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1]);
bytes.writeUInt16LE(62, 18);
bytes.writeUInt32LE(1, 20);
bytes.writeUInt16LE(64, 52);
await writeFile(target, bytes);
const provider = join(root, 'provider.mjs');
const config = join(root, 'provider.json');
await writeFile(provider, providerSource);
await writeFile(config, JSON.stringify({ command: process.execPath, args: [provider],
  mode: 'headless', timeoutMs: 3000, workspaceRoot: root }));
const server = spawn(process.execPath, [join(repo, 'scripts/rea.mjs'), 'mcp'], {
  cwd: repo, stdio: ['pipe', 'pipe', 'inherit'], env: { ...process.env, HOME: home,
    REA_ANALYSIS_PROVIDER: 'ida', REA_IDA_MCP_CONFIG: config,
    REA_MCP_INPUT_SCHEMA_PROFILE: 'compact', REA_MCP_OUTPUT_SCHEMA_PROFILE: 'off',
    REA_LOG_LEVEL: 'silent' }
});
const exited = new Promise(resolve => server.once('exit', (code, signal) => resolve({ code, signal })));
const pending = new Map();
let sequence = 0;
createInterface({ input: server.stdout }).on('line', line => {
  const response = JSON.parse(line);
  pending.get(response.id)?.(response);
  pending.delete(response.id);
});
const request = (method, params) => new Promise(resolve => {
  const id = ++sequence;
  pending.set(id, resolve);
  server.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
});
const call = async (name, args) => {
  const response = await request('tools/call', { name, arguments: args });
  assert.equal(response.error, undefined);
  return response.result;
};
const deadline = setTimeout(() => server.kill('SIGTERM'), 20000);
try {
  await request('initialize', { protocolVersion: '2025-11-25', capabilities: {},
    clientInfo: { name: 'profile-check', version: '1' } });
  server.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n');
  const opened = await call('open_binary', { path: target, provider_id: 'ida' });
  assert.notEqual(opened.isError, true);
  const success = await call('procedure_address', { procedure: 'main' });
  assert.notEqual(success.isError, true);
  assert.ok(success.structuredContent.analysis_profile);
  const failure = await call('procedure_pseudo_code', { procedure: 'main' });
  assert.equal(failure.isError, true);
  const error = JSON.parse(failure.content[0].text).error;
  const partial = error.details.partial_observation;
  assert.equal(partial.normalized_result.status, 'partial');
  assert.deepEqual(partial.raw_result.observations.map(row => row.tool), ['lookup_funcs']);
  console.log(JSON.stringify({
    success_profile: success.structuredContent.analysis_profile,
    partial_profile: partial.analysis_profile,
    error_code: error.code,
    failure_has_structured_content: Object.hasOwn(failure, 'structuredContent'),
    completed_tools: partial.raw_result.observations.map(row => row.tool),
    partial_status: partial.normalized_result.status,
    target_binding: partial.normalized_result.target_binding,
    post_operation_identity_check: partial.raw_result.post_operation_identity_check
  }, null, 2));
  const closed = await call('close_binary', {});
  assert.notEqual(closed.isError, true);
  console.log('close_binary: success');
} finally {
  server.stdin.end();
  console.log('server_exit:', JSON.stringify(await exited));
  clearTimeout(deadline);
  await rm(root, { recursive: true, force: true });
}

The successful procedure_address call is the same-session control. The following procedure_pseudo_code call reaches a completed upstream lookup_funcs reply before the simulator deliberately returns an MCP tool error for decompile. The script prints both the successful profile and the profile on the failure's partial Evidence. It asserts admission, the successful lookup, the error envelope, the retained lookup and cleanup; inspect the printed profile values to observe this defect.

Expected behavior

The partial Evidence should carry the already resolved session profile, including its profile digest and registration commitment, just as the successful lookup does. A failed upstream decompile should still produce an error and partial coverage. Preserving the known profile must leave the unverified post-operation target binding explicit. Null remains appropriate when no profile is known.

Impact and scope

An affected partial observation loses known analysis-configuration provenance and the commitment that contributes to semantic identity. The observed scope is a failed headless decompile after a completed lookup; successful Evidence retains its profile, and the failed operation remains an explicit error with partial coverage. No severe harm or incorrect successful analysis was observed.

A consumer might retain the session profile separately, but no caller workflow was verified that restores the missing profile commitment in the partial Evidence itself.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Synthetic x86_64 ELF header for admission; simulated headless IDA database-supervisor; deliberate upstream decompile error.

Interface

MCP

Earliest failure stage

Artifact or evidence registration

Relevant output or evidence

  • Expected source: AGENTS.md, Product and Authority and Architecture and Cleanup, requires preserving Evidence provenance, session semantics, identity and uncertainty across boundaries. docs/mcp-contracts.md:16 makes the analysis profile part of Evidence semantic identity; docs/mcp-contracts.md:390 specifies the immutable selected provider and complete analysis profile on a successful deep open. docs/ida-provider.md, Give REA a target for headless analysis, documents the selected database-supervisor mode. Null is allowed in the Evidence shape, but the same-session successful control establishes that the profile is known in this case.
  • Failure source: The executed production MCP reproduction above returned a concrete successful profile followed by a null profile on the partial observation. Source-derived explanation: src/ida/IdaProvider.ts:184 supplies the resolved profile to the session client; src/ida/IdaSessionClient.ts:334 retains it for successful execution, while the guarded partial-Evidence constructor at src/ida/IdaSessionClient.ts:468 omits it. src/domain/evidence.ts:411 then defaults the omitted value to null.

The contract violation is loss of known provenance, not rejection by a JSON schema. The partial observation retains its completed lookup and pre-operation metadata, so this report does not claim that all lifecycle or identity observations disappear. Source inspection explains the omission; the public MCP response establishes that it reaches the caller.

One local execution reproduced the defect. The successful Evidence contained the headless database-supervisor profile, its registration digest and its profile digest. The failed call returned unreadable_output with no structuredContent; its text diagnostic contained partial Evidence whose analysis_profile was null. Selected fields from the observed output were:

{
  "partial_profile": null,
  "error_code": "unreadable_output",
  "failure_has_structured_content": false,
  "completed_tools": ["lookup_funcs"],
  "partial_status": "partial",
  "target_binding": {
    "status": "not_verified",
    "reason": "IDA MCP tool failed: \nDeliberate fixture failure"
  },
  "post_operation_identity_check": "not_attempted"
}

The session close succeeded, the MCP process exited with code 0, and the reproduction command exited 0. The upstream decompiler error is intentional fixture behavior. The REA defect is the omission of the known profile from the resulting partial Evidence, not failure to decompile. This result does not establish real IDA engine behavior, other compatibility modes, or other failure variants.

Typed outcome or recovery expected

Repeat the same-session successful lookup and deliberate failed decompile. The failure's partial Evidence should contain the same resolved profile and digest as the successful Evidence, including the registration commitment. It should still report unreadable_output, retain only completed upstream observations, keep status: partial, preserve the deliberate failure reason, and keep the post-operation target binding unverified. Separately preserve the legitimate null-profile case when the client has no supplied profile. These are proposed acceptance checks; no fix or restoration test was performed for this report.

Analysis or suspected cause

The source references above explain the observed behavior. Runtime claims are limited to the explicitly described simulation boundaries.

Additional context

Runtime claims above describe the supplied local reproduction at the baseline commit. Real-provider frequency remains unmeasured.

The relevant failure code is unchanged on current origin/main fc712b3750.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3992 ### 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 After an IDA headless session successfully returns Evidence with a concrete analysis profile, a failed decompilation returns partial Evidence with `analysis_profile: null`. The partial record retains the completed function lookup but loses the session's known compatibility, mode and registration commitment. This was observed through REA's production stdio MCP entry point with a simulated database-supervisor provider. ### Reproduction Use the already built baseline checkout and pinned Node.js stated in the REA version and Runtime and platform fields. This reproduction uses no real IDA engine or external service. Save the following as `profile-repro.mjs` in a disposable directory outside the checkout, set `REPRO` to that file, and run `node "$REPRO" "$PWD"` from the checkout root. The script creates its fixture, isolated HOME and provider workspace beside itself, closes the session, and removes those generated resources. ```js import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import { createInterface } from 'node:readline'; import { fileURLToPath } from 'node:url'; const providerSource = ` import { createInterface } from 'node:readline'; const names = ['idb_open', 'idb_list', 'idb_close', 'server_health', 'list_funcs', 'lookup_funcs', 'find_regex', 'decompile', 'disasm', 'callees', 'xrefs_to']; let session, input; for await (const line of createInterface({ input: process.stdin })) { const request = JSON.parse(line); if (request.id === undefined) continue; const reply = result => process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: request.id, result }) + '\\n'); if (request.method === 'initialize') { reply({ protocolVersion: request.params.protocolVersion, capabilities: { tools: {} }, serverInfo: { name: 'profile-fixture', version: '1' } }); continue; } if (request.method === 'tools/list') { reply({ tools: names.map(name => ({ name, inputSchema: { type: 'object' } })) }); continue; } const { name, arguments: args } = request.params; let value; switch (name) { case 'idb_open': session = args.preferred_session_id; input = args.input_path; value = { success: true, session: { session_id: session, input_path: input } }; break; case 'idb_list': value = { sessions: session ? [{ session_id: session, input_path: input, backend: 'worker', owned: true, is_active: true }] : [] }; break; case 'idb_close': value = { success: true, session_id: session, owned: true, backend: 'worker', saved: false }; session = undefined; break; case 'server_health': value = { status: 'ok', input_path: input, idb_path: input + '.i64', module: 'input.elf', imagebase: '0x1000', auto_analysis_ready: true }; break; case 'lookup_funcs': value = args.queries.map(query => ({ query, fn: { addr: '0x1000', name: 'main', size: '0x20' }, error: null })); break; case 'decompile': reply({ isError: true, content: [{ type: 'text', text: 'Deliberate fixture failure' }] }); continue; default: throw new Error('Unexpected upstream tool: ' + name); } reply({ content: [{ type: 'text', text: JSON.stringify(value) }] }); } `; const root = await mkdtemp(join(dirname(fileURLToPath(import.meta.url)), 'profile-case-')); const repo = resolve(process.argv[2]); const home = join(root, 'home'); await mkdir(home); const target = join(root, 'input.elf'); const bytes = Buffer.alloc(64); bytes.set([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1]); bytes.writeUInt16LE(62, 18); bytes.writeUInt32LE(1, 20); bytes.writeUInt16LE(64, 52); await writeFile(target, bytes); const provider = join(root, 'provider.mjs'); const config = join(root, 'provider.json'); await writeFile(provider, providerSource); await writeFile(config, JSON.stringify({ command: process.execPath, args: [provider], mode: 'headless', timeoutMs: 3000, workspaceRoot: root })); const server = spawn(process.execPath, [join(repo, 'scripts/rea.mjs'), 'mcp'], { cwd: repo, stdio: ['pipe', 'pipe', 'inherit'], env: { ...process.env, HOME: home, REA_ANALYSIS_PROVIDER: 'ida', REA_IDA_MCP_CONFIG: config, REA_MCP_INPUT_SCHEMA_PROFILE: 'compact', REA_MCP_OUTPUT_SCHEMA_PROFILE: 'off', REA_LOG_LEVEL: 'silent' } }); const exited = new Promise(resolve => server.once('exit', (code, signal) => resolve({ code, signal }))); const pending = new Map(); let sequence = 0; createInterface({ input: server.stdout }).on('line', line => { const response = JSON.parse(line); pending.get(response.id)?.(response); pending.delete(response.id); }); const request = (method, params) => new Promise(resolve => { const id = ++sequence; pending.set(id, resolve); server.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n'); }); const call = async (name, args) => { const response = await request('tools/call', { name, arguments: args }); assert.equal(response.error, undefined); return response.result; }; const deadline = setTimeout(() => server.kill('SIGTERM'), 20000); try { await request('initialize', { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'profile-check', version: '1' } }); server.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n'); const opened = await call('open_binary', { path: target, provider_id: 'ida' }); assert.notEqual(opened.isError, true); const success = await call('procedure_address', { procedure: 'main' }); assert.notEqual(success.isError, true); assert.ok(success.structuredContent.analysis_profile); const failure = await call('procedure_pseudo_code', { procedure: 'main' }); assert.equal(failure.isError, true); const error = JSON.parse(failure.content[0].text).error; const partial = error.details.partial_observation; assert.equal(partial.normalized_result.status, 'partial'); assert.deepEqual(partial.raw_result.observations.map(row => row.tool), ['lookup_funcs']); console.log(JSON.stringify({ success_profile: success.structuredContent.analysis_profile, partial_profile: partial.analysis_profile, error_code: error.code, failure_has_structured_content: Object.hasOwn(failure, 'structuredContent'), completed_tools: partial.raw_result.observations.map(row => row.tool), partial_status: partial.normalized_result.status, target_binding: partial.normalized_result.target_binding, post_operation_identity_check: partial.raw_result.post_operation_identity_check }, null, 2)); const closed = await call('close_binary', {}); assert.notEqual(closed.isError, true); console.log('close_binary: success'); } finally { server.stdin.end(); console.log('server_exit:', JSON.stringify(await exited)); clearTimeout(deadline); await rm(root, { recursive: true, force: true }); } ``` The successful `procedure_address` call is the same-session control. The following `procedure_pseudo_code` call reaches a completed upstream `lookup_funcs` reply before the simulator deliberately returns an MCP tool error for `decompile`. The script prints both the successful profile and the profile on the failure's partial Evidence. It asserts admission, the successful lookup, the error envelope, the retained lookup and cleanup; inspect the printed profile values to observe this defect. ### Expected behavior The partial Evidence should carry the already resolved session profile, including its profile digest and registration commitment, just as the successful lookup does. A failed upstream decompile should still produce an error and partial coverage. Preserving the known profile must leave the unverified post-operation target binding explicit. Null remains appropriate when no profile is known. ### Impact and scope An affected partial observation loses known analysis-configuration provenance and the commitment that contributes to semantic identity. The observed scope is a failed headless decompile after a completed lookup; successful Evidence retains its profile, and the failed operation remains an explicit error with partial coverage. No severe harm or incorrect successful analysis was observed. A consumer might retain the session profile separately, but no caller workflow was verified that restores the missing profile commitment in the partial Evidence itself. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Synthetic x86_64 ELF header for admission; simulated headless IDA database-supervisor; deliberate upstream decompile error. ### Interface MCP ### Earliest failure stage Artifact or evidence registration ### Relevant output or evidence - Expected source: `AGENTS.md`, Product and Authority and Architecture and Cleanup, requires preserving Evidence provenance, session semantics, identity and uncertainty across boundaries. `docs/mcp-contracts.md:16` makes the analysis profile part of Evidence semantic identity; `docs/mcp-contracts.md:390` specifies the immutable selected provider and complete analysis profile on a successful deep open. `docs/ida-provider.md`, Give REA a target for headless analysis, documents the selected database-supervisor mode. Null is allowed in the Evidence shape, but the same-session successful control establishes that the profile is known in this case. - Failure source: The executed production MCP reproduction above returned a concrete successful profile followed by a null profile on the partial observation. Source-derived explanation: `src/ida/IdaProvider.ts:184` supplies the resolved profile to the session client; `src/ida/IdaSessionClient.ts:334` retains it for successful execution, while the guarded partial-Evidence constructor at `src/ida/IdaSessionClient.ts:468` omits it. `src/domain/evidence.ts:411` then defaults the omitted value to null. The contract violation is loss of known provenance, not rejection by a JSON schema. The partial observation retains its completed lookup and pre-operation metadata, so this report does not claim that all lifecycle or identity observations disappear. Source inspection explains the omission; the public MCP response establishes that it reaches the caller. One local execution reproduced the defect. The successful Evidence contained the headless database-supervisor profile, its registration digest and its profile digest. The failed call returned `unreadable_output` with no `structuredContent`; its text diagnostic contained partial Evidence whose `analysis_profile` was null. Selected fields from the observed output were: ```json { "partial_profile": null, "error_code": "unreadable_output", "failure_has_structured_content": false, "completed_tools": ["lookup_funcs"], "partial_status": "partial", "target_binding": { "status": "not_verified", "reason": "IDA MCP tool failed: \nDeliberate fixture failure" }, "post_operation_identity_check": "not_attempted" } ``` The session close succeeded, the MCP process exited with code 0, and the reproduction command exited 0. The upstream decompiler error is intentional fixture behavior. The REA defect is the omission of the known profile from the resulting partial Evidence, not failure to decompile. This result does not establish real IDA engine behavior, other compatibility modes, or other failure variants. ### Typed outcome or recovery expected Repeat the same-session successful lookup and deliberate failed decompile. The failure's partial Evidence should contain the same resolved profile and digest as the successful Evidence, including the registration commitment. It should still report `unreadable_output`, retain only completed upstream observations, keep `status: partial`, preserve the deliberate failure reason, and keep the post-operation target binding unverified. Separately preserve the legitimate null-profile case when the client has no supplied profile. These are proposed acceptance checks; no fix or restoration test was performed for this report. ### Analysis or suspected cause The source references above explain the observed behavior. Runtime claims are limited to the explicitly described simulation boundaries. ### Additional context Runtime claims above describe the supplied local reproduction at the baseline commit. Real-provider frequency remains unmeasured. The relevant failure code is unchanged on current `origin/main` fc712b3750cbe16755c4f78c1f90fdf09fbaca31.
Author
Owner

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

Agreed: the analysis_profile is a session-level fact already established by the successful prior call, so nulling it in the failed decompilation partial Evidence erases known context and makes the partial record less useful for reconstruction. Keeping the known compatibility/mode/registration while marking the decompilation itself failed is more faithful to what was observed.

<!-- gh-comment-id:6186798694 --> @XinwangTao commented on GitHub (Oct 15, 2026): Agreed: the analysis_profile is a session-level fact already established by the successful prior call, so nulling it in the failed decompilation partial Evidence erases known context and makes the partial record less useful for reconstruction. Keeping the known compatibility/mode/registration while marking the decompilation itself failed is more faithful to what was observed.
Author
Owner

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

Working on this. Reproduced with your stdio probe: the successful lookup carried the headless profile while the partial Evidence of the failed decompile had analysis_profile: null. The session client's partial Evidence constructor now passes the same resolved profile commitment the successful path uses (null stays when no profile was supplied). The probe reports equal profile digests on both records with unreadable_output, the retained lookup_funcs reply, status: partial and the unverified target binding unchanged. Draft PR follows shortly.

<!-- gh-comment-id:6191585829 --> @yankikucuk commented on GitHub (Oct 15, 2026): Working on this. Reproduced with your stdio probe: the successful lookup carried the headless profile while the partial Evidence of the failed decompile had `analysis_profile: null`. The session client's partial Evidence constructor now passes the same resolved profile commitment the successful path uses (null stays when no profile was supplied). The probe reports equal profile digests on both records with `unreadable_output`, the retained `lookup_funcs` reply, `status: partial` and the unverified target binding unchanged. Draft PR follows shortly.
Author
Owner

@XinwangTao commented on GitHub (Oct 16, 2026):

yankikucuk, the fix matches the observation — the partial Evidence should carry the same resolved profile commitment as the successful path, and keeping null only when no profile was supplied is the right default. Verifying equal profile digests across both records closes the regression loop nicely. Will review the draft.

<!-- gh-comment-id:6208640717 --> @XinwangTao commented on GitHub (Oct 16, 2026): yankikucuk, the fix matches the observation — the partial Evidence should carry the same resolved profile commitment as the successful path, and keeping null only when no profile was supplied is the right default. Verifying equal profile digests across both records closes the regression loop nicely. Will review the draft.
Author
Owner

@XinwangTao commented on GitHub (Oct 22, 2026):

Glad to see #4645 merged and the issue closed. The partial Evidence now retains the resolved profile commitment with the same digest as the successful path, while null is reserved for the genuinely profile-less case — that keeps the reconstruction provenance intact without masking the failed decompilation. The related late-cancelled variant in #5109 also looks like the same class of defect; worth confirming it follows the same constructor path.

<!-- gh-comment-id:6330252015 --> @XinwangTao commented on GitHub (Oct 22, 2026): Glad to see #4645 merged and the issue closed. The partial Evidence now retains the resolved profile commitment with the same digest as the successful path, while null is reserved for the genuinely profile-less case — that keeps the reconstruction provenance intact without masking the failed decompilation. The related late-cancelled variant in #5109 also looks like the same class of defect; worth confirming it follows the same constructor path.
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#1586
No description provided.