[GH-ISSUE #5894] [Bug] Scenario manifest counts unusable request lines in body-retention limitations #3573

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

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

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-analysis-view can describe the same scenario transaction as both having and lacking a usable request line. With one schema-valid request for data:text/plain,no-origin and no response, the manifest correctly contains no represented records, but its limitations include both:

1 of 1 scenario transactions with a usable request line retained no response body; tools citing them replay only the recorded status and content type, either of which may be absent.
1 retained records without a usable request line are not represented in the manifest.

This reproduced through the registered CLI handler with synthetic inline scenario Evidence. The same contradiction occurred for a file URL, a blob URL and an invalid URL string. An HTTPS control retained one record and did not report a missing usable request line.

Reproduction

At the revision below, place this temporary focused test at src/application/analysisView/scenarioRequestLineReproduction.test.ts and run:

./node_modules/.bin/vitest run src/application/analysisView/scenarioRequestLineReproduction.test.ts --maxWorkers=1

The test uses the repository's existing capture fixture, replaces its single request URL using the production sanitizer, validates the capture schema, creates Evidence, and calls the registered inspect-analysis-view CLI command. The assertions characterize the observed incorrect output. Remove the test after running it.

import { expect, it } from "vitest";
import { scenarioCaptureFixture, scenarioCaptureEvidence } from "../../../tests/fixtures/browserScenarioCapture.js";
import { sanitizeBrowserUrl } from "../../domain/browserObservation.js";
import { browserScenarioCaptureSchema } from "../../domain/browserScenarioCapture.js";
import { Cli } from "incur";
import { registerAnalysisViewCommands } from "../../cli/analysisViewCommands.js";
import { silentLogger } from "../../logger.js";

it.each([
  "data:text/plain,no-origin",
  "file:///public/example.txt",
  "blob:https://example.test/resource",
  "not a url",
  "https://example.test/api/control",
])("checks scenario request-line coverage for %s", async (url) => {
  const original = scenarioCaptureFixture([{ method: "GET", url: "/api/control" }]);
  const capture = browserScenarioCaptureSchema.parse({
    ...original,
    events: {
      ...original.events,
      items: original.events.items.map((event) => event.kind === "request"
        ? { ...event, url: sanitizeBrowserUrl(url) }
        : event),
    },
  });
  const input = {
    source: { kind: "inline", evidence: scenarioCaptureEvidence(capture) },
    view: { kind: "environment-manifest" },
  };
  const cli = Cli.create("repro", { sync: false });
  registerAnalysisViewCommands(cli, silentLogger);
  let stdout = "";
  let exitCode = 0;
  await cli.serve(["inspect-analysis-view", JSON.stringify(input), "--json"], {
    env: {},
    stdout: (text) => { stdout += text; },
    exit: (code) => { exitCode = code; },
  });
  expect(exitCode).toBe(0);
  const view = JSON.parse(stdout).normalized_result as Record<string, unknown>;
  const limitations = view.limitations as string[];
  const usable = limitations.filter((value) => value.includes("usable request line"));
  console.log(JSON.stringify({ url, records: view.records, usable }));
  expect(usable).toContain("1 of 1 scenario transactions with a usable request line retained no response body; tools citing them replay only the recorded status and content type, either of which may be absent.");
  if (!url.endsWith("/api/control")) {
    expect(usable).toContain("1 retained records without a usable request line are not represented in the manifest.");
    expect(view.records).toEqual([]);
  } else {
    expect(usable).toHaveLength(1);
    expect(view.records).not.toEqual([]);
  }
});

All five cases passed in the executed CLI-handler run. No browser was launched; the input captures were synthetic. The compiled CLI executable and MCP transport were not exercised.

Expected behavior

The body-retention limitation should count only transactions whose request line the manifest can actually use. A record rejected as lacking a usable request line should not also contribute to the numerator and denominator described as transactions “with a usable request line.”

The manifest contract explicitly counts records without a usable line separately and supports browser-scenario parents. The producer's represented field comment also defines it in terms of request-line usability.

Impact and scope

P3: misleading limitation text and counts for admitted scenario Evidence with an unredacted URL that cannot form a recorded request line. In the four failing cases, actual manifest records remained correctly empty. No loss of usable records or incorrect replay was observed. Callers can inspect the represented records and separate unusable-line limitation to recognize the discrepancy. Frequency in real browser captures is unknown.

REA version

5241597594 (source package version 6.7.0).

Runtime and platform

macOS 27.0.1 arm64, Node.js v26.12.0, Vitest 5.0.3.

Target and artifact context

Synthetic capture_browser_scenario Evidence containing one retained GET request and no response, supplied as an inline source for an environment-manifest view. No external application or provider was used.

Interface

CLI

Earliest failure stage

Transport or result projection

Relevant output or evidence

The focused test printed an empty records array and both contradictory limitation strings for each of these URLs:

data:text/plain,no-origin
file:///public/example.txt
blob:https://example.test/resource
not a url

For https://example.test/api/control, it printed one record with ordinal: 0, method: "GET", path: "/api/control", status: null, and content_type: null. Only the bodiless-transaction limitation appeared.

Source evidence:

Typed outcome or recovery expected

Keep the successful manifest projection and explicit unusable-record limitation. For these single unusable requests, omit the body-retention limitation that claims a usable request line. Preserve the HTTPS control's usable-record count.

Analysis or suspected cause

The scenario producer and manifest accumulator use different eligibility rules. The producer checks only URL redaction for otherwise attributable requests; the consumer also parses the transport origin and raw path. Sharing the admission decision would keep the coverage counts consistent.

A blob:https://... URL illustrates why origin alone is insufficient: its WHATWG origin is available, but the recorded transport parser cannot derive an explicit transport path. This is source-derived reasoning supporting the observed output.

Additional context

Fetched origin/main at c324d31b45. The relevant counting and request-line admission logic remains unchanged; this was a source comparison, not a runtime rerun on that revision. Live-browser production of these URL inputs remains untested.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5894 ### 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-analysis-view` can describe the same scenario transaction as both having and lacking a usable request line. With one schema-valid request for `data:text/plain,no-origin` and no response, the manifest correctly contains no represented records, but its limitations include both: ```text 1 of 1 scenario transactions with a usable request line retained no response body; tools citing them replay only the recorded status and content type, either of which may be absent. 1 retained records without a usable request line are not represented in the manifest. ``` This reproduced through the registered CLI handler with synthetic inline scenario Evidence. The same contradiction occurred for a file URL, a blob URL and an invalid URL string. An HTTPS control retained one record and did not report a missing usable request line. ### Reproduction At the revision below, place this temporary focused test at `src/application/analysisView/scenarioRequestLineReproduction.test.ts` and run: ```sh ./node_modules/.bin/vitest run src/application/analysisView/scenarioRequestLineReproduction.test.ts --maxWorkers=1 ``` The test uses the repository's existing capture fixture, replaces its single request URL using the production sanitizer, validates the capture schema, creates Evidence, and calls the registered `inspect-analysis-view` CLI command. The assertions characterize the observed incorrect output. Remove the test after running it. ```ts import { expect, it } from "vitest"; import { scenarioCaptureFixture, scenarioCaptureEvidence } from "../../../tests/fixtures/browserScenarioCapture.js"; import { sanitizeBrowserUrl } from "../../domain/browserObservation.js"; import { browserScenarioCaptureSchema } from "../../domain/browserScenarioCapture.js"; import { Cli } from "incur"; import { registerAnalysisViewCommands } from "../../cli/analysisViewCommands.js"; import { silentLogger } from "../../logger.js"; it.each([ "data:text/plain,no-origin", "file:///public/example.txt", "blob:https://example.test/resource", "not a url", "https://example.test/api/control", ])("checks scenario request-line coverage for %s", async (url) => { const original = scenarioCaptureFixture([{ method: "GET", url: "/api/control" }]); const capture = browserScenarioCaptureSchema.parse({ ...original, events: { ...original.events, items: original.events.items.map((event) => event.kind === "request" ? { ...event, url: sanitizeBrowserUrl(url) } : event), }, }); const input = { source: { kind: "inline", evidence: scenarioCaptureEvidence(capture) }, view: { kind: "environment-manifest" }, }; const cli = Cli.create("repro", { sync: false }); registerAnalysisViewCommands(cli, silentLogger); let stdout = ""; let exitCode = 0; await cli.serve(["inspect-analysis-view", JSON.stringify(input), "--json"], { env: {}, stdout: (text) => { stdout += text; }, exit: (code) => { exitCode = code; }, }); expect(exitCode).toBe(0); const view = JSON.parse(stdout).normalized_result as Record<string, unknown>; const limitations = view.limitations as string[]; const usable = limitations.filter((value) => value.includes("usable request line")); console.log(JSON.stringify({ url, records: view.records, usable })); expect(usable).toContain("1 of 1 scenario transactions with a usable request line retained no response body; tools citing them replay only the recorded status and content type, either of which may be absent."); if (!url.endsWith("/api/control")) { expect(usable).toContain("1 retained records without a usable request line are not represented in the manifest."); expect(view.records).toEqual([]); } else { expect(usable).toHaveLength(1); expect(view.records).not.toEqual([]); } }); ``` All five cases passed in the executed CLI-handler run. No browser was launched; the input captures were synthetic. The compiled CLI executable and MCP transport were not exercised. ### Expected behavior The body-retention limitation should count only transactions whose request line the manifest can actually use. A record rejected as lacking a usable request line should not also contribute to the numerator and denominator described as transactions “with a usable request line.” The [manifest contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/docs/web-network-captures.md#L400-L416) explicitly counts records without a usable line separately and supports browser-scenario parents. The producer's [represented field comment](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/scenarioCaptureRecords.ts#L141-L150) also defines it in terms of request-line usability. ### Impact and scope P3: misleading limitation text and counts for admitted scenario Evidence with an unredacted URL that cannot form a recorded request line. In the four failing cases, actual manifest records remained correctly empty. No loss of usable records or incorrect replay was observed. Callers can inspect the represented records and separate unusable-line limitation to recognize the discrepancy. Frequency in real browser captures is unknown. ### REA version 52415975942a4b9c53a817915b313ccc404f280e (source package version 6.7.0). ### Runtime and platform macOS 27.0.1 arm64, Node.js v26.12.0, Vitest 5.0.3. ### Target and artifact context Synthetic `capture_browser_scenario` Evidence containing one retained GET request and no response, supplied as an inline source for an environment-manifest view. No external application or provider was used. ### Interface CLI ### Earliest failure stage Transport or result projection ### Relevant output or evidence The focused test printed an empty `records` array and both contradictory limitation strings for each of these URLs: ```text data:text/plain,no-origin file:///public/example.txt blob:https://example.test/resource not a url ``` For `https://example.test/api/control`, it printed one record with `ordinal: 0`, `method: "GET"`, `path: "/api/control"`, `status: null`, and `content_type: null`. Only the bodiless-transaction limitation appeared. Source evidence: - [Scenario admission](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/scenarioCaptureRecords.ts#L296-L340) validates the capture schema and selected network content before projection. - [Scenario counting](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/scenarioCaptureRecords.ts#L218-L260) treats every unredacted, attributable request as represented and uses that count in the body-retention limitation. - [Request-line admission](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/webNetworkRouteTemplates.ts#L157-L181) requires an origin and recorded path as well as an available method. - [Accumulator admission](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/EnvironmentManifestAccumulator.ts#L213-L225) counts rejected request lines, then [reports that count](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisView/EnvironmentManifestAccumulator.ts#L521-L524). ### Typed outcome or recovery expected Keep the successful manifest projection and explicit unusable-record limitation. For these single unusable requests, omit the body-retention limitation that claims a usable request line. Preserve the HTTPS control's usable-record count. ### Analysis or suspected cause The scenario producer and manifest accumulator use different eligibility rules. The producer checks only URL redaction for otherwise attributable requests; the consumer also parses the transport origin and raw path. Sharing the admission decision would keep the coverage counts consistent. A `blob:https://...` URL illustrates why origin alone is insufficient: its WHATWG origin is available, but [the recorded transport parser](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/recordedTransportUrl.ts#L16-L73) cannot derive an explicit transport path. This is source-derived reasoning supporting the observed output. ### Additional context Fetched `origin/main` at c324d31b45fcab77e4ed7320ed60f0bf5a4ba188. The relevant counting and request-line admission logic remains unchanged; this was a source comparison, not a runtime rerun on that revision. Live-browser production of these URL inputs remains untested.
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#3573
No description provided.