refactor: preserve provider-neutral runtime seams

This commit is contained in:
morluto committed 2026-07-29 22:02:14 +00:00
1 parent 513d9b1555
commit a8161f0e55
6 files changed
+100 -44

No files matched your search

@@ -0,0 +1,62 @@
import type {
AnalysisClientFactory,
AnalysisProfileResolution,
AnalysisProfileResolutionOptions,
AnalysisProvider,
} from "./AnalysisProvider.js";
import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
import { BinarySession } from "./BinarySession.js";
import { SessionProviderRouter } from "./SessionProviderRouter.js";
import type { BinaryTarget } from "../domain/binaryTarget.js";
import type { AnalysisError } from "../domain/errors.js";
import type { Result } from "../domain/result.js";
type BinarySessionOptions = {
readonly resolveAnalysisProfile?: (
target: BinaryTarget,
options?: AnalysisProfileResolutionOptions,
) => Promise<Result<AnalysisProfileResolution, AnalysisError>>;
};
type BinarySessionOptionsOrResolver =
| BinarySessionOptions
| BinarySessionOptions["resolveAnalysisProfile"];
const normalizeBinarySessionOptions = (
optionsOrResolver?: BinarySessionOptionsOrResolver,
): BinarySessionOptions =>
typeof optionsOrResolver === "function"
? { resolveAnalysisProfile: optionsOrResolver }
: (optionsOrResolver ?? {});
/**
* Compose one provider-neutral session from deep-provider selection and
* disjoint auxiliary operation families.
*/
export const composeBinarySession = (
registry: AnalysisProviderRegistry | SessionProviderRouter,
auxiliaryProviders: readonly AnalysisProvider[] = [],
): BinarySession =>
new BinarySession(
registry instanceof SessionProviderRouter
? registry
: SessionProviderRouter.selectable(registry, auxiliaryProviders),
);
/**
* Compose a session through the same internal seam for focused adapter tests.
* The constructor remains private to this composition module, so callers test
* provider behavior without coupling to BinarySession construction.
*/
export const composeBinarySessionFromFactory = (
provider: AnalysisProvider | AnalysisClientFactory | SessionProviderRouter,
optionsOrResolver?: BinarySessionOptionsOrResolver,
): BinarySession =>
new BinarySession(provider, normalizeBinarySessionOptions(optionsOrResolver));
/** Compose a focused session from any provider-shaped test seam. */
export const composeBinarySessionFromProvider = (
provider: AnalysisProvider | AnalysisClientFactory | SessionProviderRouter,
optionsOrResolver?: BinarySessionOptionsOrResolver,
): BinarySession =>
new BinarySession(provider, normalizeBinarySessionOptions(optionsOrResolver));
+1 -1
View File
@@ -500,12 +500,12 @@ export const startLoopbackReplay = async (
async close() {
closePromise ??= (async () => {
recorder.stopMachineAdmission();
await recorder.drainMachine();
for (const client of websocket.clients) client.terminate();
await new Promise<void>((resolveClose) =>
websocket.close(() => resolveClose()),
);
await closeReplayServer(server);
await recorder.drainMachine();
})();
await closePromise;
},
+10 -10
View File
@@ -49,8 +49,8 @@ export interface SessionProviderRoute {
): AnalysisClient;
}
interface LegacyProviderRuntime {
readonly kind: "legacy";
interface SingleProviderRuntime {
readonly kind: "single";
readonly provider: AnalysisProvider | AnalysisClientFactory;
readonly identity: ProviderIdentity;
readonly capabilities: ReadonlyMap<string, CapabilityDescriptor> | undefined;
@@ -71,7 +71,7 @@ interface SelectableProviderRuntime {
/** Resolve either the compatibility single-provider seam or a selectable set. */
export class SessionProviderRouter {
private constructor(
private readonly runtime: LegacyProviderRuntime | SelectableProviderRuntime,
private readonly runtime: SingleProviderRuntime | SelectableProviderRuntime,
) {}
/** Preserve the existing provider/factory seam used by focused embedders. */
@@ -104,7 +104,7 @@ export class SessionProviderRouter {
provider.resolveAnalysisProfile?.(target, resolutionOptions) ??
Promise.resolve(ok({ profile: null, compatibility: {} })));
return new SessionProviderRouter({
kind: "legacy",
kind: "single",
provider,
identity,
capabilities,
@@ -148,7 +148,7 @@ export class SessionProviderRouter {
/** Provider identities exposed through the flat 1.x compatibility field. */
providerIdentities(route: SessionProviderRoute): readonly ProviderIdentity[] {
if (this.runtime.kind === "legacy") {
if (this.runtime.kind === "single") {
if (route.capabilities === undefined) return [route.identity];
return uniqueProviderIdentities(
[...route.capabilities.values()].map(({ provider }) => provider),
@@ -177,7 +177,7 @@ export class SessionProviderRouter {
binding: null,
selection: undefined,
createClient: (target, context) =>
createLegacyClient(runtime, target, undefined, context),
createSingleProviderClient(runtime, target, undefined, context),
};
}
@@ -231,7 +231,7 @@ export class SessionProviderRouter {
binding: null,
selection: undefined,
createClient: (openedTarget, context) =>
createLegacyClient(
createSingleProviderClient(
runtime,
openedTarget,
normalized.value.profile ?? undefined,
@@ -244,7 +244,7 @@ export class SessionProviderRouter {
candidateStatuses(
route: SessionProviderRoute,
): readonly AnalysisProviderCandidateStatus[] {
if (this.runtime.kind === "legacy") return [];
if (this.runtime.kind === "single") return [];
return route.selection?.candidates ?? this.runtime.registry.candidates();
}
@@ -292,8 +292,8 @@ const selectableRoute = (
};
};
const createLegacyClient = (
runtime: LegacyProviderRuntime,
const createSingleProviderClient = (
runtime: SingleProviderRuntime,
target: BinaryTarget,
profile: AnalysisProfileCommitment | undefined,
context: AnalysisClientContext,
+27 -29
View File
@@ -1,12 +1,12 @@
import type { AppConfig } from "../config.js";
import { BinarySession } from "./BinarySession.js";
import type { BinarySession } from "./BinarySession.js";
import { HopperProvider } from "../hopper/HopperProvider.js";
import { GhidraProvider } from "../ghidra/GhidraProvider.js";
import { silentLogger, type Logger } from "../logger.js";
import { GENERATED_AUXILIARY_PROVIDERS } from "../generatedMcpToolCatalog.js";
import { AnalysisProviderRegistry } from "./AnalysisProviderRegistry.js";
import { composeBinarySession } from "./BinarySessionComposition.js";
import { LazyAnalysisProvider } from "./LazyAnalysisProvider.js";
import { SessionProviderRouter } from "./SessionProviderRouter.js";
/**
* Compose the target-switching runtime shared directly by CLI and MCP adapters.
@@ -34,32 +34,30 @@ export const createBinarySession = (
throw new TypeError(`Missing generated provider metadata for ${id}`);
return new LazyAnalysisProvider({ ...generated, load });
};
return new BinarySession(
SessionProviderRouter.selectable(
new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider),
[
lazyProvider("rea-artifact-graph", async () => {
const { ArtifactProvider } = await import(
"../artifacts/ArtifactProvider.js"
);
return new ArtifactProvider(
config.artifactNativeMountEnabled,
config.artifactIntegrityContinueEnabled,
);
}),
lazyProvider("native-macos", async () => {
const { NativeMacOSProvider } = await import(
"../native/NativeMacOSProvider.js"
);
return new NativeMacOSProvider();
}),
lazyProvider("rea-dotnet-static", async () => {
const { ManagedStaticProvider } = await import(
"../dotnet/ManagedStaticProvider.js"
);
return new ManagedStaticProvider();
}),
],
),
return composeBinarySession(
new AnalysisProviderRegistry([hopper, ghidra], config.analysisProvider),
[
lazyProvider("rea-artifact-graph", async () => {
const { ArtifactProvider } = await import(
"../artifacts/ArtifactProvider.js"
);
return new ArtifactProvider(
config.artifactNativeMountEnabled,
config.artifactIntegrityContinueEnabled,
);
}),
lazyProvider("native-macos", async () => {
const { NativeMacOSProvider } = await import(
"../native/NativeMacOSProvider.js"
);
return new NativeMacOSProvider();
}),
lazyProvider("rea-dotnet-static", async () => {
const { ManagedStaticProvider } = await import(
"../dotnet/ManagedStaticProvider.js"
);
return new ManagedStaticProvider();
}),
],
);
};
@@ -43,8 +43,6 @@ export const iosManifestSchema = z.strictObject({
ui_required_device_capabilities: z.array(z.string()).default([]),
app_permissions: z.array(z.string()).default([]),
});
export type IosManifest = z.infer<typeof iosManifestSchema>;
/** Signing information. */
export const signingInfoSchema = z.strictObject({
is_signed: z.boolean().default(false),
-2
View File
@@ -85,8 +85,6 @@ export const swiftAccessLevelSchema = z.enum([
"public",
"open",
]);
export type SwiftAccessLevel = z.infer<typeof swiftAccessLevelSchema>;
/** Swift declaration metadata. */
export const swiftDeclSchema = z.strictObject({
kind: swiftDeclKindSchema,