[GH-ISSUE #7612] [Bug] Devicetree retained-reference example spells the Evidence prefix as ev: #4542

Open
opened 2026-10-31 00:50:37 +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/7612

Before submitting

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

Area

Documentation

Observed behavior

The devicetree guide illustrates a retained-reference request with "evidence_id": "ev:<digest>". Replacing only <digest> with the digest of a real retained devicetree inspection makes the production MCP inspect_analysis_view tool reject the request: invalid_request, issue path ["source","evidence_id"], reason invalid_format.

In the same MCP connection, passing the original parent_evidence_id returned by inspect_devicetree succeeds and returns the requested property page. That identifier starts with ev_. The preceding prose correctly tells readers to follow the returned ID; the conflicting literal colon is in the illustrative request shape.

The calls tested were inspect_devicetree with {path, detail: "summary"}, followed by inspect_analysis_view with {source: {kind: "retained-evidence", evidence_id}, view: {kind: "devicetree", collection: "properties", path: "/serial@1000", offset: 0, limit: 32}}. The reproduction below supplies the fixture and both exact identifier variants.

Reproduction

  1. From the already-built source baseline below, use Node.js 24.22.1 and the locked dependencies. Run this script. It creates an FDT v17 blob with one serial-node property, starts the production MCP entry point, compares both prefixes against the same retained digest, and deletes its fixture and isolated environment afterward.
node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import { mkdtemp, writeFile, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';

const root = await mkdtemp(join(tmpdir(), 'rea-doc-prefix-'));
const env = { PATH: process.env.PATH, NO_COLOR: '1' };
for (const key of ['HOME', 'DSH_HOME', 'XDG_CONFIG_HOME',
  'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME', 'TMPDIR']) {
  env[key] = root;
}
const cells = (...values) => {
  const b = Buffer.alloc(values.length * 4);
  values.forEach((value, i) => b.writeUInt32BE(value, i * 4));
  return b;
};
const name = value => {
  const b = Buffer.from(value + String.fromCharCode(0));
  return Buffer.concat([b, Buffer.alloc((4 - b.length % 4) % 4)]);
};
const body = Buffer.concat([
  cells(1), name(''), cells(1), name('serial@1000'),
  cells(3, 4, 0), cells(48000000), cells(2, 2, 9),
]);
const strings = Buffer.from('clock-frequency' + String.fromCharCode(0));
const blob = Buffer.concat([
  cells(0xd00dfeed, 56 + body.length + strings.length, 56,
    56 + body.length, 40, 17, 16, 0, strings.length, body.length),
  Buffer.alloc(16), body, strings,
]);
const path = join(root, 'board.dtb');
const client = new Client({ name: 'devicetree-doc-repro', version: '1' });
const transport = new StdioClientTransport({
  command: process.execPath, args: ['scripts/rea.mjs', 'mcp'],
  env, stderr: 'pipe',
});
transport.stderr?.resume();
try {
  await writeFile(path, blob);
  await client.connect(transport);
  const inspected = await client.callTool({
    name: 'inspect_devicetree', arguments: { path, detail: 'summary' },
  });
  assert.notEqual(inspected.isError, true, JSON.stringify(inspected));
  const id = inspected.structuredContent.normalized_result.parent_evidence_id;
  assert.match(id, /^ev_[a-f0-9]{64}$/);
  console.log(JSON.stringify({ parent_evidence_id: id }));
  const view = { kind: 'devicetree', collection: 'properties',
    path: '/serial@1000', offset: 0, limit: 32 };
  for (const [label, evidence_id] of [
    ['example prefix', id.replace(/^ev_/, 'ev:')], ['returned ID', id],
  ]) {
    const reply = await client.callTool({
      name: 'inspect_analysis_view',
      arguments: { source: { kind: 'retained-evidence', evidence_id }, view },
    });
    if (label === 'example prefix') {
      assert.equal(reply.isError, true);
      const error = JSON.parse(reply.content.find(c => c.type === 'text').text).error;
      assert.equal(error.code, 'invalid_request');
      assert.deepEqual(error.details.issues[0].path, ['source', 'evidence_id']);
      console.log(JSON.stringify({ label, code: error.code,
        issues: error.details.issues }));
    } else {
      assert.notEqual(reply.isError, true, JSON.stringify(reply));
      const items = reply.structuredContent.normalized_result.items;
      assert.equal(items[0].name, 'clock-frequency');
      console.log(JSON.stringify({ label, success: true, items }));
    }
  }
} finally {
  try { await client.close(); await transport.close(); }
  finally { await rm(root, { recursive: true, force: true }); }
}
JS
  1. The example prefix result reports invalid_format for source.evidence_id. The returned ID result succeeds and contains clock-frequency, with hex: "02dc6c00".

The production MCP comparison reproduced in both independent executions, including a replay of this exact script. A separate compiled CLI comparison also reproduced the format rejection once; changing its fabricated ID to ev_ advanced to the expected missing-record error.

Expected behavior

The retained-reference illustration should preserve the actual identifier spelling when the reader fills its digest placeholder. Use ev_<digest>, or show a placeholder for the complete returned parent_evidence_id. Passing the complete returned ID unchanged already works; the schema does not need to accept colon-prefixed IDs.

Other retained-reference guides use underscore-prefixed digest placeholders. The canonical retained-reference contract requires ev_ followed by 64 lowercase hexadecimal characters. Supporting source locations are listed in the evidence section.

Impact and scope

Following the example's literal prefix while substituting a valid retained digest prevents admission of that page request. The demonstrated recovery is to use the producer-returned parent_evidence_id unchanged; it returns the requested property from the same retained record and session. Rejection happens during input validation. No corruption, data loss or security boundary crossing was observed.

The example is illustrative: callers must replace its digest placeholder, and callers who follow the preceding prose by copying the entire returned ID avoid the failure. How often users or agents instead construct the colon-prefixed form is unknown. The observation concerns this one documentation example, rather than a general failure of devicetree inspection or retained paging.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A request assembled with the example's literal prefix and a real retained digest is rejected before the requested page can be returned.
  • Workaround status: available
  • Workaround basis: Reusing the complete producer-returned ID succeeded on the same fixture, digest and MCP connection.

REA version

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

6.8.0

Source baseline: 65f1c447344a423fa2785847dbcea1e9a9e2454a.

Runtime and platform

Node.js v24.22.1; macOS 27.0.1; Darwin arm64. The production stdio client used the locked @modelcontextprotocol/client 2.3.1 dependency. No native analysis provider or GUI was used.

Target and artifact context

An inline-generated 124-byte FDT v17 fixture containing the root, /serial@1000, and a four-byte clock-frequency property. It was inspected and retained through the production MCP tool in the same connection as both page requests.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

The exact reproduction replay returned the following parent ID (the ID can vary with the generated fixture path):

ev_05b39f25f623abb1cc6ad11515a581d53b9a3c3d2488a5d1563479160b911b96

Changing only its underscore to a colon produced this selected diagnostic:

{"label":"example prefix","code":"invalid_request","issues":[{"path":["source","evidence_id"],"reason":"invalid_format","message":"Expected a ev_ prefixed lowercase sha256 digest","expected":"regex"}]}

The exact original ID produced success: true and a property row with name: "clock-frequency", offset: 92, bytes: 4, hex: "02dc6c00", and source_index: 0. These outputs come from the same fixture and connection in the script above.

Static evidence at the stated baseline:

  • docs/devicetree.md:39-52 instructs callers to follow parent_evidence_id but illustrates ev:<digest>.
  • src/domain/evidenceReference.ts:4-8 binds the retained-reference ID to the ev prefixed-digest contract.
  • src/domain/digests.ts:21-33 defines the literal underscore separator and 64 lowercase hex characters.
  • docs/android-analysis.md:414, docs/go-binaries.md:38, docs/rust-binaries.md:52, and docs/mcp-contracts.md:222,567 preserve the underscore in illustrative digest placeholders.

Analysis or suspected cause

The variable digest is deliberately represented by angle brackets, as in other guides. The inconsistent character is outside that placeholder: the devicetree example's colon differs from both emitted identifiers and the enforced input shape. The same-record control establishes that the digest and retained evidence are otherwise usable. No cause for how the documentation acquired this spelling was established.

Additional context

Open and closed issue searches, open/closed/merged PR searches, issue comments and related PR review comments found no report of this devicetree prefix mismatch. Closed #6224 concerns an unsupported IL2CPP names filter; #7315 and PR #7317 concern DTB block boundaries. The broader documentation reports and fixes inspected did not name this prefix. This report makes no regression claim.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7612 ### 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 Documentation ### Observed behavior The devicetree guide illustrates a retained-reference request with `"evidence_id": "ev:<digest>"`. Replacing only `<digest>` with the digest of a real retained devicetree inspection makes the production MCP `inspect_analysis_view` tool reject the request: `invalid_request`, issue path `["source","evidence_id"]`, reason `invalid_format`. In the same MCP connection, passing the original `parent_evidence_id` returned by `inspect_devicetree` succeeds and returns the requested property page. That identifier starts with `ev_`. The preceding prose correctly tells readers to follow the returned ID; the conflicting literal colon is in the illustrative request shape. The calls tested were `inspect_devicetree` with `{path, detail: "summary"}`, followed by `inspect_analysis_view` with `{source: {kind: "retained-evidence", evidence_id}, view: {kind: "devicetree", collection: "properties", path: "/serial@1000", offset: 0, limit: 32}}`. The reproduction below supplies the fixture and both exact identifier variants. ### Reproduction 1. From the already-built source baseline below, use Node.js 24.22.1 and the locked dependencies. Run this script. It creates an FDT v17 blob with one serial-node property, starts the production MCP entry point, compares both prefixes against the same retained digest, and deletes its fixture and isolated environment afterward. ```sh node --input-type=module <<'JS' import assert from 'node:assert/strict'; import { mkdtemp, writeFile, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { Client } from '@modelcontextprotocol/client'; import { StdioClientTransport } from '@modelcontextprotocol/client/stdio'; const root = await mkdtemp(join(tmpdir(), 'rea-doc-prefix-')); const env = { PATH: process.env.PATH, NO_COLOR: '1' }; for (const key of ['HOME', 'DSH_HOME', 'XDG_CONFIG_HOME', 'XDG_CACHE_HOME', 'XDG_DATA_HOME', 'XDG_STATE_HOME', 'TMPDIR']) { env[key] = root; } const cells = (...values) => { const b = Buffer.alloc(values.length * 4); values.forEach((value, i) => b.writeUInt32BE(value, i * 4)); return b; }; const name = value => { const b = Buffer.from(value + String.fromCharCode(0)); return Buffer.concat([b, Buffer.alloc((4 - b.length % 4) % 4)]); }; const body = Buffer.concat([ cells(1), name(''), cells(1), name('serial@1000'), cells(3, 4, 0), cells(48000000), cells(2, 2, 9), ]); const strings = Buffer.from('clock-frequency' + String.fromCharCode(0)); const blob = Buffer.concat([ cells(0xd00dfeed, 56 + body.length + strings.length, 56, 56 + body.length, 40, 17, 16, 0, strings.length, body.length), Buffer.alloc(16), body, strings, ]); const path = join(root, 'board.dtb'); const client = new Client({ name: 'devicetree-doc-repro', version: '1' }); const transport = new StdioClientTransport({ command: process.execPath, args: ['scripts/rea.mjs', 'mcp'], env, stderr: 'pipe', }); transport.stderr?.resume(); try { await writeFile(path, blob); await client.connect(transport); const inspected = await client.callTool({ name: 'inspect_devicetree', arguments: { path, detail: 'summary' }, }); assert.notEqual(inspected.isError, true, JSON.stringify(inspected)); const id = inspected.structuredContent.normalized_result.parent_evidence_id; assert.match(id, /^ev_[a-f0-9]{64}$/); console.log(JSON.stringify({ parent_evidence_id: id })); const view = { kind: 'devicetree', collection: 'properties', path: '/serial@1000', offset: 0, limit: 32 }; for (const [label, evidence_id] of [ ['example prefix', id.replace(/^ev_/, 'ev:')], ['returned ID', id], ]) { const reply = await client.callTool({ name: 'inspect_analysis_view', arguments: { source: { kind: 'retained-evidence', evidence_id }, view }, }); if (label === 'example prefix') { assert.equal(reply.isError, true); const error = JSON.parse(reply.content.find(c => c.type === 'text').text).error; assert.equal(error.code, 'invalid_request'); assert.deepEqual(error.details.issues[0].path, ['source', 'evidence_id']); console.log(JSON.stringify({ label, code: error.code, issues: error.details.issues })); } else { assert.notEqual(reply.isError, true, JSON.stringify(reply)); const items = reply.structuredContent.normalized_result.items; assert.equal(items[0].name, 'clock-frequency'); console.log(JSON.stringify({ label, success: true, items })); } } } finally { try { await client.close(); await transport.close(); } finally { await rm(root, { recursive: true, force: true }); } } JS ``` 2. The `example prefix` result reports `invalid_format` for `source.evidence_id`. The `returned ID` result succeeds and contains `clock-frequency`, with `hex: "02dc6c00"`. The production MCP comparison reproduced in both independent executions, including a replay of this exact script. A separate compiled CLI comparison also reproduced the format rejection once; changing its fabricated ID to `ev_` advanced to the expected missing-record error. ### Expected behavior The retained-reference illustration should preserve the actual identifier spelling when the reader fills its digest placeholder. Use `ev_<digest>`, or show a placeholder for the complete returned `parent_evidence_id`. Passing the complete returned ID unchanged already works; the schema does not need to accept colon-prefixed IDs. Other retained-reference guides use underscore-prefixed digest placeholders. The canonical retained-reference contract requires `ev_` followed by 64 lowercase hexadecimal characters. Supporting source locations are listed in the evidence section. ### Impact and scope Following the example's literal prefix while substituting a valid retained digest prevents admission of that page request. The demonstrated recovery is to use the producer-returned `parent_evidence_id` unchanged; it returns the requested property from the same retained record and session. Rejection happens during input validation. No corruption, data loss or security boundary crossing was observed. The example is illustrative: callers must replace its digest placeholder, and callers who follow the preceding prose by copying the entire returned ID avoid the failure. How often users or agents instead construct the colon-prefixed form is unknown. The observation concerns this one documentation example, rather than a general failure of devicetree inspection or retained paging. - Impact level: P2 - Assessment status: supported - Impact basis: A request assembled with the example's literal prefix and a real retained digest is rejected before the requested page can be returned. - Workaround status: available - Workaround basis: Reusing the complete producer-returned ID succeeded on the same fixture, digest and MCP connection. ### REA version Exact output of `node scripts/rea.mjs --version`: ```text 6.8.0 ``` Source baseline: `65f1c447344a423fa2785847dbcea1e9a9e2454a`. ### Runtime and platform Node.js `v24.22.1`; macOS 27.0.1; Darwin arm64. The production stdio client used the locked `@modelcontextprotocol/client` 2.3.1 dependency. No native analysis provider or GUI was used. ### Target and artifact context An inline-generated 124-byte FDT v17 fixture containing the root, `/serial@1000`, and a four-byte `clock-frequency` property. It was inspected and retained through the production MCP tool in the same connection as both page requests. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence The exact reproduction replay returned the following parent ID (the ID can vary with the generated fixture path): ```text ev_05b39f25f623abb1cc6ad11515a581d53b9a3c3d2488a5d1563479160b911b96 ``` Changing only its underscore to a colon produced this selected diagnostic: ```json {"label":"example prefix","code":"invalid_request","issues":[{"path":["source","evidence_id"],"reason":"invalid_format","message":"Expected a ev_ prefixed lowercase sha256 digest","expected":"regex"}]} ``` The exact original ID produced `success: true` and a property row with `name: "clock-frequency"`, `offset: 92`, `bytes: 4`, `hex: "02dc6c00"`, and `source_index: 0`. These outputs come from the same fixture and connection in the script above. Static evidence at the stated baseline: - `docs/devicetree.md:39-52` instructs callers to follow `parent_evidence_id` but illustrates `ev:<digest>`. - `src/domain/evidenceReference.ts:4-8` binds the retained-reference ID to the `ev` prefixed-digest contract. - `src/domain/digests.ts:21-33` defines the literal underscore separator and 64 lowercase hex characters. - `docs/android-analysis.md:414`, `docs/go-binaries.md:38`, `docs/rust-binaries.md:52`, and `docs/mcp-contracts.md:222,567` preserve the underscore in illustrative digest placeholders. ### Analysis or suspected cause The variable digest is deliberately represented by angle brackets, as in other guides. The inconsistent character is outside that placeholder: the devicetree example's colon differs from both emitted identifiers and the enforced input shape. The same-record control establishes that the digest and retained evidence are otherwise usable. No cause for how the documentation acquired this spelling was established. ### Additional context Open and closed issue searches, open/closed/merged PR searches, issue comments and related PR review comments found no report of this devicetree prefix mismatch. Closed #6224 concerns an unsupported IL2CPP names filter; #7315 and PR #7317 concern DTB block boundaries. The broader documentation reports and fixes inspected did not name this prefix. This report makes no regression claim.
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#4542
No description provided.