Files
rea/src/domain/analysisView/analysisView.test.ts
T

1421 lines
44 KiB
TypeScript

import { boundedViewMessages } from "./analysisViewCoverage.js";
import { expect, it } from "vitest";
import { functionDossierSchema } from "../hopperValues.js";
import { ghidraFunctionDossier } from "../ghidraValues.fixture.js";
import { JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE } from "../../contracts/javascript/javascriptRuntimeReconciliationExample.js";
import { ARTIFACT_COMPARISON_EXAMPLE } from "../../contracts/artifactComparisonExample.js";
import {
analysisViewBindJavaScriptGraphs,
analysisViewJavaScriptAnalysis,
analysisViewJavaScriptAnalysisWithSource,
analysisViewLayoutEvidence,
analysisViewLayoutFixture,
} from "../../../tests/fixtures/analysisView.js";
import {
createJavaScriptApplicationGraph,
createJavaScriptApplicationNode,
createImmutableJavaScriptApplicationGraphSteps,
} from "../javascript/javascriptApplicationGraph.js";
import {
createJavaScriptSemanticGraph,
createImmutableJavaScriptSemanticGraphSteps,
} from "../javascript/javascriptSemanticGraph.js";
import {
completeWithinViewCoverage,
pageViewCoverage,
} from "./analysisViewCoverage.js";
import { sourceMapReference } from "../javascript/javascriptApplicationGraph.fixture.js";
import { createArtifactInspection } from "../artifactInspection.js";
import { javascriptModulePaths } from "./javascriptApplicationView.js";
import {
inspectAnalysisViewInputSchema,
projectAnalysisView,
type AnalysisViewParent,
} from "./analysisView.js";
const layoutParent = (): AnalysisViewParent => {
const evidence = analysisViewLayoutEvidence();
return {
evidenceId: evidence.evidence_id,
operation: evidence.operation,
normalizedResult: evidence.normalized_result,
limitations: evidence.limitations,
};
};
const javascriptParent = (
analysis = analysisViewJavaScriptAnalysis(),
): AnalysisViewParent => ({
evidenceId: JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE.evidence_id,
operation: "analyze_javascript_application",
normalizedResult: analysis,
limitations: JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE.limitations,
});
const nativeParent = (
result: unknown = ghidraFunctionDossier(),
): AnalysisViewParent => ({
evidenceId: "ev_" + "b".repeat(64),
operation: "analyze_function",
normalizedResult: result,
limitations: ["Retained provider limitation"],
artifact: { path: "/fixtures/example.exe", sha256: "a".repeat(64) },
});
it("projects native procedure and bounded assembly and high-pcode with exact parent identity", () => {
const parent = nativeParent();
const procedure = projectAnalysisView(parent, {
kind: "native",
facet: "procedure",
offset: 0,
limit: 64,
});
if (!procedure.ok) throw procedure.error;
expect(procedure.value).toMatchObject({
kind: "native",
parent_operation: "analyze_function",
artifact: parent.artifact,
item: { address: "0x401000" },
});
expect(procedure.value.view_digest).not.toBe(parent.evidenceId.slice(3));
const assembly = projectAnalysisView(parent, {
kind: "native",
facet: "assembly",
offset: 0,
limit: 1,
});
if (!assembly.ok) throw assembly.error;
expect(assembly.value).toMatchObject({
coverage: { examined: 1, total: 2, next_offset: 1 },
item: ["0x401000: CALL 0x401020"],
});
const flow = projectAnalysisView(parent, {
kind: "native",
facet: "value_flow_operations",
offset: 0,
limit: 1,
});
if (!flow.ok) throw flow.error;
expect(flow.value).toMatchObject({ item: [{ opcode: "COPY" }] });
});
it("rejects invalid native parents, unsupported source operations and singleton offsets", () => {
const view = {
kind: "native",
facet: "assembly",
offset: 0,
limit: 4,
} as const;
expect(projectAnalysisView(nativeParent({}), view)).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
expect(
projectAnalysisView({ ...nativeParent(), artifact: null }, view),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
expect(projectAnalysisView(layoutParent(), view)).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
expect(projectAnalysisView(javascriptParent(), view)).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
expect(
projectAnalysisView(nativeParent(), {
kind: "native",
facet: "procedure",
offset: 1,
limit: 2,
}),
).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } });
});
it("preserves native API limitations and unavailable residual unknowns", () => {
const original = functionDossierSchema.parse(ghidraFunctionDossier());
const view = {
kind: "native",
facet: "procedure",
offset: 0,
limit: 64,
} as const;
const unavailable = projectAnalysisView(
nativeParent({
...original,
native_api: {
available: false,
reason: "No decompiler",
residual_unknowns: ["Calling convention unresolved"],
},
}),
view,
);
if (!unavailable.ok) throw unavailable.error;
expect(unavailable.value.unknowns).toContain("Calling convention unresolved");
const available = projectAnalysisView(nativeParent(), view);
if (!available.ok) throw available.error;
if (!original.native_api?.available) throw new Error("expected API fixture");
expect(available.value.limitations).toEqual(
expect.arrayContaining(original.native_api.limitations),
);
});
it("rejects artifact summary views when inspection substep coverage count disagrees", () => {
const source = ARTIFACT_COMPARISON_EXAMPLE.left;
const inspection = createArtifactInspection(source);
const parent: AnalysisViewParent = {
evidenceId: `ev_${"a".repeat(64)}`,
operation: "inspect_artifact",
normalizedResult: {
...inspection,
coverage: {
...inspection.coverage,
substeps_completed: inspection.substeps.length + 1,
},
},
subject: source.subject,
limitations: [],
};
expect(projectAnalysisView(parent, { kind: "summary" })).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
});
it("pages every retained dossier collection and the native API record", () => {
const original = functionDossierSchema.parse(ghidraFunctionDossier());
const page = (
facet:
| "basic_blocks"
| "comments"
| "unresolved_calls"
| "referenced_strings"
| "referenced_names"
| "swift_string_literals",
result: unknown = original,
) => {
const view = projectAnalysisView(nativeParent(result), {
kind: "native",
facet,
offset: 0,
limit: 64,
});
if (!view.ok) throw view.error;
return view.value;
};
expect(page("basic_blocks")).toMatchObject({
item: original.basic_blocks,
coverage: { total: original.basic_blocks.length, exhausted: true },
});
expect(page("referenced_strings")).toMatchObject({
item: original.referenced_strings,
});
expect(
page("comments", {
...original,
comments: [{ address: "0x401000", kind: "comment", text: "entry" }],
}),
).toMatchObject({ item: [{ text: "entry" }] });
expect(page("referenced_names")).toMatchObject({ coverage: { total: 0 } });
const literal = {
source_address: "0x401004",
value: "SleepDisabled",
representation: "small",
address: null,
registers: ["x0", "x1"],
words: ["0x7369447065656c53", "0xed000064656c6261"],
};
expect(
page("swift_string_literals", {
...original,
swift_string_literals: [literal],
}),
).toMatchObject({ item: [literal], coverage: { total: 1 } });
expect(page("swift_string_literals")).toMatchObject({
item: { available: false, reason: "not recorded" },
});
const { unresolved_calls: _omitted, ...withoutUnresolved } = original;
expect(
page("unresolved_calls", {
...withoutUnresolved,
native_value_flow: {
available: false,
reason: "No high p-code",
limitations: [],
},
}),
).toMatchObject({ item: { available: false, reason: "not recorded" } });
const api = projectAnalysisView(nativeParent(), {
kind: "native",
facet: "native_api",
offset: 0,
limit: 64,
});
if (!api.ok) throw api.error;
expect(api.value).toMatchObject({
item: original.native_api,
coverage: { status: "complete-within-view" },
});
});
it("reports the malformed native dossier field", () => {
const result = projectAnalysisView(
nativeParent({
...functionDossierSchema.parse(ghidraFunctionDossier()),
pseudocode: 12,
}),
{
kind: "native",
facet: "pseudocode",
offset: 0,
limit: 64,
},
);
expect(result).toMatchObject({
ok: false,
error: {
issues: expect.arrayContaining([
expect.objectContaining({
path: ["source", "normalized_result", "pseudocode"],
}),
]),
},
});
});
it("native pseudocode pages do not split surrogate pairs", () => {
const original = functionDossierSchema.parse(ghidraFunctionDossier());
const parent = nativeParent({ ...original, pseudocode: "ab😀cd" });
expect(
projectAnalysisView(parent, {
kind: "native",
facet: "pseudocode",
offset: 3,
limit: 2,
}),
).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } });
const safe = projectAnalysisView(parent, {
kind: "native",
facet: "pseudocode",
offset: 2,
limit: 3,
});
if (!safe.ok) throw safe.error;
expect(safe.value).toMatchObject({
item: { text: "😀c", unit: "utf16-code-units" },
coverage: { examined: 3, next_offset: 5 },
});
});
it.each([
{ kind: "page", collection: "sections", offset: 0, limit: 500 },
{ kind: "native", facet: "assembly", offset: 0, limit: 500 },
])(
"accepts caller-selected page sizes and rejects malformed bounds: $kind",
(view) => {
const input = {
source: {
kind: "retained-evidence",
evidence_id: `ev_${"a".repeat(64)}`,
},
view,
};
expect(inspectAnalysisViewInputSchema.safeParse(input).success).toBe(true);
for (const limit of [0, -1, 0.5, Infinity, Number.MAX_SAFE_INTEGER + 1])
expect(
inspectAnalysisViewInputSchema.safeParse({
...input,
view: { ...input.view, limit },
}).success,
).toBe(false);
},
);
it("projects layout summary, facet, item, and stable pages", () => {
const parent = layoutParent();
const layout = analysisViewLayoutFixture();
const summary = projectAnalysisView(parent, { kind: "summary" });
if (!summary.ok) throw summary.error;
expect(summary.value).toMatchObject({
kind: "summary",
parent_operation: "inspect_binary_layout",
summary: {
format: "elf",
counts: { sections: 3, symbols: 3, segments: 0, relocations: 0 },
},
coverage: completeWithinViewCoverage(6, 6),
});
const mitigations = projectAnalysisView(parent, {
kind: "facet",
facet: "mitigations",
});
if (!mitigations.ok) throw mitigations.error;
if (mitigations.value.kind !== "facet")
throw new Error("expected facet view");
expect(mitigations.value.facet).toEqual(layout.mitigations);
const data = projectAnalysisView(parent, {
kind: "item",
collection: "sections",
selector: { name: ".data" },
});
if (!data.ok) throw data.error;
if (data.value.kind !== "item") throw new Error("expected item view");
expect(data.value.item).toMatchObject({
index: 2,
name: { display: ".data" },
});
const indexed = projectAnalysisView(parent, {
kind: "item",
collection: "sections",
selector: { index: 0 },
});
if (!indexed.ok) throw indexed.error;
expect(indexed.value.view_digest).not.toBe(data.value.view_digest);
expect(indexed.value.view_digest).not.toBe(parent.evidenceId.slice(3));
const page = projectAnalysisView(parent, {
kind: "page",
collection: "sections",
offset: 0,
limit: 2,
});
if (!page.ok) throw page.error;
expect(page.value).toMatchObject({
kind: "page",
coverage: {
status: "page",
examined: 2,
total: 3,
next_offset: 2,
exhausted: false,
},
});
if (page.value.kind !== "page") throw new Error("expected page view");
expect(page.value.items).toHaveLength(2);
const exhausted = projectAnalysisView(parent, {
kind: "page",
collection: "symbols",
offset: 3,
limit: 8,
});
if (!exhausted.ok) throw exhausted.error;
expect(exhausted.value).toMatchObject({
items: [],
coverage: pageViewCoverage(3, 0, 3),
});
});
it("rejects ambiguous layout names with candidate indexes", () => {
const parent = layoutParent();
const result = projectAnalysisView(parent, {
kind: "item",
collection: "sections",
selector: { name: ".text" },
});
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
issues: [
{
path: ["view", "selector", "name"],
expected: [{ index: 0 }, { index: 1 }],
},
],
},
});
});
it("counts JavaScript summary coverage in module units", () => {
const analysis = analysisViewJavaScriptAnalysisWithSource();
const moduleNode = analysis.graph.nodes[0];
const observation = moduleNode?.observations[0];
if (moduleNode === undefined || observation === undefined)
throw new Error("missing module observation");
const packageNode = createJavaScriptApplicationNode({
kind: "artifact",
identity: moduleNode.identity,
observations: moduleNode.observations.map((item) => ({
label: item.label,
properties: item.properties,
evidence: item.evidence,
...(item.source_map_reference === null
? {}
: { source_map_reference: item.source_map_reference }),
})),
});
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: [moduleNode.node_id],
nodes: [moduleNode, packageNode],
edges: [],
coverage: analysis.graph.coverage,
limitations: analysis.graph.limitations,
});
const parent = javascriptParent(
analysisViewBindJavaScriptGraphs(analysis, graph),
);
const summary = projectAnalysisView(parent, { kind: "summary" });
if (!summary.ok) throw summary.error;
expect(summary.value.coverage).toEqual({
status: "complete-within-view",
examined: 1,
total: 1,
next_offset: null,
exhausted: true,
});
const page = projectAnalysisView(parent, {
kind: "page",
collection: "modules",
offset: 0,
limit: 8,
});
if (!page.ok) throw page.error;
expect(page.value.coverage).toMatchObject({ examined: 1, total: 1 });
});
it("projects JavaScript summary and preserves selected module observations", () => {
const analysis = analysisViewJavaScriptAnalysisWithSource();
const parent = javascriptParent(analysis);
const summary = projectAnalysisView(parent, { kind: "summary" });
if (!summary.ok) throw summary.error;
if (summary.value.kind !== "summary") throw new Error("expected summary");
expect(summary.value.summary).toMatchObject({
format: "directory",
limitation_count: summary.value.limitations.length,
});
expect(JSON.stringify(summary.value)).not.toMatch(/"graph"/);
expect(summary.value.summary).not.toHaveProperty("semantic_graph");
const item = projectAnalysisView(parent, {
kind: "item",
collection: "modules",
selector: { path: "renderer.js" },
});
if (!item.ok) throw item.error;
const moduleNode = analysis.graph.nodes[0];
if (moduleNode === undefined) throw new Error("missing module");
expect(javascriptModulePaths(moduleNode)).toEqual(["renderer.js"]);
if (item.value.kind !== "item") throw new Error("expected item view");
expect(item.value.item).toMatchObject({
path: "renderer.js",
kind: "javascript-asset",
});
expect(JSON.stringify(item.value.item)).toContain("export const secret = 1;");
expect(JSON.stringify(item.value.item)).toContain("should not leak");
const page = projectAnalysisView(parent, {
kind: "page",
collection: "modules",
offset: 0,
limit: 8,
});
if (!page.ok) throw page.error;
if (page.value.kind !== "page") throw new Error("expected page view");
expect(page.value.items).toEqual([
{
node_id: analysis.graph.nodes[0]?.node_id,
kind: "javascript-asset",
path: "renderer.js",
},
]);
expect(page.value.coverage).toMatchObject({
status: "page",
examined: 1,
total: 1,
next_offset: null,
exhausted: true,
});
});
it("projects typed module facts without source bodies and keeps source coverage independent of paging", () => {
const analysis = analysisViewJavaScriptAnalysisWithSource();
const parent = javascriptParent(analysis);
const input = inspectAnalysisViewInputSchema.parse({
source: { kind: "retained-evidence", evidence_id: parent.evidenceId },
view: {
kind: "javascript",
collection: "modules",
projection: "references",
where: { path: "renderer.js" },
limit: 1,
},
});
const projected = projectAnalysisView(parent, input.view);
if (!projected.ok) throw projected.error;
if (projected.value.kind !== "javascript")
throw new Error("expected JavaScript view");
const node = analysis.graph.nodes[0];
if (!node) throw new Error("missing module");
expect(projected.value.rows).toEqual([
{
node_id: node.node_id,
kind: node.kind,
path: "renderer.js",
identity: node.identity,
retained_source_range_count: 0,
basis: [
{ authority: "artifact-bytes", state: "observed", confidence: "exact" },
],
},
]);
expect(projected.value.coverage).toMatchObject({ total: 1, exhausted: true });
expect(projected.value.source_coverage).toMatchObject({
application: analysis.graph.coverage,
semantic: { status: analysis.semantic_graph.coverage.status },
});
expect(JSON.stringify(projected.value.rows)).not.toMatch(
/secret|should not leak|observations/,
);
const row = projected.value.rows[0];
if (!row) throw new Error("missing reference");
const retainedIdentity = structuredClone(node.identity);
if (row.identity.strategy !== "content-digest")
throw new Error("expected digest identity");
row.identity.sha256 = "e".repeat(64);
expect(node.identity).toEqual(retainedIdentity);
const empty = projectAnalysisView(parent, {
...input.view,
kind: "javascript",
collection: "modules",
projection: "references",
where: { kind: "javascript-module" },
offset: 0,
limit: 1,
});
if (!empty.ok) throw empty.error;
expect(empty.value).toMatchObject({
rows: [],
coverage: { total: 0, exhausted: true },
});
});
it("counts retained range observations including repeats and skips unavailable locations", () => {
const analysis = analysisViewJavaScriptAnalysis();
const original = analysis.graph.nodes[0];
const observed = original?.observations[0];
if (!original || !observed) throw new Error("missing module observation");
const range = {
kind: "source-range",
source: "renderer.js",
start: { line: 1, column: 0 },
end: { line: 1, column: 8 },
};
const node = createJavaScriptApplicationNode({
kind: original.kind,
identity: original.identity,
observations: [
...["first retained observation", "second retained observation"].map(
(label) => ({
label,
properties: {},
evidence: {
...observed.evidence,
location: { available: true, value: range },
},
}),
),
{
label: "unavailable location",
properties: {},
evidence: {
...observed.evidence,
authority: "unknown",
state: "unavailable",
confidence: "unknown",
location: {
available: false,
reason: "not-observed",
detail: "Location was not recorded.",
},
limitations: ["Location was not recorded."],
},
},
],
});
const { graph_id: _graphId, ...application } = analysis.graph;
const graph = createJavaScriptApplicationGraph({
...application,
nodes: [node],
root_node_ids: [node.node_id],
});
const parent = javascriptParent(
analysisViewBindJavaScriptGraphs(analysis, graph),
);
const references = projectAnalysisView(parent, {
kind: "javascript",
collection: "modules",
projection: "references",
where: {},
offset: 0,
limit: 1,
});
const item = projectAnalysisView(parent, {
kind: "item",
collection: "modules",
selector: { node_id: node.node_id },
});
if (!references.ok) throw references.error;
if (!item.ok) throw item.error;
expect(references.value).toMatchObject({
rows: [{ node_id: node.node_id, retained_source_range_count: 2 }],
});
expect(item.value).toMatchObject({ item: { source_ranges: [range, range] } });
});
it("validates typed module selectors without coercing null or empty paths", () => {
const source = {
kind: "retained-evidence",
evidence_id: javascriptParent().evidenceId,
};
const view = {
kind: "javascript",
collection: "modules",
projection: "references",
};
for (const path of [null, "", "renderer.js"])
expect(
inspectAnalysisViewInputSchema.safeParse({
source,
view: { ...view, where: { path } },
}).success,
).toBe(true);
for (const where of [
{ path: 1 },
{ kind: "electron-main" },
{ glob: "*.js" },
])
expect(
inspectAnalysisViewInputSchema.safeParse({
source,
view: { ...view, where },
}).success,
).toBe(false);
expect(
projectAnalysisView(layoutParent(), {
...view,
kind: "javascript",
collection: "modules",
projection: "references",
where: {},
offset: 0,
limit: 1,
}).ok,
).toBe(false);
});
it("keeps mixed observation support distinct instead of promoting a module to observed", () => {
const analysis = analysisViewJavaScriptAnalysis();
const original = analysis.graph.nodes[0];
const observed = original?.observations[0];
if (!original || !observed) throw new Error("missing module observation");
const inferred = {
...observed.evidence,
authority: "static-relationship-inference" as const,
state: "inferred" as const,
confidence: "low" as const,
limitations: ["Static candidate; runtime loading is unknown."],
};
const node = createJavaScriptApplicationNode({
kind: original.kind,
identity: original.identity,
observations: [
{
label: observed.label,
properties: observed.properties,
evidence: observed.evidence,
},
{ label: "candidate one", properties: {}, evidence: inferred },
{ label: "candidate two", properties: {}, evidence: inferred },
],
});
const { graph_id: _graphId, ...application } = analysis.graph;
const graph = createJavaScriptApplicationGraph({
...application,
nodes: [node],
root_node_ids: [node.node_id],
});
const result = projectAnalysisView(
javascriptParent(analysisViewBindJavaScriptGraphs(analysis, graph)),
{
kind: "javascript",
collection: "modules",
projection: "references",
where: {},
offset: 0,
limit: 1,
},
);
if (!result.ok) throw result.error;
if (result.value.kind !== "javascript")
throw new Error("expected references");
expect(result.value.rows[0]?.basis).toHaveLength(2);
expect(result.value.rows[0]?.basis).toEqual(
expect.arrayContaining([
{ authority: "artifact-bytes", state: "observed", confidence: "exact" },
{
authority: "static-relationship-inference",
state: "inferred",
confidence: "low",
},
]),
);
});
it("rejects ambiguous JavaScript module paths with candidate node ids", () => {
const analysis = analysisViewJavaScriptAnalysis();
const first = analysis.graph.nodes[0];
if (first === undefined) throw new Error("missing module");
const duplicate = createJavaScriptApplicationNode({
kind: first.kind,
identity: {
strategy: "content-digest",
stability: "global-exact",
sha256: "c".repeat(64),
},
observations: first.observations.map((observation) => ({
label: observation.label,
properties: observation.properties,
evidence: {
...observation.evidence,
artifact: {
...observation.evidence.artifact,
sha256: "c".repeat(64),
artifact_id: `art_${"c".repeat(64)}`,
},
},
})),
});
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: [first.node_id, duplicate.node_id],
nodes: [first, duplicate],
edges: [],
coverage: analysis.graph.coverage,
limitations: analysis.graph.limitations,
});
const parent = javascriptParent(
analysisViewBindJavaScriptGraphs(analysis, graph),
);
const result = projectAnalysisView(parent, {
kind: "item",
collection: "modules",
selector: { path: "renderer.js" },
});
expect(result.ok).toBe(false);
if (result.ok) throw new Error("expected ambiguous path");
expect(result.error).toMatchObject({
_tag: "AnalysisInputError",
issues: [
{
path: ["view", "selector", "path"],
expected: expect.arrayContaining([
{ node_id: first.node_id },
{ node_id: duplicate.node_id },
]),
},
],
});
const view = {
kind: "javascript",
collection: "modules",
projection: "references",
where: { path: "renderer.js" },
offset: 0,
limit: 1,
} as const;
const ids: string[] = [];
for (const offset of [0, 1, 2, Number.MAX_SAFE_INTEGER]) {
const page = projectAnalysisView(parent, { ...view, offset });
if (!page.ok) throw page.error;
if (page.value.kind !== "javascript")
throw new Error("expected references");
ids.push(...page.value.rows.map(({ node_id }) => node_id));
expect(page.value.coverage).toMatchObject({
total: 2,
next_offset: offset === 0 ? 1 : null,
exhausted: offset !== 0,
});
}
expect(ids).toEqual(graph.nodes.map(({ node_id }) => node_id));
});
it("pages actual modules, including unknown and empty paths, without unrelated roles", () => {
const analysis = analysisViewJavaScriptAnalysis();
const original = analysis.graph.nodes[0];
if (original === undefined) throw new Error("missing module");
const nodes = [
original,
...(
[
"asar-entry",
"electron-renderer",
"javascript-module",
"source-module",
] as const
).map((kind) =>
createJavaScriptApplicationNode({
kind,
identity:
kind === "source-module"
? {
strategy: "source-map-original",
source_root: null,
stability: "source-map-exact",
original_source: "",
source_map_sha256: "c".repeat(64),
source_sha256: null,
}
: kind === "javascript-module"
? {
strategy: "artifact-local-key",
stability: "artifact-version",
artifact_sha256: "c".repeat(64),
namespace: kind,
key: "module",
}
: original.identity,
observations: original.observations.map((observation) => ({
...(kind === "source-module"
? {
source_map_reference: sourceMapReference(
"",
null,
"renderer.js",
),
}
: {}),
label: observation.label,
properties: {},
evidence:
kind === "javascript-module" || kind === "source-module"
? {
...observation.evidence,
artifact: {
...observation.evidence.artifact,
sha256: "c".repeat(64),
artifact_id: `art_${"c".repeat(64)}`,
},
...(kind === "javascript-module"
? {
state: "unknown",
confidence: "unknown",
limitations: ["Source location unavailable"],
location: {
available: false,
reason: "unknown",
detail: "Source location unavailable",
},
}
: {}),
}
: observation.evidence,
})),
}),
),
];
const graph = createJavaScriptApplicationGraph({
schema: "JavaScriptApplicationGraph",
root_node_ids: nodes.map((node) => node.node_id),
nodes,
edges: [],
coverage: analysis.graph.coverage,
limitations: [],
});
const parent = javascriptParent(
analysisViewBindJavaScriptGraphs(analysis, graph),
);
const page = projectAnalysisView(parent, {
kind: "page",
collection: "modules",
offset: 0,
limit: 1000,
});
if (!page.ok) throw page.error;
expect(page.value).toMatchObject({
coverage: { total: 3, examined: 3, exhausted: true },
});
if (page.value.kind !== "page") throw new Error("expected page");
expect(page.value.items).toEqual(
expect.arrayContaining([
expect.objectContaining({ kind: "javascript-module", path: null }),
expect.objectContaining({ kind: "source-module", path: "" }),
]),
);
const emptyPath = projectAnalysisView(parent, {
kind: "item",
collection: "modules",
selector: { path: "" },
});
for (const [path, kind] of [
[null, "javascript-module"],
["", "source-module"],
] as const) {
const references = projectAnalysisView(parent, {
kind: "javascript",
collection: "modules",
projection: "references",
where: { path },
offset: 0,
limit: 1,
});
if (!references.ok) throw references.error;
expect(references.value).toMatchObject({
rows: [{ path, kind }],
coverage: { total: 1, exhausted: true },
});
}
expect(emptyPath.ok).toBe(true);
const role = nodes.find((node) => node.kind === "electron-renderer");
if (role === undefined) throw new Error("missing role");
expect(
projectAnalysisView(parent, {
kind: "item",
collection: "modules",
selector: { node_id: role.node_id },
}).ok,
).toBe(false);
});
const complete = <Value>(steps: Generator<void, Value>): Value => {
let step = steps.next();
while (!step.done) step = steps.next();
return step.value;
};
it("reuses authenticated graphs while rejecting malformed metadata and imported companions", () => {
const analysis = analysisViewJavaScriptAnalysis();
const { graph_id: _applicationId, ...application } = analysis.graph;
const graph = complete(
createImmutableJavaScriptApplicationGraphSteps(application),
);
const { graph_id: _semanticId, ...semantic } = analysis.semantic_graph;
const semanticGraph = complete(
createImmutableJavaScriptSemanticGraphSteps({
...semantic,
application_graph_id: graph.graph_id,
}),
);
const owned = { ...analysis, graph, semantic_graph: semanticGraph };
expect(
projectAnalysisView(javascriptParent(owned), { kind: "summary" }).ok,
).toBe(true);
for (const malformed of [
{ ...owned, root_artifact_sha256: "d".repeat(64) },
{
...owned,
statistics: { ...owned.statistics, parsed_javascript_files: -1 },
},
{
...owned,
semantic_graph: Object.freeze({
...semanticGraph,
application_graph_id: "jag_" + "d".repeat(64),
}),
},
]) {
const result = projectAnalysisView(
{ ...javascriptParent(owned), normalizedResult: malformed },
{ kind: "summary" },
);
expect(result).toMatchObject({
ok: false,
error: {
_tag: "AnalysisInputError",
issues: expect.arrayContaining([
expect.objectContaining({
path: expect.arrayContaining(["source", "normalized_result"]),
}),
]),
},
});
}
});
it("retains semantic coverage and limitations in a summary", () => {
const analysis = analysisViewJavaScriptAnalysis();
const { graph_id: _graphId, ...semantic } = analysis.semantic_graph;
const semanticGraph = createJavaScriptSemanticGraph({
...semantic,
coverage: {
...semantic.coverage,
status: "partial",
truncated: true,
omitted_nodes: null,
omitted_relations: null,
limits: [
{ name: "semantic_graph_node_ceiling", value: 100000, unit: "items" },
],
},
limitations: ["Some semantic nodes could not be retained."],
});
const result = projectAnalysisView(
javascriptParent({ ...analysis, semantic_graph: semanticGraph }),
{ kind: "summary" },
);
if (!result.ok) throw result.error;
expect(result.value).toMatchObject({
summary: {
limitation_count: 1,
coverage: { semantic: { status: "partial", truncated: true } },
},
limitations: ["Some semantic nodes could not be retained."],
unknowns: expect.arrayContaining([
"semantic_graph.coverage.status is partial",
]),
});
});
it.each([63, 64, 65, 10_000])(
"bounds and deduplicates %i messages in JavaScript and layout views",
(count) => {
const messages = Array.from(
{ length: count },
(_, index) => `Module ${String(index).padStart(5, "0")} was not parsed.`,
);
const analysis = analysisViewJavaScriptAnalysis();
const { graph_id: _graphId, ...semantic } = analysis.semantic_graph;
const parent = javascriptParent({
...analysis,
limitations: messages,
semantic_graph: createJavaScriptSemanticGraph({
...semantic,
limitations: messages.slice(0, 1),
}),
});
const duplicatedParent = { ...parent, limitations: messages };
const notice = (
omitted: number,
field: "limitations" | "unknowns",
evidenceId: string,
) =>
`${String(omitted)} additional ${field} omitted from this view; consult source parent Evidence ${evidenceId} for the complete list (the caller-supplied record, or its retaining session).`;
for (const view of [
{ kind: "summary" },
{ kind: "page", collection: "modules", offset: 0, limit: 1 },
] as const) {
const result = projectAnalysisView(duplicatedParent, view);
if (!result.ok) throw result.error;
if (result.value.kind === "summary")
expect(result.value.summary.limitation_count).toBe(count);
if (count <= 65) {
expect(result.value.limitations).toEqual(messages);
expect(result.value.unknowns).toEqual(
[...messages, "semantic_graph.coverage.status is unknown"].sort(),
);
} else {
const limitations = result.value.limitations;
const unknowns = result.value.unknowns;
expect(limitations.length).toBeGreaterThan(65);
expect(limitations.at(-1)).toBe(
notice(
count - limitations.length + 1,
"limitations",
parent.evidenceId,
),
);
expect(unknowns.length).toBeGreaterThan(65);
expect(unknowns.at(-1)).toBe(
notice(
count + 1 - unknowns.length + 1,
"unknowns",
parent.evidenceId,
),
);
}
}
const layout = layoutParent();
const layoutWithMessages = {
...layout,
limitations: messages,
normalizedResult: {
...analysisViewLayoutFixture(),
limitations: messages,
},
};
for (const view of [
{ kind: "summary" },
{ kind: "page", collection: "sections", offset: 0, limit: 1 },
] as const) {
const result = projectAnalysisView(layoutWithMessages, view);
if (!result.ok) throw result.error;
if (count <= 65) expect(result.value.limitations).toEqual(messages);
else {
expect(result.value.limitations.length).toBeGreaterThan(65);
expect(result.value.limitations.at(-1)).toBe(
notice(
count - result.value.limitations.length + 1,
"limitations",
layout.evidenceId,
),
);
}
}
},
);
it.each(["partial", "unknown"] as const)(
"preserves %s parent graph completeness when a module page is exhausted",
(status) => {
const analysis = analysisViewJavaScriptAnalysis();
const first = analysis.graph.nodes[0];
if (first === undefined) throw new Error("missing module");
const unknown = createJavaScriptApplicationNode({
kind: "unknown",
identity: {
strategy: "observation-fingerprint",
stability: "observation-only",
observation_sha256: "8".repeat(64),
scope: "unclassified module",
},
observations: first.observations.map((observation) => ({
label: "unclassified module",
properties: {},
evidence: observation.evidence,
})),
});
const messages = [
...Array.from(
{ length: 65 },
(_, index) =>
`Module ${String(index).padStart(5, "0")} was not parsed.`,
),
"z".repeat(300_000),
];
const { graph_id: _applicationId, ...application } = analysis.graph;
const graph = createJavaScriptApplicationGraph({
...application,
nodes: [...application.nodes, unknown],
limitations: messages.slice(0, 1),
coverage: { ...application.coverage, status, omitted_count: null },
});
const bound = analysisViewBindJavaScriptGraphs(analysis, graph);
const { graph_id: _semanticId, ...semantic } = bound.semantic_graph;
const parent = javascriptParent({
...bound,
limitations: messages,
semantic_graph: createJavaScriptSemanticGraph({
...semantic,
limitations: messages.slice(0, 1),
}),
});
for (const view of [
{ kind: "page", collection: "modules", offset: 0, limit: 10 },
{
kind: "javascript",
collection: "modules",
projection: "references",
where: {},
offset: 0,
limit: 10,
},
{
kind: "javascript",
collection: "modules",
projection: "references",
where: { path: "not-retained.js" },
offset: 0,
limit: 10,
},
{
kind: "item",
collection: "modules",
selector: { path: "renderer.js" },
},
] as const) {
const result = projectAnalysisView(parent, view);
if (!result.ok) throw result.error;
expect(result.value.coverage.exhausted).toBe(true);
if (result.value.kind === "javascript")
expect(result.value.source_coverage).toMatchObject({
application: { status, omitted_count: null },
semantic: { status: "unknown" },
application_unknown_count: 1,
});
expect(result.value.unknowns).toEqual([
"1 application graph unknowns remain in the parent Evidence.",
...messages.slice(0, 65),
`graph.coverage.status is ${status}`,
"semantic_graph.coverage.status is unknown",
`1 additional unknowns omitted from this view; consult source parent Evidence ${parent.evidenceId} for the complete list (the caller-supplied record, or its retaining session).`,
]);
}
},
);
it("does not match unavailable layout names using a display placeholder", () => {
const layout = analysisViewLayoutFixture();
const section = layout.sections[2];
if (section === undefined) throw new Error("missing section");
const result = projectAnalysisView(
{
...layoutParent(),
normalizedResult: {
...layout,
sections: [
...layout.sections.slice(0, 2),
{
...section,
name: {
...section.name,
display: "<unavailable>",
unknown_reason: "Name bytes unavailable",
bytes_base64: null,
location: null,
},
},
],
},
},
{
kind: "item",
collection: "sections",
selector: { name: "<unavailable>" },
},
);
expect(result.ok).toBe(false);
});
it("rejects unsupported parent operations and incompatible collections", () => {
expect(
projectAnalysisView(layoutParent(), {
kind: "managed",
collection: "attributes",
offset: 0,
limit: 1,
}),
).toMatchObject({ ok: false, error: { _tag: "AnalysisInputError" } });
const unsupported = projectAnalysisView(
{
evidenceId: `ev_${"d".repeat(64)}`,
operation: "inspect_recorded_crash",
normalizedResult: { path: "/artifacts/crash.core" },
limitations: [],
},
{ kind: "summary" },
);
expect(unsupported).toMatchObject({
ok: false,
error: { _tag: "AnalysisUnsupportedTargetError" },
});
const modulesOnLayout = projectAnalysisView(layoutParent(), {
kind: "page",
collection: "modules",
offset: 0,
limit: 1,
});
expect(modulesOnLayout).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
const sectionsOnJs = projectAnalysisView(javascriptParent(), {
kind: "item",
collection: "sections",
selector: { name: ".text" },
});
expect(sectionsOnJs).toMatchObject({
ok: false,
error: { _tag: "AnalysisInputError" },
});
});
it("keeps the complete unique set when it fits the byte budget", () => {
const messages = ["a".repeat(2_400), "b".repeat(2_400)];
expect(
boundedViewMessages(messages, `ev_${"a".repeat(64)}`, "limitations"),
).toEqual(messages);
});
it("does not impose a message-count cutoff below the byte budget", () => {
const messages = Array.from(
{ length: 100 },
(_, index) => `message ${String(index).padStart(2, "0")}`,
);
expect(
boundedViewMessages(messages, `ev_${"a".repeat(64)}`, "limitations"),
).toEqual(messages.toSorted());
});
it("bounds repeated view messages even when fewer than 64 source strings are huge", () => {
const oversized = ["\u0000".repeat(300_000), "\\".repeat(300_000)];
const projected = boundedViewMessages(
oversized,
`ev_${"a".repeat(64)}`,
"limitations",
);
expect(projected).toHaveLength(1);
expect(projected[0]).toContain("2 additional limitations omitted");
expect(Buffer.byteLength(JSON.stringify(projected))).toBeLessThan(256 * 1024);
});
it("preserves selected native facet unavailability instead of an exhausted empty page", () => {
const parent = nativeParent({
...functionDossierSchema.parse(ghidraFunctionDossier()),
facet_availability: {
comments: { available: false, reason: "Typed comments unavailable" },
references: { available: false, reason: "Typed references unavailable" },
},
});
for (const [facet, reason] of [
["comments", "Typed comments unavailable"],
["incoming_references", "Typed references unavailable"],
] as const) {
const result = projectAnalysisView(parent, {
kind: "native",
facet,
offset: 0,
limit: 8,
});
if (!result.ok) throw result.error;
expect(result.value).toMatchObject({
item: { available: false, reason },
unknowns: expect.arrayContaining([reason]),
});
}
});
it("selects a unique content-deduplicated module by any retained path", () => {
const source = analysisViewJavaScriptAnalysisWithSource();
const original = source.graph.nodes[0];
const observation = original?.observations[0];
if (original === undefined || observation === undefined)
throw new Error("Missing module fixture");
const node = createJavaScriptApplicationNode({
kind: original.kind,
identity: original.identity,
observations: [
...original.observations.map(({ label, properties, evidence }) => ({
label,
properties,
evidence,
})),
{
evidence: observation.evidence,
label: "renderer-alias.js",
properties: { ...observation.properties, path: "renderer-alias.js" },
},
],
});
const { graph_id: _graphId, ...graphFacts } = source.graph;
const graph = createJavaScriptApplicationGraph({
...graphFacts,
nodes: [node],
});
const parent = javascriptParent(
analysisViewBindJavaScriptGraphs(source, graph),
);
for (const path of ["renderer.js", "renderer-alias.js"]) {
const selected = projectAnalysisView(parent, {
kind: "item",
collection: "modules",
selector: { path },
});
if (!selected.ok) throw selected.error;
expect(selected.value).toMatchObject({
kind: "item",
item: { node_id: node.node_id, path },
});
}
});