mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
fix(analysis-view): preserve native relationship coverage
Carry the parent native coverage facts through inline and retained views independently of row-page exhaustion. Preserve uncertainty, optional diagnostics and source Evidence. Refs #3891, #4263 Co-authored-by: ogutdgn <79801848+ogutdgn@users.noreply.github.com>
This commit is contained in:
1 parent
a05092726f
commit
89dd71f0f0
4 files changed
+130
No files matched your search
@@ -224,6 +224,9 @@ const createAnalysisViewEvidence = (
|
||||
: "derived",
|
||||
authority: parent.authority,
|
||||
limitations: result.limitations,
|
||||
...(result.kind === "native" && parent.relationship_coverage !== null
|
||||
? { relationshipCoverage: parent.relationship_coverage }
|
||||
: {}),
|
||||
locations: [
|
||||
...(result.artifact.path.length > 0
|
||||
? [{ kind: "artifact-path" as const, path: result.artifact.path }]
|
||||
|
||||
@@ -17,12 +17,50 @@ import {
|
||||
analysisViewObjectDenseCapture,
|
||||
analysisViewJavaScriptEvidence,
|
||||
analysisViewJavaScriptAnalysisWithSource,
|
||||
analysisViewNativeEvidence,
|
||||
} from "../../fixtures/analysisView.js";
|
||||
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { ghidraFunctionDossier } from "../../../src/domain/ghidraValues.fixture.js";
|
||||
import { createEvidenceBundle } from "../../../src/domain/evidenceBundle.js";
|
||||
import { inspectEvidenceBundle } from "../../../src/application/investigation/InspectEvidenceBundle.js";
|
||||
|
||||
cliTest(
|
||||
"preserves native relationship coverage independently of exhausted CLI pages",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-native-coverage-cli-");
|
||||
const parent = analysisViewNativeEvidence({
|
||||
status: "partial",
|
||||
limited_directions: ["callees"],
|
||||
limitations: ["Callees: fixture read limit"],
|
||||
diagnostics: ["Fixture relationship diagnostic"],
|
||||
});
|
||||
const parentBefore = JSON.stringify(parent);
|
||||
const request = join(root, "request.json");
|
||||
await writeFile(
|
||||
request,
|
||||
JSON.stringify({
|
||||
source: { kind: "inline", evidence: parent },
|
||||
view: { kind: "native", facet: "callees", offset: 0, limit: 1 },
|
||||
}),
|
||||
);
|
||||
const response = await cli.run({
|
||||
arguments: ["inspect-analysis-view", request, "--json"],
|
||||
});
|
||||
expect(response.exitCode).toBe(0);
|
||||
const evidence = parseEvidence(response.json);
|
||||
expect(evidence.relationship_coverage).toEqual(
|
||||
parent.relationship_coverage,
|
||||
);
|
||||
expect(evidence.normalized_result).toMatchObject({
|
||||
item: [],
|
||||
coverage: { total: 0, exhausted: true },
|
||||
limitations: expect.arrayContaining(["Fixture dossier caveat"]),
|
||||
});
|
||||
expect(evidence.evidence_links).toEqual([parent.evidence_id]);
|
||||
expect(JSON.stringify(parent)).toBe(parentBefore);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"reads typed module references through the public CLI with canonical Evidence",
|
||||
async ({ cli }) => {
|
||||
|
||||
@@ -37,11 +37,13 @@ import {
|
||||
analysisViewCaptureFixture,
|
||||
analysisViewCaptureEvidence,
|
||||
analysisViewObjectDenseCapture,
|
||||
analysisViewNativeEvidence,
|
||||
} from "../../fixtures/analysisView.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { BINARY_LAYOUT_TEST_PROVIDER } from "../../fixtures/binaryDiagnostics/layout.js";
|
||||
import { wasmArtifactFixture } from "../../fixtures/wasm/artifact.js";
|
||||
import { evmInterfaceFixture } from "../../fixtures/evm/interface.js";
|
||||
import type { RelationshipCoverage } from "../../../src/domain/relationshipCoverage.js";
|
||||
|
||||
const connect = async (
|
||||
binaryLayout?: BinaryLayoutService,
|
||||
@@ -70,6 +72,72 @@ const connect = async (
|
||||
return { client, session };
|
||||
};
|
||||
|
||||
it("preserves native relationship coverage through inline and retained MCP views", async () => {
|
||||
const { client, session } = await connect();
|
||||
const { ajv, outputSchema } = await advertisedMcpToolSchemas(
|
||||
client,
|
||||
"inspect_analysis_view",
|
||||
);
|
||||
const coverages: (RelationshipCoverage | undefined)[] = [
|
||||
{
|
||||
status: "partial",
|
||||
limited_directions: ["callees"],
|
||||
limitations: ["Callees: fixture read limit"],
|
||||
diagnostics: ["Fixture relationship diagnostic"],
|
||||
},
|
||||
{ status: "partial", limitations: ["Historical read limit"] },
|
||||
{
|
||||
status: "unknown",
|
||||
limited_directions: ["callees"],
|
||||
reason: "Callees not measured",
|
||||
diagnostics: ["Unknown coverage diagnostic"],
|
||||
},
|
||||
{
|
||||
status: "complete",
|
||||
diagnostics: ["Only direct references are observed"],
|
||||
},
|
||||
{ status: "complete" },
|
||||
undefined,
|
||||
];
|
||||
for (const coverage of coverages) {
|
||||
const parent = analysisViewNativeEvidence(coverage);
|
||||
const parentBefore = JSON.stringify(parent);
|
||||
const recorded = session.recordEvidence(parent);
|
||||
if (!recorded.ok) throw recorded.error;
|
||||
const outputs = [];
|
||||
for (const source of [
|
||||
{ kind: "inline", evidence: parent },
|
||||
{ kind: "retained-evidence", evidence_id: parent.evidence_id },
|
||||
]) {
|
||||
const response = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source,
|
||||
view: { kind: "native", facet: "callees", offset: 0, limit: 1 },
|
||||
},
|
||||
});
|
||||
expect(response.isError).not.toBe(true);
|
||||
expect(
|
||||
ajv.validate(outputSchema, response.structuredContent),
|
||||
JSON.stringify(ajv.errors),
|
||||
).toBe(true);
|
||||
const evidence = parseEvidence(response.structuredContent);
|
||||
expect(evidence.relationship_coverage).toEqual(
|
||||
parent.relationship_coverage,
|
||||
);
|
||||
expect(evidence.normalized_result).toMatchObject({
|
||||
item: [],
|
||||
coverage: { total: 0, exhausted: true },
|
||||
limitations: expect.arrayContaining(["Fixture dossier caveat"]),
|
||||
});
|
||||
expect(session.evidenceById(evidence.evidence_id)).toEqual(evidence);
|
||||
outputs.push(evidence);
|
||||
}
|
||||
expect(outputs[0]).toEqual(outputs[1]);
|
||||
expect(JSON.stringify(parent)).toBe(parentBefore);
|
||||
}
|
||||
});
|
||||
|
||||
it("advertises typed module references and preserves portable/retained Evidence parity", async () => {
|
||||
const { client, session } = await connect();
|
||||
const parent = analysisViewJavaScriptEvidence(
|
||||
|
||||
Vendored
+21
@@ -13,6 +13,27 @@ import {
|
||||
import { createJavaScriptSemanticGraph } from "../../src/domain/javascript/javascriptSemanticGraph.js";
|
||||
import type { BinaryLayout } from "../../src/domain/native/binaryLayout.js";
|
||||
import { binaryLayoutSchema } from "../../src/domain/native/binaryLayout.js";
|
||||
import { ghidraFunctionDossier } from "../../src/domain/ghidraValues.fixture.js";
|
||||
import { functionDossierSchema } from "../../src/domain/hopperValues.js";
|
||||
import type { RelationshipCoverage } from "../../src/domain/relationshipCoverage.js";
|
||||
|
||||
/** Canonical portable Evidence; no native provider is started by this fixture. */
|
||||
export const analysisViewNativeEvidence = (
|
||||
relationshipCoverage?: RelationshipCoverage,
|
||||
): Evidence =>
|
||||
createEvidence(
|
||||
{ path: "/fixtures/native.exe", format: "pe", sha256: "a".repeat(64) },
|
||||
{ id: "fixture", name: "Fixture", version: "1" },
|
||||
{
|
||||
operation: "analyze_function",
|
||||
parameters: { address: "0x401000" },
|
||||
result: {
|
||||
...functionDossierSchema.parse(ghidraFunctionDossier()),
|
||||
limitations: ["Fixture dossier caveat"],
|
||||
},
|
||||
...(relationshipCoverage === undefined ? {} : { relationshipCoverage }),
|
||||
},
|
||||
);
|
||||
|
||||
const hex = (value: number): string =>
|
||||
value === 0 ? "0x0" : `0x${value.toString(16)}`;
|
||||
|
||||
Reference in new issue
Block a user