[GH-ISSUE #7387] [Bug] Environment coverage excludes recorded HEAD and OPTIONS tools from read counts #4393

Closed
opened 2026-10-31 00:49:28 +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/7387

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

Requesting environment-manifest, environment-state, and environment-coverage for the same inline capture Evidence produces inconsistent tool accounting. In an independently executed fixture with GET, HEAD, and OPTIONS requests to one URL, the manifest contains three tools. The state view contains only the GET tool, and coverage reports one state-backed read and zero replayed reads. HEAD and OPTIONS receive neither a state view nor a recorded reason, and neither appears among writes. The generic coverage object nevertheless reports all three tools examined. None of the returned limitations explains this omission.

This was reproduced through the inspectAnalysisView application service used by the MCP handler. An MCP transport and a running environment server were not exercised.

Reproduction

Use a source checkout at commit f8f7ac96de8a66a4ab9a23d87e04779bfcd11045, Node.js v24.22.1, and the locked dependencies installed with npm ci. Create the following temporary test from the repository root:

cat > src/domain/analysisView/verify-probe.test.ts <<'TEST'
import { expect, it } from "vitest";
import { inspectAnalysisView } from "../../application/analysisView/AnalysisViewService.js";
import { analysisViewCaptureEvidence, environmentManifestCaptureFixture } from "../../../tests/fixtures/analysisView.js";

it("accounts for the methods supplied to the public analysis-view service", async () => {
  const entries = ["GET", "HEAD", "OPTIONS"].map((method) => ({
    request: { method, url: "https://sample.test/api/items" },
    response: {
      status: 200,
      content: method === "GET"
        ? { mimeType: "application/json", text: '{"items":[{"id":"a"},{"id":"b"}],"total":2}', size: 46 }
        : { mimeType: "application/json", size: 0 },
    },
  }));
  const evidence = analysisViewCaptureEvidence(environmentManifestCaptureFixture(entries));
  const results: Record<string, any> = {};
  for (const kind of ["environment-manifest", "environment-state", "environment-coverage"]) {
    const result = await inspectAnalysisView({
      source: { kind: "inline", evidence },
      view: { kind, template_rules: [] },
    });
    if (!result.ok) throw result.error;
    results[kind] = result.value.normalized_result;
  }
  const manifest = results["environment-manifest"];
  const state = results["environment-state"];
  const coverage = results["environment-coverage"];
  console.log(JSON.stringify({
    manifest: manifest.tools.map((tool: any) => ({ name: tool.name, method: tool.method })),
    views: state.views,
    reads: coverage.reads,
    writes: coverage.writes,
    coverage: coverage.coverage,
    limitations: coverage.limitations,
  }, null, 2));
  expect(manifest.tools.map((tool: any) => tool.method)).toEqual(["GET", "HEAD", "OPTIONS"]);
  expect(state.views.map((view: any) => view.tool)).toEqual(["get_items"]);
  expect(state.writes).toEqual([]);
  expect(coverage.reads).toEqual({ state: 1, recorded: 0, recorded_reasons: {} });
  expect(coverage.writes).toEqual({ served: [], withheld: [] });
  expect(coverage.coverage).toMatchObject({ examined: 3, total: 3, exhausted: true });
});
TEST
npm run test:local -- src/domain/analysisView/verify-probe.test.ts
rm src/domain/analysisView/verify-probe.test.ts

The test passed in one execution. Its assertions describe the incorrect result: three manifest tools but only one accounted-for read. The synthetic URL is input data only; this test makes no request to it.

Expected behavior

Every read tool should have either a render plan or a recorded view explaining why it replays. Expected source: docs/specs/agent-environments/state-view/spec.md:7. HEAD and OPTIONS belong to the documented read-only method set, so their empty response bodies do not exempt them from read accounting. Expected source: docs/specs/agent-environments/state-coverage/spec.md:67. Coverage should count replayed reads as well as state-backed reads, as promised by docs/specs/agent-environments/environment-build/spec.md:40. For this fixture, the two unrendered read tools need recorded entries and must contribute to the replayed-read count.

Impact and scope

P2: a consumer assessing an environment from its state and coverage Evidence receives incomplete read counts and no explanation for two tools that the manifest advertises. This is consequential evidence-summary behavior, rather than a cosmetic label. The reproduction establishes the problem for the synthetic three-method input; its prevalence in real captures was not measured. Captured records remain represented by the manifest. Source inspection shows a recording fallback for calls without a state view, so this finding does not establish incorrect served responses or a blocked serving workflow. Comparing manifest tool names against state views exposes the gap locally but does not correct the returned counts.

REA version

6.8.0 from package.json; source commit f8f7ac96de.

Runtime and platform

Node.js v24.22.1; macOS, process.platform darwin, OS release 27.0.0; arm64. Measured in the verifier environment.

Target and artifact context

Synthetic HAR-shaped capture Evidence containing three successful requests to one path. GET has a JSON collection body; HEAD and OPTIONS have empty bodies. No executable target or external service is involved.

Interface

MCP

Earliest failure stage

Transport or result projection

Relevant output or evidence

The focused application-service test printed the following values, reduced here to the fields that establish the omission:

manifest: get_items (GET), head_items (HEAD), options_items (OPTIONS)
state views: get_items (list)
reads: {"state":1,"recorded":0,"recorded_reasons":{}}
writes: {"served":[],"withheld":[]}
coverage: {"status":"complete-within-view","examined":3,"total":3,"next_offset":null,"exhausted":true}

Failure source: src/domain/analysisView/EnvironmentStateExtraction.ts:333 only creates candidates for GET after handling query POSTs and inferred read-only tools; these HEAD and OPTIONS tools leave that loop without a view. src/domain/analysisView/environmentStateRules.ts:232 defines the write-method set without HEAD or OPTIONS, and src/domain/analysisView/EnvironmentStateWriteSimulation.ts:188 excludes non-write methods from write plans. src/domain/analysisView/environmentCoverageView.ts:121 counts only existing views. src/domain/analysisView/EnvironmentStateAccumulator.ts:417 returns the manifest's coverage alongside the incomplete state views.

The supported MCP route is established by src/server/registerAnalysisViewTool.ts:28, which calls the application service exercised by the reproduction. src/application/environment/environmentStateAnswer.ts:588 supplies the source-derived recording fallback when a tool has no view; that fallback was not executed in this reproduction. The generic examination count is not a claim that all tools render from state; the defect is the absence of these replayed reads from both state explanations and coverage counts.

Additional context

Verified offline at the commit above; no source fix is included. Related, distinct report: #5974 (closed) concerned digit-string item counters in environment-state list views.

Originally created by @coygeek on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7387 ### 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 Requesting `environment-manifest`, `environment-state`, and `environment-coverage` for the same inline capture Evidence produces inconsistent tool accounting. In an independently executed fixture with GET, HEAD, and OPTIONS requests to one URL, the manifest contains three tools. The state view contains only the GET tool, and coverage reports one state-backed read and zero replayed reads. HEAD and OPTIONS receive neither a state view nor a recorded reason, and neither appears among writes. The generic coverage object nevertheless reports all three tools examined. None of the returned limitations explains this omission. This was reproduced through the `inspectAnalysisView` application service used by the MCP handler. An MCP transport and a running environment server were not exercised. ### Reproduction Use a source checkout at commit `f8f7ac96de8a66a4ab9a23d87e04779bfcd11045`, Node.js v24.22.1, and the locked dependencies installed with `npm ci`. Create the following temporary test from the repository root: ```sh cat > src/domain/analysisView/verify-probe.test.ts <<'TEST' import { expect, it } from "vitest"; import { inspectAnalysisView } from "../../application/analysisView/AnalysisViewService.js"; import { analysisViewCaptureEvidence, environmentManifestCaptureFixture } from "../../../tests/fixtures/analysisView.js"; it("accounts for the methods supplied to the public analysis-view service", async () => { const entries = ["GET", "HEAD", "OPTIONS"].map((method) => ({ request: { method, url: "https://sample.test/api/items" }, response: { status: 200, content: method === "GET" ? { mimeType: "application/json", text: '{"items":[{"id":"a"},{"id":"b"}],"total":2}', size: 46 } : { mimeType: "application/json", size: 0 }, }, })); const evidence = analysisViewCaptureEvidence(environmentManifestCaptureFixture(entries)); const results: Record<string, any> = {}; for (const kind of ["environment-manifest", "environment-state", "environment-coverage"]) { const result = await inspectAnalysisView({ source: { kind: "inline", evidence }, view: { kind, template_rules: [] }, }); if (!result.ok) throw result.error; results[kind] = result.value.normalized_result; } const manifest = results["environment-manifest"]; const state = results["environment-state"]; const coverage = results["environment-coverage"]; console.log(JSON.stringify({ manifest: manifest.tools.map((tool: any) => ({ name: tool.name, method: tool.method })), views: state.views, reads: coverage.reads, writes: coverage.writes, coverage: coverage.coverage, limitations: coverage.limitations, }, null, 2)); expect(manifest.tools.map((tool: any) => tool.method)).toEqual(["GET", "HEAD", "OPTIONS"]); expect(state.views.map((view: any) => view.tool)).toEqual(["get_items"]); expect(state.writes).toEqual([]); expect(coverage.reads).toEqual({ state: 1, recorded: 0, recorded_reasons: {} }); expect(coverage.writes).toEqual({ served: [], withheld: [] }); expect(coverage.coverage).toMatchObject({ examined: 3, total: 3, exhausted: true }); }); TEST npm run test:local -- src/domain/analysisView/verify-probe.test.ts rm src/domain/analysisView/verify-probe.test.ts ``` The test passed in one execution. Its assertions describe the incorrect result: three manifest tools but only one accounted-for read. The synthetic URL is input data only; this test makes no request to it. ### Expected behavior Every read tool should have either a render plan or a recorded view explaining why it replays. Expected source: `docs/specs/agent-environments/state-view/spec.md:7`. HEAD and OPTIONS belong to the documented read-only method set, so their empty response bodies do not exempt them from read accounting. Expected source: `docs/specs/agent-environments/state-coverage/spec.md:67`. Coverage should count replayed reads as well as state-backed reads, as promised by `docs/specs/agent-environments/environment-build/spec.md:40`. For this fixture, the two unrendered read tools need recorded entries and must contribute to the replayed-read count. ### Impact and scope P2: a consumer assessing an environment from its state and coverage Evidence receives incomplete read counts and no explanation for two tools that the manifest advertises. This is consequential evidence-summary behavior, rather than a cosmetic label. The reproduction establishes the problem for the synthetic three-method input; its prevalence in real captures was not measured. Captured records remain represented by the manifest. Source inspection shows a recording fallback for calls without a state view, so this finding does not establish incorrect served responses or a blocked serving workflow. Comparing manifest tool names against state views exposes the gap locally but does not correct the returned counts. ### REA version 6.8.0 from package.json; source commit f8f7ac96de8a66a4ab9a23d87e04779bfcd11045. ### Runtime and platform Node.js v24.22.1; macOS, process.platform darwin, OS release 27.0.0; arm64. Measured in the verifier environment. ### Target and artifact context Synthetic HAR-shaped capture Evidence containing three successful requests to one path. GET has a JSON collection body; HEAD and OPTIONS have empty bodies. No executable target or external service is involved. ### Interface MCP ### Earliest failure stage Transport or result projection ### Relevant output or evidence The focused application-service test printed the following values, reduced here to the fields that establish the omission: ```text manifest: get_items (GET), head_items (HEAD), options_items (OPTIONS) state views: get_items (list) reads: {"state":1,"recorded":0,"recorded_reasons":{}} writes: {"served":[],"withheld":[]} coverage: {"status":"complete-within-view","examined":3,"total":3,"next_offset":null,"exhausted":true} ``` Failure source: `src/domain/analysisView/EnvironmentStateExtraction.ts:333` only creates candidates for GET after handling query POSTs and inferred read-only tools; these HEAD and OPTIONS tools leave that loop without a view. `src/domain/analysisView/environmentStateRules.ts:232` defines the write-method set without HEAD or OPTIONS, and `src/domain/analysisView/EnvironmentStateWriteSimulation.ts:188` excludes non-write methods from write plans. `src/domain/analysisView/environmentCoverageView.ts:121` counts only existing views. `src/domain/analysisView/EnvironmentStateAccumulator.ts:417` returns the manifest's coverage alongside the incomplete state views. The supported MCP route is established by `src/server/registerAnalysisViewTool.ts:28`, which calls the application service exercised by the reproduction. `src/application/environment/environmentStateAnswer.ts:588` supplies the source-derived recording fallback when a tool has no view; that fallback was not executed in this reproduction. The generic examination count is not a claim that all tools render from state; the defect is the absence of these replayed reads from both state explanations and coverage counts. ### Additional context Verified offline at the commit above; no source fix is included. Related, distinct report: #5974 (closed) concerned digit-string item counters in environment-state list views.
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#4393
No description provided.