[GH-ISSUE #7562] [Bug] Inline reconstruction requests report an intact Evidence record as corrupt bundle content #4503

Closed
opened 2026-10-31 00:50:20 +00:00 by bsakaguchi · 0 comments
Owner

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

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

node scripts/rea.mjs build-reconstruction-obligation-ledger ledger.json --json returns evidence_integrity_mismatch / integrity_mismatch when ledger.json supplies an authentic standalone Evidence record in evidence_bundle. Its message is a 1,920-character JSON schema-error array, repeated in remediation.action and details.reason. It does not explain that a bundle was expected instead of a record.

node scripts/rea.mjs evaluate-reconstruction-coverage coverage.json --json produces the same classification and dump when the workspace supplies that record in its bundle field. Reading the exact same record through evidence-import record.json --json correctly returns invalid_request / invalid_input, details.reason: "wrong-kind", and guidance to export a session bundle. The intact record is rejected in all three cases; this report concerns the inline routes' incorrect failure category and lost guidance.

Reproduction

  1. Start in the prepared baseline checkout root, with its locked dependencies and built runtime available, and Node.js 24.22.1 on PATH. No analysis engine is needed.
  2. Run the following block. It generates its fixture using a supported CLI producer, redirects HOME and XDG directories for every REA invocation, exercises each inline route twice, and compares the file-reader behavior. The coverage frame has all required arrays; its absent boundary is checked only after bundle admission. The successful ledger control uses the documented empty bundle.
python3 - <<'PYREPLAY'
import json, os, pathlib, subprocess, tempfile
repo = pathlib.Path.cwd()
with tempfile.TemporaryDirectory() as directory:
    work = pathlib.Path(directory)
    env = dict(os.environ, REA_LOG_LEVEL='silent', NODE_DISABLE_COMPILE_CACHE='1')
    for key in ('HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME'):
        folder = work / key.lower()
        folder.mkdir()
        env[key] = str(folder)
    def run(*args):
        result = subprocess.run(['node', str(repo / 'scripts/rea.mjs'), *args],
            cwd=work, env=env, capture_output=True, text=True, timeout=90)
        assert not result.stderr, result.stderr
        return result.returncode, json.loads(result.stdout)
    (work / 'sample.bin').write_bytes(b'sample bytes')
    status, record = run('inspect-byte-entropy', 'sample.bin', '--json')
    assert status == 0
    (work / 'record.json').write_text(json.dumps(record))
    ledger = {'evidence_bundle': record, 'reviewed_obligations': [],
              'manifest': {'bindings': [], 'contradictions': []}}
    coverage = {key: [] for key in ('artifacts', 'surfaces', 'owners', 'claims',
        'verifier_contracts', 'verifier_results', 'residual_unknown_ids',
        'contradictions', 'package_proofs', 'boundaries')}
    coverage['evidence_bundle'] = record
    requests = [
        ('ledger', 'build-reconstruction-obligation-ledger', ledger),
        ('coverage', 'evaluate-reconstruction-coverage',
         {'coverage': coverage, 'boundary_id': 'b'})]
    for label, command, request in requests:
        (work / (label + '.json')).write_text(json.dumps(request))
        for attempt in (1, 2):
            status, error = run(command, label + '.json', '--json')
            assert status == 1
            assert error['code'] == 'evidence_integrity_mismatch'
            assert error['category'] == 'integrity_mismatch'
            message = error['message']
            assert len(message) == 1920
            assert message == error['remediation']['action'] == error['details']['reason']
            assert 'recovery' not in error
            print(label, attempt, 'exit=1', error['code'], 'message_length=' + str(len(message)))
    status, error = run('evidence-import', 'record.json', '--json')
    assert status == 1 and error['code'] == 'invalid_request'
    assert error['category'] == 'invalid_input' and error['details']['reason'] == 'wrong-kind'
    assert record['evidence_id'] in error['message']
    assert 'one Evidence record' in error['message']
    print('file-control exit=1 invalid_request invalid_input wrong-kind')
    ledger['evidence_bundle'] = {key: [] for key in (
        'artifacts', 'providers', 'environments', 'scenarios', 'captures', 'unknowns', 'records')}
    (work / 'empty-ledger.json').write_text(json.dumps(ledger))
    status, result = run('build-reconstruction-obligation-ledger', 'empty-ledger.json', '--json')
    assert status == 0 and result['normalized_result']['status'] == 'unknown'
    print('empty-bundle ledger-control exit=0 status=unknown')
PYREPLAY
  1. The exact reduced block above produced:
ledger 1 exit=1 evidence_integrity_mismatch message_length=1920
ledger 2 exit=1 evidence_integrity_mismatch message_length=1920
coverage 1 exit=1 evidence_integrity_mismatch message_length=1920
coverage 2 exit=1 evidence_integrity_mismatch message_length=1920
file-control exit=1 invalid_request invalid_input wrong-kind
empty-bundle ledger-control exit=0 status=unknown

Each inline case also reproduced in two earlier invocations with the same request shape: four failures in four valid-envelope attempts per route during this verification. An initial coverage fixture with incorrect workspace fields was corrected before these counts; its schema refusal is not counted as this defect.

Expected behavior

An authentic Evidence record supplied where a bundle is required should produce an input-kind refusal that identifies the mistake and explains how to supply a proper bundle. Preserve integrity failures for malformed or tampered content.

The ledger documentation explicitly accepts an inline bundle and tells callers to replace the empty example with an exported session bundle. The coverage documentation likewise requires an inline workspace with authenticated Evidence. The repository's boundary invariant requires meaningful failure reasons and consumers to preserve their owner's parsed facts. The Evidence owner already distinguishes an authenticated standalone record from an invalid bundle, and the file-reader control demonstrates the intended classification for this exact record. Source locators are in Evidence below.

Impact and scope

P2: the observed machine-readable category incorrectly identifies an input-kind mistake as an integrity failure, and the recovery text contains validator internals instead of the existing bundle-export guidance. A caller branching on that category receives the wrong recovery signal; no actual downstream retry loop was tested.

The failure is bounded to the tested wrong-kind inputs. The valid empty ledger request succeeded. In an additional control, wrapping the same record with the built bundle constructor allowed the ledger to succeed with an unknown result; supplying a proper bundle is a practical correction that preserves the record. No data loss, modified Evidence, acceptance of invalid input, or target execution was observed. MCP runtime behavior and other platforms were not executed. Coverage success is not claimed: replacing its record with an empty bundle reached the later, accurate unknown-boundary refusal.

REA version

Exact node scripts/rea.mjs --version output: 6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1, arm64. Prepared source-checkout runtime; no installation or rebuild during verification.

Target and artifact context

Synthetic 12-byte file containing sample bytes, inspected without executing it. Its authentic inspect_byte_entropy Evidence record is reused unchanged. No Hopper, Ghidra, GUI application, or external analysis provider is required.

Interface

CLI

Earliest failure stage

Request parsing or validation

Relevant output or evidence

The reduced replay asserts the actual output fields before printing its summary. Both inline commands returned code: "evidence_integrity_mismatch", category: "integrity_mismatch", and retryable: false. Decoding message yields schema issues for the seven absent bundle arrays and unrecognized standalone-record fields. The full 1,920-character message equals both remediation.action and details.reason; no recovery field is present. The file control returned invalid_request / invalid_input with wrong-kind, the record ID in its message, and export-a-bundle guidance.

Source at the baseline:

  • docs/reconstruction-obligation-ledgers.md:29-59 documents the complete inline request and exported-bundle replacement; docs/reconstruction-coverage.md:3-11 documents the coverage input and authentication requirement.
  • AGENTS.md, Architecture and Cleanup, requires meaningful failure reasons and consumers using their owner's parsed facts.
  • src/domain/evidenceBundle.ts:244-273 authenticates the standalone record and produces kind: "single-record" plus specific guidance. src/application/EvidenceBundleFiles.ts:135-149 preserves that distinction for file reads.
  • src/domain/evidenceBundle.ts:303-315 instead discards the owner's kind and guidance when building the inline reader's integrity error, forwarding the schema exception's message.
  • src/application/ReconstructionObligationLedgerRequest.ts:19-41 and src/domain/reconstructionCoverage.ts:213-238 accept the bundle field as unknown and reach that inline reader after request-frame validation. These are supported request paths, rather than test-only code.

Typed outcome or recovery expected

Return invalid_request / invalid_input for an authenticated standalone record, with useful bundle-kind and export guidance. An input issue identifying evidence_bundle would also help, but no particular issue layout, record-ID details field, or replacement sentence is required by this report. Keep actual integrity failures distinct.

Analysis or suspected cause

Source-derived: the safe bundle parser returns both a classified failure and its cause. The domain inline reader uses only the cause and wraps every expected parser failure as EvidenceIntegrityError. This loses the single-record result before either reconstruction workflow can project it. The file reader consumes that result correctly. No fix was applied or tested.

Additional context

Closed #2050 established the same intact-record distinction for file readers. Its closing comment and superseded PR #2051 verify evidence-import after commit d8c76b629639bda4405afa9ae2f7676b694f2f10. That file-route repair works in this verification, while the inline reconstruction requests still fail. This is a remaining route gap after the claimed fix, rather than a request to reopen its already-correct file readers.

Git history shows the domain inline helper was added later in 1d5357aaad5d341d738a65c7d1f5633b966aedf8, a descendant of the #2050 fix. No historical runtime bisection or last-known-good result for these same inline commands is claimed.

Merged PR #1073 introduced specific owned bundle diagnostics; restoring only a generic integrity sentence would still lose the existing wrong-kind guidance. #7330 and PR #7358 concern canonical record ordering, #7443 concerns empty file arguments, and closed #7444 concerns directory overwrite advice. Their inputs and restoration conditions differ. The searches also checked the snapshot, BOM, missing-reference, and function-Evidence diagnostic families; no report covering this inline reconstruction gap was found.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7562 ### 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 `node scripts/rea.mjs build-reconstruction-obligation-ledger ledger.json --json` returns `evidence_integrity_mismatch` / `integrity_mismatch` when `ledger.json` supplies an authentic standalone Evidence record in `evidence_bundle`. Its message is a 1,920-character JSON schema-error array, repeated in `remediation.action` and `details.reason`. It does not explain that a bundle was expected instead of a record. `node scripts/rea.mjs evaluate-reconstruction-coverage coverage.json --json` produces the same classification and dump when the workspace supplies that record in its bundle field. Reading the exact same record through `evidence-import record.json --json` correctly returns `invalid_request` / `invalid_input`, `details.reason: "wrong-kind"`, and guidance to export a session bundle. The intact record is rejected in all three cases; this report concerns the inline routes' incorrect failure category and lost guidance. ### Reproduction 1. Start in the prepared baseline checkout root, with its locked dependencies and built runtime available, and Node.js 24.22.1 on PATH. No analysis engine is needed. 2. Run the following block. It generates its fixture using a supported CLI producer, redirects HOME and XDG directories for every REA invocation, exercises each inline route twice, and compares the file-reader behavior. The coverage frame has all required arrays; its absent boundary is checked only after bundle admission. The successful ledger control uses the documented empty bundle. ```sh python3 - <<'PYREPLAY' import json, os, pathlib, subprocess, tempfile repo = pathlib.Path.cwd() with tempfile.TemporaryDirectory() as directory: work = pathlib.Path(directory) env = dict(os.environ, REA_LOG_LEVEL='silent', NODE_DISABLE_COMPILE_CACHE='1') for key in ('HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME'): folder = work / key.lower() folder.mkdir() env[key] = str(folder) def run(*args): result = subprocess.run(['node', str(repo / 'scripts/rea.mjs'), *args], cwd=work, env=env, capture_output=True, text=True, timeout=90) assert not result.stderr, result.stderr return result.returncode, json.loads(result.stdout) (work / 'sample.bin').write_bytes(b'sample bytes') status, record = run('inspect-byte-entropy', 'sample.bin', '--json') assert status == 0 (work / 'record.json').write_text(json.dumps(record)) ledger = {'evidence_bundle': record, 'reviewed_obligations': [], 'manifest': {'bindings': [], 'contradictions': []}} coverage = {key: [] for key in ('artifacts', 'surfaces', 'owners', 'claims', 'verifier_contracts', 'verifier_results', 'residual_unknown_ids', 'contradictions', 'package_proofs', 'boundaries')} coverage['evidence_bundle'] = record requests = [ ('ledger', 'build-reconstruction-obligation-ledger', ledger), ('coverage', 'evaluate-reconstruction-coverage', {'coverage': coverage, 'boundary_id': 'b'})] for label, command, request in requests: (work / (label + '.json')).write_text(json.dumps(request)) for attempt in (1, 2): status, error = run(command, label + '.json', '--json') assert status == 1 assert error['code'] == 'evidence_integrity_mismatch' assert error['category'] == 'integrity_mismatch' message = error['message'] assert len(message) == 1920 assert message == error['remediation']['action'] == error['details']['reason'] assert 'recovery' not in error print(label, attempt, 'exit=1', error['code'], 'message_length=' + str(len(message))) status, error = run('evidence-import', 'record.json', '--json') assert status == 1 and error['code'] == 'invalid_request' assert error['category'] == 'invalid_input' and error['details']['reason'] == 'wrong-kind' assert record['evidence_id'] in error['message'] assert 'one Evidence record' in error['message'] print('file-control exit=1 invalid_request invalid_input wrong-kind') ledger['evidence_bundle'] = {key: [] for key in ( 'artifacts', 'providers', 'environments', 'scenarios', 'captures', 'unknowns', 'records')} (work / 'empty-ledger.json').write_text(json.dumps(ledger)) status, result = run('build-reconstruction-obligation-ledger', 'empty-ledger.json', '--json') assert status == 0 and result['normalized_result']['status'] == 'unknown' print('empty-bundle ledger-control exit=0 status=unknown') PYREPLAY ``` 3. The exact reduced block above produced: ```text ledger 1 exit=1 evidence_integrity_mismatch message_length=1920 ledger 2 exit=1 evidence_integrity_mismatch message_length=1920 coverage 1 exit=1 evidence_integrity_mismatch message_length=1920 coverage 2 exit=1 evidence_integrity_mismatch message_length=1920 file-control exit=1 invalid_request invalid_input wrong-kind empty-bundle ledger-control exit=0 status=unknown ``` Each inline case also reproduced in two earlier invocations with the same request shape: four failures in four valid-envelope attempts per route during this verification. An initial coverage fixture with incorrect workspace fields was corrected before these counts; its schema refusal is not counted as this defect. ### Expected behavior An authentic Evidence record supplied where a bundle is required should produce an input-kind refusal that identifies the mistake and explains how to supply a proper bundle. Preserve integrity failures for malformed or tampered content. The ledger documentation explicitly accepts an inline bundle and tells callers to replace the empty example with an exported session bundle. The coverage documentation likewise requires an inline workspace with authenticated Evidence. The repository's boundary invariant requires meaningful failure reasons and consumers to preserve their owner's parsed facts. The Evidence owner already distinguishes an authenticated standalone record from an invalid bundle, and the file-reader control demonstrates the intended classification for this exact record. Source locators are in Evidence below. ### Impact and scope P2: the observed machine-readable category incorrectly identifies an input-kind mistake as an integrity failure, and the recovery text contains validator internals instead of the existing bundle-export guidance. A caller branching on that category receives the wrong recovery signal; no actual downstream retry loop was tested. The failure is bounded to the tested wrong-kind inputs. The valid empty ledger request succeeded. In an additional control, wrapping the same record with the built bundle constructor allowed the ledger to succeed with an `unknown` result; supplying a proper bundle is a practical correction that preserves the record. No data loss, modified Evidence, acceptance of invalid input, or target execution was observed. MCP runtime behavior and other platforms were not executed. Coverage success is not claimed: replacing its record with an empty bundle reached the later, accurate unknown-boundary refusal. ### REA version Exact `node scripts/rea.mjs --version` output: `6.8.0` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js v24.22.1; macOS 27.0.1, arm64. Prepared source-checkout runtime; no installation or rebuild during verification. ### Target and artifact context Synthetic 12-byte file containing `sample bytes`, inspected without executing it. Its authentic `inspect_byte_entropy` Evidence record is reused unchanged. No Hopper, Ghidra, GUI application, or external analysis provider is required. ### Interface CLI ### Earliest failure stage Request parsing or validation ### Relevant output or evidence The reduced replay asserts the actual output fields before printing its summary. Both inline commands returned `code: "evidence_integrity_mismatch"`, `category: "integrity_mismatch"`, and `retryable: false`. Decoding `message` yields schema issues for the seven absent bundle arrays and unrecognized standalone-record fields. The full 1,920-character message equals both `remediation.action` and `details.reason`; no `recovery` field is present. The file control returned `invalid_request` / `invalid_input` with `wrong-kind`, the record ID in its message, and export-a-bundle guidance. Source at the baseline: - `docs/reconstruction-obligation-ledgers.md:29-59` documents the complete inline request and exported-bundle replacement; `docs/reconstruction-coverage.md:3-11` documents the coverage input and authentication requirement. - `AGENTS.md`, Architecture and Cleanup, requires meaningful failure reasons and consumers using their owner's parsed facts. - `src/domain/evidenceBundle.ts:244-273` authenticates the standalone record and produces `kind: "single-record"` plus specific guidance. `src/application/EvidenceBundleFiles.ts:135-149` preserves that distinction for file reads. - `src/domain/evidenceBundle.ts:303-315` instead discards the owner's kind and guidance when building the inline reader's integrity error, forwarding the schema exception's message. - `src/application/ReconstructionObligationLedgerRequest.ts:19-41` and `src/domain/reconstructionCoverage.ts:213-238` accept the bundle field as unknown and reach that inline reader after request-frame validation. These are supported request paths, rather than test-only code. ### Typed outcome or recovery expected Return `invalid_request` / `invalid_input` for an authenticated standalone record, with useful bundle-kind and export guidance. An input issue identifying `evidence_bundle` would also help, but no particular issue layout, record-ID details field, or replacement sentence is required by this report. Keep actual integrity failures distinct. ### Analysis or suspected cause Source-derived: the safe bundle parser returns both a classified failure and its cause. The domain inline reader uses only the cause and wraps every expected parser failure as `EvidenceIntegrityError`. This loses the `single-record` result before either reconstruction workflow can project it. The file reader consumes that result correctly. No fix was applied or tested. ### Additional context Closed [#2050](https://github.com/morluto/rea/issues/2050) established the same intact-record distinction for file readers. Its closing comment and superseded [PR #2051](https://github.com/morluto/rea/pull/2051) verify `evidence-import` after commit `d8c76b629639bda4405afa9ae2f7676b694f2f10`. That file-route repair works in this verification, while the inline reconstruction requests still fail. This is a remaining route gap after the claimed fix, rather than a request to reopen its already-correct file readers. Git history shows the domain inline helper was added later in `1d5357aaad5d341d738a65c7d1f5633b966aedf8`, a descendant of the #2050 fix. No historical runtime bisection or last-known-good result for these same inline commands is claimed. Merged [PR #1073](https://github.com/morluto/rea/pull/1073) introduced specific owned bundle diagnostics; restoring only a generic integrity sentence would still lose the existing wrong-kind guidance. [#7330](https://github.com/morluto/rea/issues/7330) and [PR #7358](https://github.com/morluto/rea/pull/7358) concern canonical record ordering, [#7443](https://github.com/morluto/rea/issues/7443) concerns empty file arguments, and closed [#7444](https://github.com/morluto/rea/issues/7444) concerns directory overwrite advice. Their inputs and restoration conditions differ. The searches also checked the snapshot, BOM, missing-reference, and function-Evidence diagnostic families; no report covering this inline reconstruction gap was found.
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#4503
No description provided.