fix(analysis-view): keep inferred confidence on views of inferred Evidence (#8099)

createAnalysisViewEvidence chose view confidence from the view result kind
only, so every summary, page or item view of an inferred parent
(compare_source_to_bundle, inspect_evm_interface, partially resolved
inspect_il2cpp_metadata) was sealed as derived while keeping the parent's
authority. A projection never makes its facts more certain: an inferred
parent now yields inferred views through CLI and MCP alike. Observed and
derived parents keep their existing view confidence and identities.

Fixes #8098

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-30 00:59:53 -07:00
1 parent 317bf3fe23
commit bfa082f777
7 files changed
+114 -3

No files matched your search

+4 -2
View File
@@ -80,8 +80,10 @@ without bytecode text or duplicated raw stdout:
}
```
The result is distinct derived Evidence linked to its complete parent. Its
`normalized_result.summary` preserves every producer-returned selector, decoded
The result is distinct view Evidence linked to its complete parent. Like the
parent, it has `confidence: inferred`; projection never strengthens inferred
candidates. Its `normalized_result.summary` preserves every producer-returned
selector, decoded
byte offset, dispatch, argument/mutability string including null, carrier/decoded
identities, original limitations and explicit unknowns. Payload `evidence_kind`
remains `inferred`. It preserves stderr/truncation and records stdout text length.
+3
View File
@@ -482,6 +482,9 @@ session retention refuses summary detail with `capability_unavailable`;
For `inspect_artifact`, omission also keeps the original request parameters and
complete output shape. Delivery selection never reaches the inventory engine;
the complete inspection and its atomic inventory Evidence remain retained.
A view is never more certain than its parent: a summary or other view of
`inferred` Evidence is `inferred`. Other views are `derived`, unless the
view kind itself infers, as described below.
`inspect_analysis_view` also accepts `api-surface` for retained HAR capture Evidence,
grouping request lines by method, origin and format-rule path template. Templated
@@ -10,7 +10,9 @@ import {
analysisViewJavaScriptAnalysisWithSource,
analysisViewJavaScriptEvidence,
analysisViewLayoutEvidence,
analysisViewLayoutFixture,
} from "../../../tests/fixtures/analysisView.js";
import { BINARY_LAYOUT_TEST_PROVIDER } from "../../../tests/fixtures/binaryDiagnostics/layout.js";
import { describe, expect, it, vi } from "vitest";
import { JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE } from "../../contracts/javascript/javascriptRuntimeReconciliationExample.js";
@@ -450,6 +452,54 @@ it("projects inline layout Evidence and retains a derived view record", async ()
});
});
it("never seals a view as more certain than its parent Evidence", async () => {
const layout = analysisViewLayoutFixture();
for (const [confidence, expected] of [
["observed", "derived"],
["derived", "derived"],
["inferred", "inferred"],
] as const) {
const parent = createEvidence(
{
path: layout.artifact.path,
format: "elf",
architecture: "x86_64",
sha256: layout.artifact.sha256,
},
BINARY_LAYOUT_TEST_PROVIDER,
{
operation: "inspect_binary_layout",
parameters: { path: layout.artifact.path },
result: layout,
confidence,
limitations: layout.limitations,
},
);
// Inline CLI/MCP views and retained summary delivery share one sealing owner.
const views = [
await inspectAnalysisView({
source: { kind: "inline", evidence: parent },
view: { kind: "item", collection: "sections", selector: { index: 2 } },
}),
projectRetainedAnalysis(
parent,
{
recordEvidence: () => ok("added"),
evidenceById: (id) =>
id === parent.evidence_id ? parent : undefined,
},
{ kind: "summary" },
),
];
for (const view of views) {
if (!view.ok) throw view.error;
expect(view.value.confidence).toBe(expected);
expect(view.value.authority).toBe(parent.authority);
expect(view.value.evidence_links).toEqual([parent.evidence_id]);
}
}
});
it("resolves same-session retained JavaScript Evidence without re-running analysis", async () => {
const parent = analysisViewJavaScriptEvidence(
analysisViewJavaScriptAnalysisWithSource(),
@@ -416,8 +416,10 @@ const createAnalysisViewEvidence = (
result,
rawResult: null,
confidence:
// A projection never strengthens its parent: inferred facts stay inferred.
// Grouping is whole-capture, so any templated group or parameter makes the result inferred;
// entity types and identity in an extracted state, and the coverage summarizing it, always are.
parent.confidence === "inferred" ||
result.kind === "environment-state" ||
result.kind === "environment-coverage" ||
((result.kind === "api-surface" ||
@@ -30,6 +30,7 @@ import {
environmentStateCaptureFixture,
} from "../../fixtures/analysisView.js";
import { binaryLayoutFixture } from "../../fixtures/binaryDiagnostics/layout.js";
import { evmInterfaceFixture } from "../../fixtures/evm/interface.js";
import {
createEvidence,
parseEvidence,
@@ -854,6 +855,48 @@ cliTest(
},
);
cliTest(
"keeps an inferred parent's confidence on every view through the compiled CLI",
async ({ cli }) => {
const value = evmInterfaceFixture();
const parent = createEvidence(
{
path: value.artifact.path,
sha256: value.artifact.sha256,
format: "file",
},
{ id: "fixture-evm", name: "EVMole", version: "1" },
{
operation: "inspect_evm_interface",
parameters: { path: value.artifact.path, encoding: "hex" },
result: value,
confidence: "inferred",
},
);
for (const view of [
{ kind: "summary" },
{ kind: "evm", facet: "bytecode_hex", offset: 0, limit: 4 },
]) {
const result = await cli.run({
arguments: [
"inspect-analysis-view",
JSON.stringify({
source: { kind: "inline", evidence: parent },
view,
}),
"--json",
],
});
expect(result.exitCode, result.stdout).toBe(0);
expect(parseEvidence(result.json)).toMatchObject({
confidence: "inferred",
authority: parent.authority,
evidence_links: [parent.evidence_id],
});
}
},
);
cliTest("rejects malformed JSON before projection", async ({ cli }) => {
const root = await createTestTempDirectory("rea-analysis-view-cli-bad-");
const result = await cli.run({
+4 -1
View File
@@ -398,7 +398,9 @@ it("returns the exact inferred interface summary while preserving complete defau
);
expect(derived.evidence_id).not.toBe(original.evidence_id);
expect(derived.evidence_links).toEqual([original.evidence_id]);
expect(derived.confidence).toBe("derived");
// Retention and paging do not make inferred candidates more certain.
expect(original.confidence).toBe("inferred");
expect(derived.confidence).toBe("inferred");
const { hex: _hex, ...bytecode } = value.bytecode;
expect(view).toMatchObject({
parent_operation: "inspect_evm_interface",
@@ -517,6 +519,7 @@ it.each(["valid", "wrong-subject", "no-subject"])(
exhausted: true,
});
expect(derived.evidence_links).toEqual([source.evidence_id]);
expect(derived.confidence).toBe("inferred");
} else
expect(parseMcpToolError(response).error.code).toBe("invalid_request");
expect(inspect).not.toHaveBeenCalled();
@@ -22,6 +22,8 @@ it.each(["full", "compact"] as const)(
parent.normalized_result,
);
expect(full.summary).toMatchObject({ split: 1, unknown: 2 });
// Static source-to-bundle mappings are inferences; no view may strengthen them.
expect(parent.confidence).toBe("inferred");
const fileParent = await call("compare_source_to_bundle", {
...inputs,
reference: { kind: "file", path },
@@ -45,6 +47,7 @@ it.each(["full", "compact"] as const)(
...inputs,
view: { kind: "summary" },
});
expect(summary.confidence).toBe("inferred");
expect(summary.normalized_result).toMatchObject({
kind: "summary",
parent_evidence_id: parent.evidence_id,
@@ -80,6 +83,7 @@ it.each(["full", "compact"] as const)(
});
const outputSchema: Record<string, unknown> = comparisonTool.outputSchema!;
expect(ajv.compile(outputSchema)(selected)).toBe(true);
expect(selected.confidence).toBe("inferred");
const page = analysisViewResultSchema.parse(selected.normalized_result);
expect(page).toMatchObject({
kind: "source-to-bundle",
@@ -101,6 +105,10 @@ it.each(["full", "compact"] as const)(
kind: "retained-evidence",
evidence_id: fileParent.evidence_id,
};
for (const view of [{ kind: "summary" }, request])
expect(
(await call("inspect_analysis_view", { source, view })).confidence,
).toBe("inferred");
await assertRetainedPages(call, source, request, full);
await writeFile(
path,