mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
866 lines
26 KiB
TypeScript
866 lines
26 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { createAnalysisProfile } from "../../domain/analysisProfile.js";
|
|
import type { RelationshipCoverage } from "../../domain/relationshipCoverage.js";
|
|
import {
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
} from "../../domain/analysisSnapshot.fixture.js";
|
|
import { createEvidence } from "../../domain/evidence.js";
|
|
import { createEvidenceBundle } from "../../domain/evidenceBundle.js";
|
|
import { ok } from "../../domain/result.js";
|
|
import { createAnalysisExecution } from "../AnalysisProvider.js";
|
|
import {
|
|
createAnalysisSnapshotWorkflowEntry,
|
|
parseAnalysisSnapshot,
|
|
serializeAnalysisSnapshot,
|
|
snapshotEvidenceForQuery,
|
|
snapshotBinding,
|
|
snapshotTarget,
|
|
} from "../../domain/analysisSnapshot.js";
|
|
import { REA_WORKFLOW_PROVIDER } from "../../domain/investigationProviders.js";
|
|
import { workflowAnalysisProfile } from "../WorkflowAnalysisProfile.js";
|
|
import type {
|
|
AnalysisOperation,
|
|
CapabilityDescriptor,
|
|
} from "../AnalysisProvider.js";
|
|
import {
|
|
AnalysisSnapshotCache,
|
|
SNAPSHOT_CACHE_ENTRY_CEILING,
|
|
isSnapshotCacheable,
|
|
} from "./AnalysisSnapshotCache.js";
|
|
import { enhancedOutputSchemas } from "../../contracts/toolOutputSchemaGroups.js";
|
|
|
|
it("exports independent nested query, workflow and Evidence payloads", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const result = { nested: [{ text: "ret" }], name: "main" };
|
|
const parameters = { address: "0x1000", document: "fixture" };
|
|
const workflowProfile = workflowAnalysisProfile(profile);
|
|
cache.record({
|
|
target,
|
|
profile,
|
|
operation: "procedure_info",
|
|
parameters,
|
|
execution: createAnalysisExecution(result, ANALYSIS_SNAPSHOT_PROVIDER, {
|
|
subject: target,
|
|
analysisProfile: profile,
|
|
rawResult: result,
|
|
}),
|
|
});
|
|
cache.recordWorkflow({
|
|
target,
|
|
profile,
|
|
operation: "analyze_function",
|
|
parameters,
|
|
execution: {
|
|
...createAnalysisExecution(result, REA_WORKFLOW_PROVIDER, {
|
|
subject: target,
|
|
rawResult: result,
|
|
}),
|
|
analysisProfile: workflowProfile,
|
|
},
|
|
});
|
|
const bundle = createEvidenceBundle([
|
|
createEvidence(target, ANALYSIS_SNAPSHOT_PROVIDER, {
|
|
operation: "procedure_info",
|
|
parameters,
|
|
result,
|
|
rawResult: result,
|
|
analysisProfile: profile,
|
|
}),
|
|
createEvidence(target, REA_WORKFLOW_PROVIDER, {
|
|
operation: "analyze_function",
|
|
confidence: "derived",
|
|
parameters,
|
|
result,
|
|
rawResult: result,
|
|
analysisProfile: workflowProfile,
|
|
}),
|
|
]);
|
|
const exported = cache.export(target, profile, bundle);
|
|
if (!exported.ok) throw exported.error;
|
|
const expected = serializeAnalysisSnapshot(exported.value);
|
|
const changed = JSON.stringify(exported.value).replaceAll(
|
|
'"ret"',
|
|
'"changed"',
|
|
);
|
|
// Mutate the returned graph itself, including every duplicated JSON result.
|
|
for (const payload of [
|
|
...exported.value.entries.flatMap(({ execution }) => [
|
|
execution.result,
|
|
execution.raw_result,
|
|
]),
|
|
...exported.value.workflow_entries.flatMap(({ execution }) => [
|
|
execution.result,
|
|
execution.raw_result,
|
|
]),
|
|
...exported.value.evidence_bundle.records.flatMap((record) => [
|
|
record.normalized_result,
|
|
record.raw_result,
|
|
]),
|
|
]) {
|
|
if (
|
|
typeof payload !== "object" ||
|
|
payload === null ||
|
|
Array.isArray(payload)
|
|
)
|
|
throw new Error("Expected nested fixture result");
|
|
Reflect.set(payload, "nested", [{ text: "changed" }]);
|
|
}
|
|
expect(JSON.stringify(exported.value)).toBe(changed);
|
|
const fresh = cache.export(target, profile, bundle);
|
|
if (!fresh.ok) throw fresh.error;
|
|
expect(serializeAnalysisSnapshot(fresh.value)).toBe(expected);
|
|
});
|
|
|
|
it("counts newly retained direct and workflow bindings when staging", () => {
|
|
const source = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const parameters = { address: "0x1000" };
|
|
const workflowProfile = workflowAnalysisProfile(profile, "trace_feature");
|
|
source.record({
|
|
target,
|
|
profile,
|
|
operation: "procedure_info",
|
|
parameters,
|
|
execution: createAnalysisExecution("direct", ANALYSIS_SNAPSHOT_PROVIDER, {
|
|
subject: target,
|
|
analysisProfile: profile,
|
|
}),
|
|
});
|
|
source.recordWorkflow({
|
|
target,
|
|
profile,
|
|
operation: "trace_feature",
|
|
parameters: { query: "feature" },
|
|
execution: {
|
|
...createAnalysisExecution("workflow", REA_WORKFLOW_PROVIDER, {
|
|
subject: target,
|
|
}),
|
|
analysisProfile: workflowProfile,
|
|
},
|
|
});
|
|
const bundle = createEvidenceBundle([
|
|
createEvidence(target, ANALYSIS_SNAPSHOT_PROVIDER, {
|
|
operation: "procedure_info",
|
|
parameters,
|
|
result: "direct",
|
|
analysisProfile: profile,
|
|
}),
|
|
createEvidence(target, REA_WORKFLOW_PROVIDER, {
|
|
operation: "trace_feature",
|
|
confidence: "derived",
|
|
parameters: { query: "feature" },
|
|
result: "workflow",
|
|
analysisProfile: workflowProfile,
|
|
}),
|
|
]);
|
|
const exported = source.export(target, profile, bundle);
|
|
if (!exported.ok) throw exported.error;
|
|
const snapshot = parseAnalysisSnapshot(
|
|
JSON.parse(serializeAnalysisSnapshot(exported.value)),
|
|
);
|
|
expect(snapshot.entries).toHaveLength(1);
|
|
expect(snapshot.workflow_entries).toHaveLength(1);
|
|
|
|
const workflowOnly = new AnalysisSnapshotCache();
|
|
expect(workflowOnly.stage({ ...snapshot, entries: [] })).toBe(1);
|
|
expect(workflowOnly.workflowEntries()).toHaveLength(1);
|
|
|
|
const staged = new AnalysisSnapshotCache();
|
|
expect(staged.stage(snapshot)).toBe(2);
|
|
expect(staged.entries()).toHaveLength(1);
|
|
expect(staged.workflowEntries()).toHaveLength(1);
|
|
expect(staged.stage(snapshot), "replacements are not new entries").toBe(0);
|
|
});
|
|
|
|
it("rebinds replayed target subjects while preserving child artifact subjects", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const relocatedTarget = { ...target, path: "/tmp/relocated-app" };
|
|
const parameters = { address: "0x1000" };
|
|
const locations = [{ kind: "address" as const, address: "0x1000" }];
|
|
cache.record({
|
|
target,
|
|
profile,
|
|
operation: "procedure_info",
|
|
parameters,
|
|
execution: createAnalysisExecution(
|
|
{ name: "main" },
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{
|
|
subject: target,
|
|
rawResult: { provider: "raw" },
|
|
locations,
|
|
analysisProfile: profile,
|
|
},
|
|
),
|
|
});
|
|
|
|
const replayedTarget = cache.lookup(
|
|
relocatedTarget,
|
|
profile,
|
|
"procedure_info",
|
|
parameters,
|
|
);
|
|
expect(replayedTarget?.subject?.path).toBe(relocatedTarget.path);
|
|
expect(replayedTarget?.rawResult).toEqual({ provider: "raw" });
|
|
expect(replayedTarget?.locations).toEqual(locations);
|
|
|
|
const childSubject = {
|
|
path: "/tmp/child.zip",
|
|
sha256: "b".repeat(64),
|
|
format: "zip" as const,
|
|
};
|
|
const childParameters = { address: "0x2000" };
|
|
cache.record({
|
|
target,
|
|
profile,
|
|
operation: "procedure_info",
|
|
parameters: childParameters,
|
|
execution: createAnalysisExecution(
|
|
{ name: "child" },
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{ subject: childSubject, analysisProfile: profile },
|
|
),
|
|
});
|
|
expect(
|
|
cache.lookup(relocatedTarget, profile, "procedure_info", childParameters)
|
|
?.subject,
|
|
).toMatchObject(childSubject);
|
|
});
|
|
|
|
it("binds an omitted subject to the selected target in snapshot replay", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const relocatedTarget = { ...target, path: "/tmp/relocated-default-target" };
|
|
const parameters = { address: "0x2a00" };
|
|
const execution = createAnalysisExecution(
|
|
{ name: "default-bound" },
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{ analysisProfile: profile },
|
|
);
|
|
expect(execution).not.toHaveProperty("subject");
|
|
cache.record({
|
|
target,
|
|
profile,
|
|
operation: "procedure_info",
|
|
parameters,
|
|
execution,
|
|
});
|
|
|
|
const snapshot = cache.export(
|
|
target,
|
|
profile,
|
|
createEvidenceBundle([
|
|
createEvidence(target, ANALYSIS_SNAPSHOT_PROVIDER, {
|
|
operation: "procedure_info",
|
|
parameters,
|
|
result: { name: "default-bound" },
|
|
analysisProfile: profile,
|
|
}),
|
|
]),
|
|
);
|
|
if (!snapshot.ok) throw snapshot.error;
|
|
expect(snapshot.value.entries[0]?.execution.subject?.path).toBe(target.path);
|
|
const reparsed = parseAnalysisSnapshot(
|
|
JSON.parse(serializeAnalysisSnapshot(snapshot.value)),
|
|
);
|
|
expect(reparsed.entries[0]?.execution.subject?.path).toBe(target.path);
|
|
cache.clear();
|
|
const imported = cache.import(
|
|
reparsed,
|
|
{ target: relocatedTarget, profile },
|
|
() => ok(0),
|
|
);
|
|
expect(imported.ok).toBe(true);
|
|
expect(
|
|
cache.lookup(relocatedTarget, profile, "procedure_info", parameters)
|
|
?.subject?.path,
|
|
).toBe(relocatedTarget.path);
|
|
});
|
|
|
|
it("preserves an explicitly unbound subject through snapshot replay", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const parameters = { address: "0x3000" };
|
|
cache.record({
|
|
target,
|
|
profile,
|
|
operation: "procedure_info",
|
|
parameters,
|
|
execution: createAnalysisExecution(
|
|
{ name: "unbound" },
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{ subject: null, analysisProfile: profile },
|
|
),
|
|
});
|
|
|
|
expect(
|
|
cache.lookup(
|
|
{ ...target, path: "/tmp/relocated-unbound-target" },
|
|
profile,
|
|
"procedure_info",
|
|
parameters,
|
|
)?.subject,
|
|
).toBeNull();
|
|
});
|
|
|
|
describe("analysis snapshot cache capacity", () => {
|
|
it("shares capacity across query kinds while allowing replacements and fresh partitions", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const execution = {
|
|
...createAnalysisExecution("workflow", REA_WORKFLOW_PROVIDER, {
|
|
subject: target,
|
|
}),
|
|
analysisProfile: workflowAnalysisProfile(profile),
|
|
};
|
|
const workflowInput = {
|
|
target,
|
|
profile,
|
|
operation: "trace_feature",
|
|
parameters: { query: "existing" },
|
|
execution,
|
|
};
|
|
for (let index = 0; index < SNAPSHOT_CACHE_ENTRY_CEILING - 1; index += 1)
|
|
cache.recordWorkflow({
|
|
...workflowInput,
|
|
parameters: { query: String(index) },
|
|
});
|
|
// Staging and recording both spend the same budget; an existing query can
|
|
// still be refreshed when no space remains for a new binding.
|
|
const directInput = {
|
|
target,
|
|
profile,
|
|
operation: "address_name" as const,
|
|
parameters: { address: "0x1000", document: "fixture" },
|
|
execution: createAnalysisExecution(
|
|
"initial",
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{ subject: target, analysisProfile: profile },
|
|
),
|
|
};
|
|
cache.record(directInput);
|
|
cache.record({
|
|
...directInput,
|
|
parameters: { address: "0x2000", document: "fixture" },
|
|
});
|
|
cache.recordWorkflow(workflowInput);
|
|
expect(cache.entries()).toHaveLength(1);
|
|
expect(cache.workflowEntries()).toHaveLength(
|
|
SNAPSHOT_CACHE_ENTRY_CEILING - 1,
|
|
);
|
|
|
|
cache.record({
|
|
...directInput,
|
|
execution: { ...directInput.execution, result: "updated" },
|
|
});
|
|
cache.recordWorkflow({
|
|
...workflowInput,
|
|
parameters: { query: "0" },
|
|
execution: { ...execution, result: "updated" },
|
|
});
|
|
expect(
|
|
cache.lookup(target, profile, "address_name", directInput.parameters)
|
|
?.result,
|
|
).toBe("updated");
|
|
expect(
|
|
cache.workflowEntries().find(({ parameters }) => parameters.query === "0")
|
|
?.execution.result,
|
|
).toBe("updated");
|
|
|
|
const incoming = createAnalysisSnapshotWorkflowEntry({
|
|
...workflowInput,
|
|
target: snapshotTarget(target),
|
|
binding: snapshotBinding(profile),
|
|
});
|
|
const evidence = createEvidence(target, REA_WORKFLOW_PROVIDER, {
|
|
operation: workflowInput.operation,
|
|
confidence: "derived",
|
|
parameters: workflowInput.parameters,
|
|
result: execution.result,
|
|
rawResult: execution.rawResult,
|
|
analysisProfile: execution.analysisProfile,
|
|
limitations: execution.limitations,
|
|
locations: execution.locations,
|
|
});
|
|
expect(
|
|
cache.stage(
|
|
parseAnalysisSnapshot({
|
|
target: snapshotTarget(target),
|
|
binding: snapshotBinding(profile),
|
|
entries: [],
|
|
workflow_entries: [incoming],
|
|
evidence_bundle: createEvidenceBundle([evidence]),
|
|
}),
|
|
),
|
|
"a capacity-refused workflow binding is not counted",
|
|
).toBe(0);
|
|
expect(cache.workflowEntries()).toHaveLength(
|
|
SNAPSHOT_CACHE_ENTRY_CEILING - 1,
|
|
);
|
|
|
|
const otherTarget = { ...target, sha256: "b".repeat(64) };
|
|
cache.recordWorkflow({
|
|
...workflowInput,
|
|
target: otherTarget,
|
|
execution: { ...execution, subject: otherTarget },
|
|
});
|
|
expect(cache.entries()).toEqual([]);
|
|
expect(cache.workflowEntries()).toHaveLength(1);
|
|
cache.clear();
|
|
cache.record({
|
|
...directInput,
|
|
target: otherTarget,
|
|
execution: { ...directInput.execution, subject: otherTarget },
|
|
});
|
|
expect(cache.entries()).toHaveLength(1);
|
|
expect(cache.workflowEntries()).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe("analysis snapshot cache policy", () => {
|
|
it.each<{
|
|
policy?: CapabilityDescriptor["cachePolicy"];
|
|
operation?: CapabilityDescriptor["operation"];
|
|
effects?: Partial<CapabilityDescriptor["effects"]>;
|
|
parameters?: Record<string, string>;
|
|
cacheable: boolean;
|
|
}>([
|
|
{ cacheable: false },
|
|
{ policy: "snapshot", cacheable: true },
|
|
{ policy: "live", cacheable: false },
|
|
{
|
|
policy: "live",
|
|
effects: { mayWriteFilesystem: false },
|
|
cacheable: false,
|
|
},
|
|
{
|
|
policy: "snapshot",
|
|
effects: { mutatesArtifact: true },
|
|
cacheable: false,
|
|
},
|
|
{
|
|
policy: "snapshot",
|
|
effects: { changesPermissions: true },
|
|
cacheable: false,
|
|
},
|
|
{ policy: "snapshot", operation: "list_documents", cacheable: false },
|
|
{ policy: "snapshot", operation: "list_strings", cacheable: false },
|
|
{
|
|
policy: "snapshot",
|
|
operation: "address_name",
|
|
parameters: { document: "fixture" },
|
|
cacheable: false,
|
|
},
|
|
])(
|
|
"keeps explicit cache policy within immutable and state-independent boundaries: %j",
|
|
({
|
|
policy,
|
|
operation = "analyze_function",
|
|
effects,
|
|
parameters = {},
|
|
cacheable,
|
|
}) => {
|
|
const descriptor: CapabilityDescriptor = {
|
|
provider: ANALYSIS_SNAPSHOT_PROVIDER,
|
|
operation,
|
|
available: true,
|
|
reason: null,
|
|
...(policy === undefined ? {} : { cachePolicy: policy }),
|
|
effects: {
|
|
mutatesArtifact: false,
|
|
launchesProcess: true,
|
|
mayShowUi: false,
|
|
mayAccessNetwork: false,
|
|
mayWriteFilesystem: true,
|
|
changesPermissions: false,
|
|
requiresRoot: false,
|
|
...effects,
|
|
},
|
|
limitations: [],
|
|
};
|
|
expect(isSnapshotCacheable(operation, descriptor, parameters)).toBe(
|
|
cacheable,
|
|
);
|
|
},
|
|
);
|
|
});
|
|
|
|
describe("analysis snapshot cache partitioning", () => {
|
|
it.each<{
|
|
operation: Exclude<AnalysisOperation, "health">;
|
|
parameters: Record<string, string>;
|
|
cacheable: boolean;
|
|
}>([
|
|
{ operation: "list_strings", parameters: {}, cacheable: false },
|
|
{
|
|
operation: "list_strings",
|
|
parameters: { document: "fixture" },
|
|
cacheable: true,
|
|
},
|
|
{
|
|
operation: "address_name",
|
|
parameters: { document: "fixture" },
|
|
cacheable: false,
|
|
},
|
|
{
|
|
operation: "address_name",
|
|
parameters: { document: "fixture", address: "0x1000" },
|
|
cacheable: true,
|
|
},
|
|
{ operation: "binary_overview", parameters: {}, cacheable: true },
|
|
])(
|
|
"retains explicit document and cursor requirements for $operation",
|
|
({ operation, parameters, cacheable }) => {
|
|
const descriptor: CapabilityDescriptor = {
|
|
operation,
|
|
provider: ANALYSIS_SNAPSHOT_PROVIDER,
|
|
available: true,
|
|
reason: null,
|
|
effects: {
|
|
mutatesArtifact: false,
|
|
launchesProcess: false,
|
|
mayShowUi: false,
|
|
mayAccessNetwork: false,
|
|
mayWriteFilesystem: false,
|
|
changesPermissions: false,
|
|
requiresRoot: false,
|
|
},
|
|
limitations: [],
|
|
};
|
|
expect(isSnapshotCacheable(operation, descriptor, parameters)).toBe(
|
|
cacheable,
|
|
);
|
|
},
|
|
);
|
|
|
|
it("returns detached data only from the exact provider/profile partition", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const execution = createAnalysisExecution(
|
|
"main",
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{
|
|
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
rawResult: "main",
|
|
subject: {
|
|
path: ANALYSIS_SNAPSHOT_TARGET.path,
|
|
sha256: ANALYSIS_SNAPSHOT_TARGET.sha256,
|
|
format: ANALYSIS_SNAPSHOT_TARGET.format,
|
|
...(ANALYSIS_SNAPSHOT_TARGET.architecture === undefined
|
|
? {}
|
|
: { architecture: ANALYSIS_SNAPSHOT_TARGET.architecture }),
|
|
},
|
|
},
|
|
);
|
|
cache.record({
|
|
target: ANALYSIS_SNAPSHOT_TARGET,
|
|
profile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
operation: "address_name",
|
|
parameters: { address: "0x1000", document: "fixture" },
|
|
execution,
|
|
});
|
|
const evidence = createEvidence(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{
|
|
operation: "address_name",
|
|
parameters: { address: "0x1000", document: "fixture" },
|
|
result: "main",
|
|
rawResult: "main",
|
|
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
},
|
|
);
|
|
const exported = cache.export(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
createEvidenceBundle([evidence]),
|
|
);
|
|
if (!exported.ok) throw exported.error;
|
|
const tampered = structuredClone(exported.value);
|
|
const tamperedEntry = tampered.entries[0];
|
|
if (tamperedEntry === undefined)
|
|
throw new Error("exported snapshot entry missing");
|
|
tamperedEntry.execution.result = "changed without Evidence";
|
|
let mergedEvidence = false;
|
|
expect(
|
|
new AnalysisSnapshotCache().import(tampered, undefined, () => {
|
|
mergedEvidence = true;
|
|
return ok(0);
|
|
}).ok,
|
|
).toBe(false);
|
|
expect(mergedEvidence).toBe(false);
|
|
const entry = exported.value.entries[0];
|
|
if (entry === undefined) throw new Error("exported snapshot entry missing");
|
|
Reflect.set(entry.execution, "provider", {
|
|
id: "forged",
|
|
name: "Forged",
|
|
version: "9",
|
|
});
|
|
entry.execution.limitations.push("forged");
|
|
|
|
expect(
|
|
cache.lookup(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
"address_name",
|
|
{ address: "0x1000", document: "fixture" },
|
|
),
|
|
).toMatchObject({
|
|
provider: ANALYSIS_SNAPSHOT_PROVIDER,
|
|
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
limitations: [expect.stringContaining("local REA analysis snapshot")],
|
|
});
|
|
const changedProfile = createAnalysisProfile(ANALYSIS_SNAPSHOT_PROVIDER, {
|
|
loader: "configured-override",
|
|
});
|
|
expect(
|
|
cache.lookup(ANALYSIS_SNAPSHOT_TARGET, changedProfile, "address_name", {
|
|
address: "0x1000",
|
|
document: "fixture",
|
|
}),
|
|
).toBeUndefined();
|
|
const changedProviderProfile = createAnalysisProfile(
|
|
{ id: "other", name: "Other", version: "1" },
|
|
{ loader: "mach-o-arm64" },
|
|
);
|
|
expect(
|
|
cache.lookup(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
changedProviderProfile,
|
|
"address_name",
|
|
{ address: "0x1000", document: "fixture" },
|
|
),
|
|
).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
const createRelationshipCoverageSnapshot = (
|
|
relationshipCoverage?: RelationshipCoverage,
|
|
) => {
|
|
const operation = "procedure_callees" as const;
|
|
const parameters = { procedure: "0x1000", document: "fixture" };
|
|
const cache = new AnalysisSnapshotCache();
|
|
const execution = createAnalysisExecution(
|
|
["0x2000"],
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{
|
|
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
rawResult: ["0x2000"],
|
|
subject: ANALYSIS_SNAPSHOT_TARGET,
|
|
...(relationshipCoverage === undefined ? {} : { relationshipCoverage }),
|
|
},
|
|
);
|
|
cache.record({
|
|
target: ANALYSIS_SNAPSHOT_TARGET,
|
|
profile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
operation,
|
|
parameters,
|
|
execution,
|
|
});
|
|
const evidence = createEvidence(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{
|
|
operation,
|
|
parameters,
|
|
result: ["0x2000"],
|
|
rawResult: ["0x2000"],
|
|
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
...(relationshipCoverage === undefined ? {} : { relationshipCoverage }),
|
|
},
|
|
);
|
|
const exported = cache.export(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
createEvidenceBundle([evidence]),
|
|
);
|
|
if (!exported.ok) throw exported.error;
|
|
return { cache, operation, parameters, snapshot: exported.value };
|
|
};
|
|
|
|
describe("relationship coverage snapshot replay", () => {
|
|
it("authenticates coverage and rejects missing serialized fields", () => {
|
|
const coverage: RelationshipCoverage = {
|
|
status: "complete",
|
|
diagnostics: ["The provider retained an unresolved call target."],
|
|
};
|
|
const { cache, operation, parameters, snapshot } =
|
|
createRelationshipCoverageSnapshot(coverage);
|
|
expect(
|
|
cache.lookup(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
operation,
|
|
parameters,
|
|
)?.relationshipCoverage,
|
|
).toEqual(coverage);
|
|
|
|
const tampered = structuredClone(snapshot);
|
|
const tamperedEntry = tampered.entries[0];
|
|
if (tamperedEntry === undefined) throw new Error("snapshot entry missing");
|
|
tamperedEntry.execution.relationship_coverage = {
|
|
status: "complete",
|
|
diagnostics: ["forged coverage"],
|
|
};
|
|
expect(
|
|
new AnalysisSnapshotCache().import(tampered, undefined, () => ok(0)).ok,
|
|
).toBe(false);
|
|
|
|
const missingCoverage = structuredClone(snapshot);
|
|
const missingCoverageEntry = missingCoverage.entries[0];
|
|
if (missingCoverageEntry === undefined)
|
|
throw new Error("snapshot entry missing");
|
|
Reflect.deleteProperty(
|
|
missingCoverageEntry.execution,
|
|
"relationship_coverage",
|
|
);
|
|
const missingCoverageEvidence = missingCoverage.evidence_bundle.records[0];
|
|
if (missingCoverageEvidence === undefined)
|
|
throw new Error("snapshot Evidence missing");
|
|
Reflect.deleteProperty(missingCoverageEvidence, "relationship_coverage");
|
|
expect(() => parseAnalysisSnapshot(missingCoverage)).toThrow();
|
|
expect(
|
|
new AnalysisSnapshotCache().import(missingCoverage, undefined, () =>
|
|
ok(0),
|
|
).ok,
|
|
).toBe(false);
|
|
});
|
|
|
|
it("replays absent coverage as explicitly unknown", () => {
|
|
const { operation, parameters, snapshot } =
|
|
createRelationshipCoverageSnapshot();
|
|
expect(snapshot.entries[0]?.execution.relationship_coverage).toBeNull();
|
|
expect(
|
|
snapshot.evidence_bundle.records[0]?.relationship_coverage,
|
|
).toBeNull();
|
|
|
|
const replayCache = new AnalysisSnapshotCache();
|
|
expect(replayCache.import(snapshot, undefined, () => ok(0)).ok).toBe(true);
|
|
expect(
|
|
replayCache.lookup(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
operation,
|
|
parameters,
|
|
)?.relationshipCoverage,
|
|
).toEqual({
|
|
status: "unknown",
|
|
reason: "Snapshot entry does not report relationship coverage.",
|
|
});
|
|
});
|
|
});
|
|
|
|
it("does not replay pre-envelope call graphs under the revised workflow profile", () => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const target = ANALYSIS_SNAPSHOT_TARGET;
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
const operation = "get_call_graph" as const;
|
|
const parameters = { address: "0x1000", direction: "forward" };
|
|
const oldProfile = workflowAnalysisProfile(profile);
|
|
const revisedProfile = workflowAnalysisProfile(profile, operation);
|
|
const oldResult = { "0": [] };
|
|
cache.recordWorkflow({
|
|
target,
|
|
profile,
|
|
operation,
|
|
parameters,
|
|
execution: {
|
|
...createAnalysisExecution(oldResult, REA_WORKFLOW_PROVIDER, {
|
|
subject: target,
|
|
rawResult: oldResult,
|
|
}),
|
|
analysisProfile: oldProfile,
|
|
},
|
|
});
|
|
const evidence = createEvidence(target, REA_WORKFLOW_PROVIDER, {
|
|
operation,
|
|
confidence: "derived",
|
|
parameters,
|
|
result: oldResult,
|
|
rawResult: oldResult,
|
|
analysisProfile: oldProfile,
|
|
});
|
|
const exported = cache.export(
|
|
target,
|
|
profile,
|
|
createEvidenceBundle([evidence]),
|
|
);
|
|
if (!exported.ok) throw exported.error;
|
|
const snapshot = parseAnalysisSnapshot(exported.value);
|
|
|
|
expect(
|
|
snapshotEvidenceForQuery(snapshot, {
|
|
target,
|
|
bindingProfile: profile,
|
|
operation,
|
|
parameters,
|
|
provider: REA_WORKFLOW_PROVIDER,
|
|
evidenceProfile: revisedProfile,
|
|
}),
|
|
).toBeUndefined();
|
|
expect(
|
|
enhancedOutputSchemas.get_call_graph?.safeParse({
|
|
normalized_result: oldResult,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
|
|
describe("executions that report truncated references", () => {
|
|
const record = (referencesTruncated: boolean | undefined) => {
|
|
const cache = new AnalysisSnapshotCache();
|
|
const parameters = { address: "0x1000", document: "fixture" };
|
|
cache.record({
|
|
target: ANALYSIS_SNAPSHOT_TARGET,
|
|
profile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
operation: "xrefs",
|
|
parameters,
|
|
execution: createAnalysisExecution(
|
|
["0x2000"],
|
|
ANALYSIS_SNAPSHOT_PROVIDER,
|
|
{
|
|
subject: ANALYSIS_SNAPSHOT_TARGET,
|
|
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
|
|
...(referencesTruncated === undefined ? {} : { referencesTruncated }),
|
|
},
|
|
),
|
|
});
|
|
return cache.lookup(
|
|
ANALYSIS_SNAPSHOT_TARGET,
|
|
ANALYSIS_SNAPSHOT_PROFILE,
|
|
"xrefs",
|
|
parameters,
|
|
);
|
|
};
|
|
|
|
it("are not replayed, because a replay would lose the truncation", () => {
|
|
expect(record(true)).toBeUndefined();
|
|
});
|
|
|
|
it("do not stop an execution that is complete or silent from being replayed", () => {
|
|
expect(record(false)?.result).toEqual(["0x2000"]);
|
|
expect(record(undefined)?.result).toEqual(["0x2000"]);
|
|
});
|
|
});
|
|
|
|
it("revises the trace_feature workflow profile so earlier results are not replayed", () => {
|
|
const profile = ANALYSIS_SNAPSHOT_PROFILE;
|
|
|
|
expect(workflowAnalysisProfile(profile, "trace_feature")).not.toEqual(
|
|
workflowAnalysisProfile(profile),
|
|
);
|
|
expect(
|
|
workflowAnalysisProfile(profile, "trace_call_path"),
|
|
"other workflows keep their profile",
|
|
).toEqual(workflowAnalysisProfile(profile));
|
|
});
|