[GH-ISSUE #6847] [Bug] State-served item reads drop repeated path separators and cannot find extracted entities #4187

Closed
opened 2026-10-31 00:47:32 +00:00 by bsakaguchi · 0 comments
Owner

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

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

Executed against the source revision below: a valid synthetic HAR recording of GET https://example.test/api//books/1 produces an item view and a retained entity in collection /api//books. Calling that view with its recorded identity returns AnalysisInputError:

The state holds no /api//books entity 1 in /api/books.

The otherwise identical /api/books/1 fixture succeeds. Direct recorded replay of the double-separator fixture also succeeds with exact_match: true. This is source-runtime execution of production decoding, Evidence projection, source/state loading, and the state-answer boundary; the packaged CLI and HTTP/MCP server transports were not run.

Reproduction

From a checkout of the revision above with dependencies installed, run this self-contained synthetic probe. It contacts no network destination and creates no files:

node --import tsx --input-type=module <<'JS'
import { createHash } from 'node:crypto';
import { decodeHarCapture } from './src/browser/history/HarCapture.ts';
import { createEvidence } from './src/domain/evidence.ts';
import { inspectAnalysisView } from './src/application/analysisView/AnalysisViewService.ts';
import { loadEnvironmentSource } from './src/application/environment/environmentSource.ts';
import { loadEnvironmentState } from './src/application/environment/environmentState.ts';
import { EnvironmentStateStore } from './src/application/environment/environmentStateStore.ts';
import { replayEnvironmentTool } from './src/application/environment/environmentReplay.ts';
import { answerFromState } from './src/application/environment/environmentStateAnswer.ts';

function unwrap(result) {
  if (!result.ok) throw result.error;
  return result.value;
}
async function probe(path) {
  const text = JSON.stringify({ log: {
    version: '1.2', creator: {name: 'Synthetic', version: '1'},
    entries: [{ startedDateTime: new Date().toISOString(), time: 1,
      request: { method: 'GET', url: `https://example.test${path}`, httpVersion: 'HTTP/1.1', cookies: [], headers: [], queryString: [], headersSize: -1, bodySize: 0 },
      response: { status: 200, statusText: 'OK', httpVersion: 'HTTP/1.1', cookies: [], headers: [], content: {size: 28, mimeType: 'application/json', text: JSON.stringify({id:1,title:'Recorded'})}, redirectURL: '', headersSize: -1, bodySize: -1 },
      cache: {}, timings: {send:0,wait:1,receive:0}
    }]
  }});
  const digest = createHash('sha256').update(text).digest('hex');
  const capture = {
    ...decodeHarCapture(text, []),
    artifact: {path:'/tmp/rea-example/capture.har', bytes: Buffer.byteLength(text), sha256: digest},
    format:'har', runtime_attribution:'unknown', limitations:[]
  };
  const evidence = createEvidence({path:capture.artifact.path,format:'file',sha256:digest}, capture.decoder, {
    operation:'inspect_web_network_capture', parameters:{sensitive_value_count:0}, result:capture, confidence:'observed'
  });
  const manifest = unwrap(await inspectAnalysisView({source:{kind:'inline',evidence},view:{kind:'environment-manifest'}}));
  const stateEvidence = unwrap(await inspectAnalysisView({source:{kind:'inline',evidence},view:{kind:'environment-state'}}));
  const source = unwrap(loadEnvironmentSource(manifest,evidence));
  const state = unwrap(loadEnvironmentState(stateEvidence,source));
  const tool = source.manifest.tools[0];
  const store = new EnvironmentStateStore(state);
  const args = Object.fromEntries(tool.params.map(p=>[p.name,'1']));
  const answer = answerFromState({source,store},tool,args);
  console.log(JSON.stringify({path,tool:{name:tool.name,path_template:tool.path_template},entities:state.entities,views:[...state.views.values()],replay:replayEnvironmentTool(source,tool,args),answer},null,2));
}
await probe('/api/books/1');
await probe('/api//books/1');
JS

Both probe runs at this revision reproduced the mismatch; the control succeeded on both runs.

Expected behavior

A state item view should find the entity it extracted from the same recorded path and identity. Preserve the empty internal path segment when matching /api//books.

The existing state extraction test explicitly requires collections with repeated separators to remain distinct. The owning segment parser preserves internal empty segments.

Impact and scope

A supported captured item can become unreadable when served from its correctly extracted state. Observed with a single benign JSON response and a numeric path identity; no edits to state or unusual capture extensions are needed. The single-separator control and direct recorded replay return the recorded body. Scope beyond item reads and this fixture is untested. Frequency in real captures is unknown.

REA version

Source 169512a35bb015ff209168fb8bc1c9b31a2c678b; package metadata 6.7.0. The source checkout may contain changes beyond published npm 6.7.0; the published package was not tested.

Runtime and platform

Node.js v26.12.1; Linux x86_64; pinned tsx dependency. No browser, Fiddler, QUIC peer, or device was used.

Target and artifact context

Synthetic HAR 1.2: one GET request and a successful JSON item response, compared with a single-separator control. example.test is fixture data only.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

Reduced observed output:

{
  "path": "/api//books/1",
  "tool": {"name":"get_books_by_id","path_template":"/api//books/{numeric}"},
  "entity": {"type":"/api//books","collection":"/api//books","id":"1","fields":{"id":1,"title":"Recorded"}},
  "view": {"shape":"item","type":"/api//books","envelope_ordinal":0,"checked_record_ordinals":[0]},
  "direct_replay": {"ok":true,"status":200,"body":{"id":1,"title":"Recorded"},"exact_match":true},
  "state_answer": {"ok":false,"error":{"_tag":"AnalysisInputError","operation":"environment-serve","userMessage":"The state holds no /api//books entity 1 in /api/books."}}
}

The JSON above selects and renames fields from the complete output to show the mismatch; the reproduction prints the complete results. The single-separator control returns the same body with state.source: "state" and state.view: "item".

Typed outcome or recovery expected

A successful state item answer for the recorded entity, preserving collection /api//books.

Analysis or suspected cause

Source-derived explanation: callSegments discards every empty literal segment. Collection matching requires equal segment counts, while entity selection uses that matching result to locate the held entity. The production tool dispatcher calls the tested state-answer function.

Additional context

No source changes, full build, or test suite were run. Related closed #6696 covered state extraction collapsing distinct collections and coupling their writes. The current fixture instead retains the correct /api//books collection and entity during extraction; the remaining failure occurs when the state-serving call path discards the empty segment and cannot find that correctly retained entity. No write or second collection is needed. Real capture prevalence, packaged CLI behavior, and HTTP/MCP transport behavior remain untested.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A valid captured item produces a held state entity but its recorded read fails with an input error; the failure is bounded to affected path spellings and the single-separator control succeeds.
  • Workaround status: available
  • Workaround basis: Direct recorded replay was executed on the same capture-derived source and returned status 200, the recorded item body, and exact_match true. This preserves the recording but does not provide editable state behavior; no CLI-level workaround was tested.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6847 ## 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 Executed against the source revision below: a valid synthetic HAR recording of `GET https://example.test/api//books/1` produces an item view and a retained entity in collection `/api//books`. Calling that view with its recorded identity returns `AnalysisInputError`: ```text The state holds no /api//books entity 1 in /api/books. ``` The otherwise identical `/api/books/1` fixture succeeds. Direct recorded replay of the double-separator fixture also succeeds with `exact_match: true`. This is source-runtime execution of production decoding, Evidence projection, source/state loading, and the state-answer boundary; the packaged CLI and HTTP/MCP server transports were not run. ## Reproduction From a checkout of the revision above with dependencies installed, run this self-contained synthetic probe. It contacts no network destination and creates no files: ```sh node --import tsx --input-type=module <<'JS' import { createHash } from 'node:crypto'; import { decodeHarCapture } from './src/browser/history/HarCapture.ts'; import { createEvidence } from './src/domain/evidence.ts'; import { inspectAnalysisView } from './src/application/analysisView/AnalysisViewService.ts'; import { loadEnvironmentSource } from './src/application/environment/environmentSource.ts'; import { loadEnvironmentState } from './src/application/environment/environmentState.ts'; import { EnvironmentStateStore } from './src/application/environment/environmentStateStore.ts'; import { replayEnvironmentTool } from './src/application/environment/environmentReplay.ts'; import { answerFromState } from './src/application/environment/environmentStateAnswer.ts'; function unwrap(result) { if (!result.ok) throw result.error; return result.value; } async function probe(path) { const text = JSON.stringify({ log: { version: '1.2', creator: {name: 'Synthetic', version: '1'}, entries: [{ startedDateTime: new Date().toISOString(), time: 1, request: { method: 'GET', url: `https://example.test${path}`, httpVersion: 'HTTP/1.1', cookies: [], headers: [], queryString: [], headersSize: -1, bodySize: 0 }, response: { status: 200, statusText: 'OK', httpVersion: 'HTTP/1.1', cookies: [], headers: [], content: {size: 28, mimeType: 'application/json', text: JSON.stringify({id:1,title:'Recorded'})}, redirectURL: '', headersSize: -1, bodySize: -1 }, cache: {}, timings: {send:0,wait:1,receive:0} }] }}); const digest = createHash('sha256').update(text).digest('hex'); const capture = { ...decodeHarCapture(text, []), artifact: {path:'/tmp/rea-example/capture.har', bytes: Buffer.byteLength(text), sha256: digest}, format:'har', runtime_attribution:'unknown', limitations:[] }; const evidence = createEvidence({path:capture.artifact.path,format:'file',sha256:digest}, capture.decoder, { operation:'inspect_web_network_capture', parameters:{sensitive_value_count:0}, result:capture, confidence:'observed' }); const manifest = unwrap(await inspectAnalysisView({source:{kind:'inline',evidence},view:{kind:'environment-manifest'}})); const stateEvidence = unwrap(await inspectAnalysisView({source:{kind:'inline',evidence},view:{kind:'environment-state'}})); const source = unwrap(loadEnvironmentSource(manifest,evidence)); const state = unwrap(loadEnvironmentState(stateEvidence,source)); const tool = source.manifest.tools[0]; const store = new EnvironmentStateStore(state); const args = Object.fromEntries(tool.params.map(p=>[p.name,'1'])); const answer = answerFromState({source,store},tool,args); console.log(JSON.stringify({path,tool:{name:tool.name,path_template:tool.path_template},entities:state.entities,views:[...state.views.values()],replay:replayEnvironmentTool(source,tool,args),answer},null,2)); } await probe('/api/books/1'); await probe('/api//books/1'); JS ``` Both probe runs at this revision reproduced the mismatch; the control succeeded on both runs. ## Expected behavior A state item view should find the entity it extracted from the same recorded path and identity. Preserve the empty internal path segment when matching `/api//books`. The existing [state extraction test](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/domain/analysisView/environmentStateView.test.ts#L151-L160) explicitly requires collections with repeated separators to remain distinct. The owning [segment parser](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/domain/analysisView/environmentStateRules.ts#L210-L215) preserves internal empty segments. ## Impact and scope A supported captured item can become unreadable when served from its correctly extracted state. Observed with a single benign JSON response and a numeric path identity; no edits to state or unusual capture extensions are needed. The single-separator control and direct recorded replay return the recorded body. Scope beyond item reads and this fixture is untested. Frequency in real captures is unknown. ## REA version Source `169512a35bb015ff209168fb8bc1c9b31a2c678b`; package metadata `6.7.0`. The source checkout may contain changes beyond published npm `6.7.0`; the published package was not tested. ## Runtime and platform Node.js `v26.12.1`; Linux `x86_64`; pinned `tsx` dependency. No browser, Fiddler, QUIC peer, or device was used. ## Target and artifact context Synthetic HAR 1.2: one GET request and a successful JSON item response, compared with a single-separator control. `example.test` is fixture data only. ## Interface Other ## Earliest failure stage Transport or result projection ## Relevant output or evidence Reduced observed output: ```json { "path": "/api//books/1", "tool": {"name":"get_books_by_id","path_template":"/api//books/{numeric}"}, "entity": {"type":"/api//books","collection":"/api//books","id":"1","fields":{"id":1,"title":"Recorded"}}, "view": {"shape":"item","type":"/api//books","envelope_ordinal":0,"checked_record_ordinals":[0]}, "direct_replay": {"ok":true,"status":200,"body":{"id":1,"title":"Recorded"},"exact_match":true}, "state_answer": {"ok":false,"error":{"_tag":"AnalysisInputError","operation":"environment-serve","userMessage":"The state holds no /api//books entity 1 in /api/books."}} } ``` The JSON above selects and renames fields from the complete output to show the mismatch; the reproduction prints the complete results. The single-separator control returns the same body with `state.source: "state"` and `state.view: "item"`. ## Typed outcome or recovery expected A successful state item answer for the recorded entity, preserving collection `/api//books`. ## Analysis or suspected cause Source-derived explanation: [callSegments](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/application/environment/environmentStateCallLines.ts#L30-L37) discards every empty literal segment. [Collection matching](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/domain/analysisView/environmentStateRules.ts#L260-L274) requires equal segment counts, while [entity selection](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/application/environment/environmentStateAnswer.ts#L223-L242) uses that matching result to locate the held entity. The production [tool dispatcher](https://github.com/morluto/rea/blob/169512a35bb015ff209168fb8bc1c9b31a2c678b/src/application/environment/environmentToolAnswer.ts#L27-L43) calls the tested state-answer function. ## Additional context No source changes, full build, or test suite were run. Related closed [#6696](https://github.com/morluto/rea/issues/6696) covered state extraction collapsing distinct collections and coupling their writes. The current fixture instead retains the correct `/api//books` collection and entity during extraction; the remaining failure occurs when the state-serving call path discards the empty segment and cannot find that correctly retained entity. No write or second collection is needed. Real capture prevalence, packaged CLI behavior, and HTTP/MCP transport behavior remain untested. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A valid captured item produces a held state entity but its recorded read fails with an input error; the failure is bounded to affected path spellings and the single-separator control succeeds. - Workaround status: available - Workaround basis: Direct recorded replay was executed on the same capture-derived source and returned status 200, the recorded item body, and exact_match true. This preserves the recording but does not provide editable state behavior; no CLI-level workaround was tested.
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#4187
No description provided.