Introduce deterministic deep-provider discovery and immutable per-target routing while retaining disjoint artifact and native capability families. Expose provider selection consistently through MCP, CLI, environment configuration, session status, prompt completion, typed diagnostics, and generated documentation. Preserve exact provider/profile provenance without request racing or transparent fallback. Cover overlapping providers, ambiguity, rejection details, cancellation, restoration, package installation, and real Hopper target switching.
130 lines
4.1 KiB
TypeScript
130 lines
4.1 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { BinarySession } from "../src/application/BinarySession.js";
|
|
import { CompositeProvider } from "../src/application/CompositeProvider.js";
|
|
import type {
|
|
AnalysisOperation,
|
|
AnalysisProvider,
|
|
CapabilityDescriptor,
|
|
ProviderIdentity,
|
|
} from "../src/application/AnalysisProvider.js";
|
|
import { createAnalysisExecution } from "../src/application/AnalysisProvider.js";
|
|
import type { BinaryTarget } from "../src/domain/binaryTarget.js";
|
|
import { ok } from "../src/domain/result.js";
|
|
|
|
const target: BinaryTarget = {
|
|
path: "/fixture",
|
|
kind: "database",
|
|
format: "analysis-database",
|
|
sha256: "0".repeat(64),
|
|
};
|
|
|
|
describe("composite analysis provider", () => {
|
|
it("routes disjoint operations and does not start children during health", async () => {
|
|
const hopperCalls: string[] = [];
|
|
const nativeCalls: string[] = [];
|
|
const hopper = provider("hopper", "address_name", hopperCalls);
|
|
const native = provider("native", "analyze_function", nativeCalls);
|
|
const composite = new CompositeProvider([hopper, native]);
|
|
const client = composite.createClient(target);
|
|
|
|
expect(await client.execute("health", {})).toEqual({
|
|
ok: true,
|
|
value: {
|
|
result: null,
|
|
rawResult: null,
|
|
provider: {
|
|
id: "composite:hopper+native",
|
|
name: "REA composite analysis provider",
|
|
version: null,
|
|
},
|
|
limitations: [],
|
|
locations: [],
|
|
subject: null,
|
|
},
|
|
});
|
|
expect(hopperCalls).toEqual([]);
|
|
expect(nativeCalls).toEqual([]);
|
|
expect(await client.execute("address_name", {})).toEqual({
|
|
ok: true,
|
|
value: createAnalysisExecution("hopper:address_name", hopper.identity()),
|
|
});
|
|
expect(await client.execute("analyze_function", {})).toEqual({
|
|
ok: true,
|
|
value: createAnalysisExecution(
|
|
"native:analyze_function",
|
|
native.identity(),
|
|
),
|
|
});
|
|
expect(hopperCalls).toEqual(["address_name"]);
|
|
expect(nativeCalls).toEqual(["analyze_function"]);
|
|
expect(await client.execute("list_names", {})).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCapabilityUnavailableError" },
|
|
});
|
|
});
|
|
|
|
it("publishes exact operation provenance and rejects ambiguous routes", () => {
|
|
const hopper = provider("hopper", "address_name", []);
|
|
const native = provider("native", "analyze_function", []);
|
|
const session = new BinarySession(new CompositeProvider([hopper, native]));
|
|
expect(session.providerIdentity("address_name").id).toBe("hopper");
|
|
expect(session.providerIdentity("analyze_function").id).toBe("native");
|
|
expect(session.providerIdentity("binary_overview").id).toBe("hopper");
|
|
expect(session.providerIdentity().id).toBe("composite:hopper+native");
|
|
expect(
|
|
() =>
|
|
new CompositeProvider([
|
|
hopper,
|
|
provider("duplicate", "address_name", []),
|
|
]),
|
|
).toThrow(/Multiple providers declare operation address_name/u);
|
|
});
|
|
});
|
|
|
|
const provider = (
|
|
id: string,
|
|
operation: Exclude<AnalysisOperation, "health">,
|
|
calls: string[],
|
|
): AnalysisProvider => {
|
|
const identity: ProviderIdentity = { id, name: id, version: "1" };
|
|
return {
|
|
identity: () => identity,
|
|
capabilities: () => [capability(identity, operation)],
|
|
createClient: () => ({
|
|
execute: (called) => {
|
|
calls.push(called);
|
|
return Promise.resolve(
|
|
ok(createAnalysisExecution(`${id}:${called}`, identity)),
|
|
);
|
|
},
|
|
close: () => Promise.resolve(),
|
|
}),
|
|
};
|
|
};
|
|
|
|
const capability = (
|
|
providerIdentity: ProviderIdentity,
|
|
operation: Exclude<AnalysisOperation, "health">,
|
|
): CapabilityDescriptor => ({
|
|
provider: providerIdentity,
|
|
operation,
|
|
inputContractVersion: 1,
|
|
outputContractVersion: 1,
|
|
available: true,
|
|
reason: null,
|
|
pagination: "none",
|
|
exhaustive: true,
|
|
effects: {
|
|
mutatesArtifact: false,
|
|
launchesProcess: true,
|
|
mayShowUi: false,
|
|
mayAccessNetwork: false,
|
|
mayWriteFilesystem: false,
|
|
changesPermissions: false,
|
|
requiresRoot: false,
|
|
},
|
|
limits: { maxResults: null, maxPayloadBytes: 1_000_000, timeoutMs: 5_000 },
|
|
limitations: [],
|
|
});
|