refactor(binary): move production composition outside application workflows (#965)
This commit is contained in:
parent
b5afa5f91e
commit
33d4761cc7
|
|
@ -30,7 +30,7 @@ analyzer, or provider-version differences while appearing target-compatible.
|
|||
|
||||
The decision addressed the earlier representations of
|
||||
[`CompositeProvider`](https://github.com/morluto/rea/blob/main/src/application/binary/CompositeProvider.ts),
|
||||
[runtime composition](https://github.com/morluto/rea/blob/main/src/application/runtime.ts),
|
||||
[runtime composition](https://github.com/morluto/rea/blob/main/src/composition/binary.ts),
|
||||
[`BinarySession`](https://github.com/morluto/rea/blob/main/src/application/binary/BinarySession.ts),
|
||||
[`BinaryTarget`](https://github.com/morluto/rea/blob/main/src/domain/binaryTarget.ts),
|
||||
[analysis snapshot](https://github.com/morluto/rea/blob/main/src/domain/analysisSnapshot.ts), and
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ graph TD
|
|||
DISPATCH["scripts/rea.mjs<br/>Executable dispatcher<br/>routes mcp / one-shot"]
|
||||
end
|
||||
|
||||
subgraph "Production Composition"
|
||||
COMPOSITION["src/composition/<br/>fresh typed provider and session factories<br/>one-shot binding and independent optional loading"]
|
||||
end
|
||||
|
||||
subgraph "Adapters"
|
||||
SERVER["src/server/<br/>MCP request translation<br/>createServer, tool registration, toolResult"]
|
||||
HOPPER_BRIDGE["bridge/hopper_bridge.py<br/>Runs inside Hopper<br/>adapts operations to Hopper Python API"]
|
||||
|
|
@ -18,7 +22,7 @@ graph TD
|
|||
REGISTRY["AnalysisProviderRegistry<br/>sorted deep candidates<br/>deterministic selection, no fallback"]
|
||||
RECORDS["src/application/investigation/<br/>composed InvestigationRecords + narrow ports<br/>EvidenceLedger and Unknown ownership"]
|
||||
SNAPSHOT["BinarySessionRecords + AnalysisSnapshotCache<br/>binary target/profile snapshots<br/>compatibility facade and post-commit notifications"]
|
||||
JS_RECON["src/application/ + src/artifacts/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
|
||||
JS_RECON["src/application/javascript/ + src/artifacts/javascript/<br/>JavaScript artifact reconstruction<br/>directory/ASAR reads<br/>AST-only bundle projection"]
|
||||
end
|
||||
|
||||
subgraph "Provider Layers"
|
||||
|
|
@ -69,6 +73,10 @@ graph TD
|
|||
MAIN --> SERVER
|
||||
MAIN --> APP
|
||||
CLI --> APP
|
||||
MAIN --> COMPOSITION
|
||||
CLI --> COMPOSITION
|
||||
SERVER --> COMPOSITION
|
||||
COMPOSITION --> SESSION
|
||||
APP --> FIRMWARE
|
||||
FIRMWARE --> PROCESS
|
||||
FIRMWARE --> CONTRACTS
|
||||
|
|
@ -84,9 +92,10 @@ graph TD
|
|||
APP -->|record callbacks| RECORDS
|
||||
SERVER -->|narrow record ports| RECORDS
|
||||
RECORDS --> DOMAIN
|
||||
REGISTRY --> HOPPER
|
||||
REGISTRY --> GHIDRA
|
||||
REGISTRY --> IDA
|
||||
COMPOSITION --> HOPPER
|
||||
COMPOSITION --> GHIDRA
|
||||
COMPOSITION --> IDA
|
||||
COMPOSITION --> AUX
|
||||
IDA --> IDA_MCP
|
||||
IDA --> CONTRACTS
|
||||
IDA --> DOMAIN
|
||||
|
|
|
|||
|
|
@ -6,17 +6,17 @@ there is no new universal provider interface or fixed module manifest.
|
|||
|
||||
## Ownership to preserve
|
||||
|
||||
| Responsibility | Current owner | Migration direction |
|
||||
| --------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------- |
|
||||
| Analyst semantics and Evidence representation | domain | Keep pure and provider-neutral |
|
||||
| Named public input/output contracts | contracts | Keep exact CLI/MCP meaning and complete discovery |
|
||||
| Producer parsing and external tool protocols | provider adapters | Keep behind the relevant typed port |
|
||||
| Shared analyst workflows | application | Share between CLI and MCP |
|
||||
| Concrete provider construction | binary runtime, composition, MCP startup | Consolidate one capability at a time in production composition |
|
||||
| Investigation Evidence and Unknowns | InvestigationRecords/EvidenceLedger | Consume narrow read/write/atomic record ports |
|
||||
| Target/profile snapshots and invalidation | BinarySessionRecords/BinarySession | Keep binary-owned |
|
||||
| Subprocess ownership and host primitives | process/windows | Reuse; preserve cancellation and cleanup |
|
||||
| Public translation | CLI/MCP adapters | Delegate to shared workflows and named contracts |
|
||||
| Responsibility | Current owner | Migration direction |
|
||||
| --------------------------------------------- | ----------------------------------- | ----------------------------------------------------- |
|
||||
| Analyst semantics and Evidence representation | domain | Keep pure and provider-neutral |
|
||||
| Named public input/output contracts | contracts | Keep exact CLI/MCP meaning and complete discovery |
|
||||
| Producer parsing and external tool protocols | provider adapters | Keep behind the relevant typed port |
|
||||
| Shared analyst workflows | application | Share between CLI and MCP |
|
||||
| Concrete provider construction | composition, MCP startup | Keep fresh typed factories at the production boundary |
|
||||
| Investigation Evidence and Unknowns | InvestigationRecords/EvidenceLedger | Consume narrow read/write/atomic record ports |
|
||||
| Target/profile snapshots and invalidation | BinarySessionRecords/BinarySession | Keep binary-owned |
|
||||
| Subprocess ownership and host primitives | process/windows | Reuse; preserve cancellation and cleanup |
|
||||
| Public translation | CLI/MCP adapters | Delegate to shared workflows and named contracts |
|
||||
|
||||
Retain exact Evidence IDs, provenance, detached reads, atomic Evidence/Unknown
|
||||
mutations and optimistic revisions. Preserve current close and failed-cleanup
|
||||
|
|
@ -69,8 +69,8 @@ paths removed during a move. Pass exact source paths for a focused check.
|
|||
|
||||
Pure source and its tests keep inward dependencies. Application/server provider
|
||||
construction guards cover the migrated deep, Android, firmware, JavaScript recovery and observation
|
||||
implementations. The existing binary runtime is an exact temporary composition
|
||||
exception; shared browser capture/export helpers remain admitted while their
|
||||
implementations. Application workflows cannot import composition, including the
|
||||
former binary runtime entrypoint; shared browser capture/export helpers remain admitted while their
|
||||
ownership is reviewed. Test lane restrictions still use Oxlint. Producer-dependent
|
||||
browser and managed comparison tests live in boundary lanes with their original
|
||||
fixtures and assertions. This is a development guard, not runtime authorization.
|
||||
|
|
@ -104,8 +104,8 @@ inside the provider. The existing launcher seams remain available to boundary
|
|||
fixtures, which now exercise the production factories.
|
||||
|
||||
Application workflows and MCP registration must not import these concrete
|
||||
providers. Composition may import their implementations. Binary composition is
|
||||
still at its existing entrypoint while its direct-analysis callers are investigated.
|
||||
providers. Composition may import their implementations. Binary composition also
|
||||
uses this outer boundary after its direct-analysis callers were migrated.
|
||||
This concrete pilot does not introduce a shared provider interface or module
|
||||
manifest.
|
||||
|
||||
|
|
@ -329,10 +329,9 @@ records still have their separate owner in `src/application/investigation/`.
|
|||
|
||||
`AnalysisProvider.ts` stays shared because its identity/execution types also serve
|
||||
nonbinary ports. `BinaryTargetResolver.ts` stays shared because Android, firmware,
|
||||
managed and artifact workflows use its target parsing. The existing `runtime.ts`
|
||||
remains the exact temporary production wiring entrypoint while direct-analysis
|
||||
callers are migrated deliberately; application workflows gain no outward
|
||||
composition dependency.
|
||||
managed and artifact workflows use its target parsing. Production wiring lives
|
||||
in `src/composition/binary.ts`; application workflows gain no outward composition
|
||||
dependency.
|
||||
|
||||
The three colocated application tests move with their owner. Composition cases
|
||||
remain in `tests/composition/analysis-sessions/`; filesystem and SDK cases retain
|
||||
|
|
@ -385,3 +384,23 @@ Portable decoder and injected producer tests establish their format/boundary
|
|||
claims. Actual Swift/Xcode, assetutil, native NIB, bundle signing and DMG lifecycle
|
||||
claims require the real Apple lane. Native binary API/value-flow semantics and
|
||||
shared cross-domain contract aggregates keep their separate owners.
|
||||
|
||||
## Binary production composition closure
|
||||
|
||||
`src/composition/binary.ts` is the shared production owner for deep candidate
|
||||
construction, lazy auxiliary composition and the managed-only session factory.
|
||||
It preserves the existing constructor bodies, selection policy and fresh session
|
||||
per invocation. Factory registration acquires no engine or target.
|
||||
|
||||
`DirectAnalysis` and `DirectAnalysisStatus` receive these two existing factories
|
||||
through `DirectAnalysisDependencies`. They retain configuration parsing, selected
|
||||
status environment, cancellation, snapshot replay/binding, Evidence projection
|
||||
and finally-close. The dependency uses the existing BinarySession type, including
|
||||
its replay policy; it adds no universal backend or lifecycle contract.
|
||||
|
||||
`src/composition/directAnalysis.ts` binds the production factories for CLI and
|
||||
one-shot managed MCP callers. Binding creates no session and stores no retained
|
||||
analysis state. MCP startup constructs its connection session through the same
|
||||
binary factory. Source and compiled fixtures use these entrypoints. The former
|
||||
application runtime path is removed, and the source guard no longer admits its
|
||||
outward provider/composition imports.
|
||||
|
|
|
|||
|
|
@ -89,12 +89,14 @@ const failedBoundary = (file, target) => {
|
|||
)
|
||||
return "pure-layer";
|
||||
}
|
||||
if (file === "src/application/runtime.ts") return undefined;
|
||||
if (layer === "application" && targetLayer === "composition")
|
||||
return "application-composition";
|
||||
if (
|
||||
(layer === "application" || layer === "server") &&
|
||||
(MIGRATED_PROVIDER_ROOTS.has(targetLayer) ||
|
||||
/^src\/(?:artifacts\/ArtifactProvider|dotnet\/ManagedStaticProvider|native\/NativeMacOSProvider)\.[^/]+$/u.test(
|
||||
target,
|
||||
) ||
|
||||
/^src\/browser\/[^/]*Provider\.[^/]+$/u.test(target))
|
||||
)
|
||||
return "provider-construction";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { parseConfig } from "../config.js";
|
|||
import type { JsonValue } from "../domain/jsonValue.js";
|
||||
import { EnhancedTools } from "./EnhancedTools.js";
|
||||
import { executeFunctionAnalysisEvidence } from "./FunctionAnalysisEvidence.js";
|
||||
import { createBinarySession, createManagedBinarySession } from "./runtime.js";
|
||||
import type { DirectAnalysisDependencies } from "./DirectAnalysisDependencies.js";
|
||||
import type { BinarySession } from "./binary/BinarySession.js";
|
||||
import { silentLogger, type Logger } from "../logger.js";
|
||||
import { createEvidence } from "../domain/evidence.js";
|
||||
import type { Evidence } from "../domain/evidence.js";
|
||||
|
|
@ -38,11 +39,6 @@ import {
|
|||
import type { AnalysisProviderSelector } from "../contracts/providerSelection.js";
|
||||
import { artifactInspectionResultSchema } from "../domain/artifactInspection.js";
|
||||
|
||||
export {
|
||||
runCapabilityStatus,
|
||||
runProviderStatus,
|
||||
} from "./DirectAnalysisStatus.js";
|
||||
|
||||
type DirectAnalysisTool =
|
||||
| "annotate_native_function"
|
||||
| "inspect_native_load_image"
|
||||
|
|
@ -70,6 +66,7 @@ type DirectAnalysisTool =
|
|||
* retain a target or provider client for a subsequent command.
|
||||
*/
|
||||
export const runDirectAnalysis = async (
|
||||
dependencies: DirectAnalysisDependencies,
|
||||
path: string,
|
||||
tool: DirectAnalysisTool,
|
||||
arguments_: Readonly<Record<string, JsonValue>>,
|
||||
|
|
@ -82,7 +79,7 @@ export const runDirectAnalysis = async (
|
|||
} = {},
|
||||
): Promise<JsonValue> =>
|
||||
withProcessCancellation(options.signal, (signal) =>
|
||||
runAnalysis(path, tool, arguments_, {
|
||||
runAnalysis(dependencies, path, tool, arguments_, {
|
||||
logger: options.logger ?? silentLogger,
|
||||
snapshotPath: options.snapshotPath,
|
||||
signal,
|
||||
|
|
@ -97,6 +94,7 @@ export const runDirectAnalysis = async (
|
|||
|
||||
/** Execute one provider-native semantic operation with atomic provenance. */
|
||||
export const runProviderAnalysis = async (
|
||||
dependencies: DirectAnalysisDependencies,
|
||||
...[path, tool, arguments_, logger = silentLogger, signal]: readonly [
|
||||
path: string,
|
||||
tool: NativeToolName | ArtifactAnalysisOperation | ManagedToolName,
|
||||
|
|
@ -106,9 +104,9 @@ export const runProviderAnalysis = async (
|
|||
]
|
||||
): Promise<JsonValue> =>
|
||||
isManagedToolName(tool)
|
||||
? runManagedProviderAnalysis(path, tool, signal)
|
||||
? runManagedProviderAnalysis(dependencies, path, tool, signal)
|
||||
: withProcessCancellation(signal, (operationSignal) =>
|
||||
runAnalysis(path, tool, arguments_, {
|
||||
runAnalysis(dependencies, path, tool, arguments_, {
|
||||
logger,
|
||||
snapshotPath: undefined,
|
||||
signal: operationSignal,
|
||||
|
|
@ -117,12 +115,13 @@ export const runProviderAnalysis = async (
|
|||
|
||||
/** Execute managed metadata inspection in an isolated managed-only session. */
|
||||
export const runManagedProviderExecution = async (
|
||||
dependencies: DirectAnalysisDependencies,
|
||||
path: string,
|
||||
tool: ManagedToolName,
|
||||
signal?: AbortSignal,
|
||||
): Promise<Result<AnalysisExecution, AnalysisError>> =>
|
||||
withProcessCancellation(signal, async (operationSignal) => {
|
||||
const session = createManagedBinarySession();
|
||||
const session = dependencies.createManagedBinarySession();
|
||||
try {
|
||||
const opened = await session.open(path, { signal: operationSignal });
|
||||
if (!opened.ok) return opened;
|
||||
|
|
@ -133,11 +132,17 @@ export const runManagedProviderExecution = async (
|
|||
});
|
||||
|
||||
const runManagedProviderAnalysis = async (
|
||||
dependencies: DirectAnalysisDependencies,
|
||||
path: string,
|
||||
tool: ManagedToolName,
|
||||
signal?: AbortSignal,
|
||||
): Promise<JsonValue> => {
|
||||
const execution = await runManagedProviderExecution(path, tool, signal);
|
||||
const execution = await runManagedProviderExecution(
|
||||
dependencies,
|
||||
path,
|
||||
tool,
|
||||
signal,
|
||||
);
|
||||
if (!execution.ok) return cliError(execution.error);
|
||||
const value = execution.value;
|
||||
return createEvidence(value.subject ?? undefined, value.provider, {
|
||||
|
|
@ -151,6 +156,7 @@ const runManagedProviderAnalysis = async (
|
|||
};
|
||||
|
||||
const runAnalysis = async (
|
||||
dependencies: DirectAnalysisDependencies,
|
||||
path: string,
|
||||
tool:
|
||||
| NativeToolName
|
||||
|
|
@ -177,7 +183,7 @@ const runAnalysis = async (
|
|||
const { logger, signal, snapshotPath } = options;
|
||||
const config = parseConfig(options.environment ?? process.env);
|
||||
if (!config.ok) return cliError(config.error);
|
||||
const session = createBinarySession(config.value, logger);
|
||||
const session = dependencies.createBinarySession(config.value, logger);
|
||||
try {
|
||||
const prepared = await prepareSnapshot({
|
||||
path,
|
||||
|
|
@ -247,13 +253,13 @@ const runAnalysis = async (
|
|||
};
|
||||
|
||||
const canReplayDirectTool = (
|
||||
session: ReturnType<typeof createBinarySession>,
|
||||
tool: Parameters<typeof runAnalysis>[1],
|
||||
session: BinarySession,
|
||||
tool: Parameters<typeof runAnalysis>[2],
|
||||
): boolean =>
|
||||
tool !== "trace_native_ui_action" && session.allowsSnapshotReplay(tool);
|
||||
|
||||
const executeAnalysisTool = async (input: {
|
||||
readonly session: ReturnType<typeof createBinarySession>;
|
||||
readonly session: BinarySession;
|
||||
readonly openedTarget: BinaryTarget;
|
||||
readonly tool:
|
||||
| NativeToolName
|
||||
|
|
@ -372,7 +378,7 @@ const prepareSnapshot = async (options: {
|
|||
};
|
||||
|
||||
const replayProviderFor = (
|
||||
session: ReturnType<typeof createBinarySession>,
|
||||
session: BinarySession,
|
||||
tool:
|
||||
| NativeToolName
|
||||
| ArtifactAnalysisOperation
|
||||
|
|
@ -384,7 +390,7 @@ const replayProviderFor = (
|
|||
: session.providerIdentity(tool);
|
||||
|
||||
const analysisProfileForEvidence = (
|
||||
session: ReturnType<typeof createBinarySession>,
|
||||
session: BinarySession,
|
||||
tool:
|
||||
| NativeToolName
|
||||
| ArtifactAnalysisOperation
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import type { AppConfig } from "../config.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import type { BinarySession } from "./binary/BinarySession.js";
|
||||
|
||||
/** Existing one-shot session factories supplied by the production boundary. */
|
||||
export interface DirectAnalysisDependencies {
|
||||
readonly createBinarySession: (
|
||||
config: AppConfig,
|
||||
logger: Logger,
|
||||
) => BinarySession;
|
||||
readonly createManagedBinarySession: () => BinarySession;
|
||||
}
|
||||
|
|
@ -3,15 +3,16 @@ import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
|||
import { jsonObjectSchema, type JsonValue } from "../domain/jsonValue.js";
|
||||
import { createServerIdentity } from "../serverIdentity.js";
|
||||
import { silentLogger, type Logger } from "../logger.js";
|
||||
import { createBinarySession } from "./runtime.js";
|
||||
import type { DirectAnalysisDependencies } from "./DirectAnalysisDependencies.js";
|
||||
|
||||
const runSessionStatus = async (
|
||||
dependencies: Pick<DirectAnalysisDependencies, "createBinarySession">,
|
||||
logger: Logger = silentLogger,
|
||||
environment: Readonly<Record<string, string | undefined>> = process.env,
|
||||
): Promise<JsonValue> => {
|
||||
const config = parseConfig(environment);
|
||||
if (!config.ok) return { error: projectAnalysisError(config.error) };
|
||||
const session = createBinarySession(config.value, logger);
|
||||
const session = dependencies.createBinarySession(config.value, logger);
|
||||
try {
|
||||
return {
|
||||
...jsonObjectSchema.parse(session.status()),
|
||||
|
|
@ -26,12 +27,14 @@ const runSessionStatus = async (
|
|||
|
||||
/** List binary-session provider candidates and auxiliary operation availability. */
|
||||
export const runProviderStatus = (
|
||||
dependencies: Pick<DirectAnalysisDependencies, "createBinarySession">,
|
||||
logger: Logger = silentLogger,
|
||||
environment: Readonly<Record<string, string | undefined>> = process.env,
|
||||
) => runSessionStatus(logger, environment);
|
||||
) => runSessionStatus(dependencies, logger, environment);
|
||||
|
||||
/** List binary-session operation descriptors; this is not the full MCP catalog. */
|
||||
export const runCapabilityStatus = (
|
||||
dependencies: Pick<DirectAnalysisDependencies, "createBinarySession">,
|
||||
logger: Logger = silentLogger,
|
||||
environment: Readonly<Record<string, string | undefined>> = process.env,
|
||||
) => runSessionStatus(logger, environment);
|
||||
) => runSessionStatus(dependencies, logger, environment);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from "incur";
|
||||
|
||||
import { runProviderAnalysis } from "../application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../composition/directAnalysis.js";
|
||||
import { createArtifactExtractionDestination } from "../application/ArtifactExtractionDestination.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { z } from "incur";
|
|||
import { stat } from "node:fs/promises";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
import { runDirectAnalysis } from "../application/DirectAnalysis.js";
|
||||
import { runDirectAnalysis } from "../composition/directAnalysis.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from "incur";
|
||||
|
||||
import { runDirectAnalysis } from "../application/DirectAnalysis.js";
|
||||
import { runDirectAnalysis } from "../composition/directAnalysis.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
import { logCliCommand } from "../cliLogging.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { z } from "incur";
|
|||
import { compareManagedMemberPaths } from "../application/managed/ManagedMemberComparisonService.js";
|
||||
import { verifyManagedNativeBoundariesEvidence } from "../application/managed/ManagedNativeVerificationService.js";
|
||||
import { importManagedReconstructionEvidence } from "../application/managed/ManagedReconstructionService.js";
|
||||
import { runProviderAnalysis } from "../application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../composition/directAnalysis.js";
|
||||
import { parseCliJsonInput } from "../cliJsonInput.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { CLI_COMMANDS } from "../cliCommandNames.js";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
runCapabilityStatus,
|
||||
runProviderAnalysis,
|
||||
runProviderStatus,
|
||||
} from "../application/DirectAnalysis.js";
|
||||
} from "../composition/directAnalysis.js";
|
||||
import { importReferenceSource } from "../application/ReferenceSourceImport.js";
|
||||
import { projectReferenceSourceImportError } from "../application/ReferenceSourceImportTypes.js";
|
||||
import { parseConfig } from "../config.js";
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest";
|
|||
import { createCli } from "./cli.js";
|
||||
import { createSystemDoctorHost } from "./doctorRuntime.js";
|
||||
import { captureProcessScenarioFile } from "./application/ProcessCli.js";
|
||||
import { runCapabilityStatus } from "./application/DirectAnalysisStatus.js";
|
||||
import { runCapabilityStatus } from "./composition/directAnalysis.js";
|
||||
import { probeProcessCaptureCapability } from "./application/ProcessHarness.js";
|
||||
import { parseEvidence } from "./domain/evidence.js";
|
||||
import { parseProcessCapture } from "./domain/processCapture.js";
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import type { AppConfig } from "../config.js";
|
||||
import type { BinarySession } from "./binary/BinarySession.js";
|
||||
import type { BinarySession } from "../application/binary/BinarySession.js";
|
||||
import { HopperProvider } from "../hopper/HopperProvider.js";
|
||||
import { GhidraProvider } from "../ghidra/GhidraProvider.js";
|
||||
import { IdaProvider } from "../ida/IdaProvider.js";
|
||||
import { silentLogger, type Logger } from "../logger.js";
|
||||
import { auxiliaryAnalysisProviderDeclarations } from "../composition/auxiliaryAnalysisProviders.js";
|
||||
import { AnalysisProviderRegistry } from "./binary/AnalysisProviderRegistry.js";
|
||||
import { composeBinarySession } from "./binary/BinarySessionComposition.js";
|
||||
import { LazyAnalysisProvider } from "./binary/LazyAnalysisProvider.js";
|
||||
import { auxiliaryAnalysisProviderDeclarations } from "./auxiliaryAnalysisProviders.js";
|
||||
import { AnalysisProviderRegistry } from "../application/binary/AnalysisProviderRegistry.js";
|
||||
import { composeBinarySession } from "../application/binary/BinarySessionComposition.js";
|
||||
import { LazyAnalysisProvider } from "../application/binary/LazyAnalysisProvider.js";
|
||||
import { ManagedStaticProvider } from "../dotnet/ManagedStaticProvider.js";
|
||||
import { SessionProviderRouter } from "./binary/SessionProviderRouter.js";
|
||||
import { SessionProviderRouter } from "../application/binary/SessionProviderRouter.js";
|
||||
|
||||
/**
|
||||
* Compose the target-switching runtime shared directly by CLI and MCP adapters.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
runDirectAnalysis as executeDirectAnalysis,
|
||||
runProviderAnalysis as executeProviderAnalysis,
|
||||
runManagedProviderExecution as executeManagedProvider,
|
||||
} from "../application/DirectAnalysis.js";
|
||||
import {
|
||||
runCapabilityStatus as executeCapabilityStatus,
|
||||
runProviderStatus as executeProviderStatus,
|
||||
} from "../application/DirectAnalysisStatus.js";
|
||||
import type { DirectAnalysisDependencies } from "../application/DirectAnalysisDependencies.js";
|
||||
import { createBinarySession, createManagedBinarySession } from "./binary.js";
|
||||
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
createBinarySession,
|
||||
createManagedBinarySession,
|
||||
};
|
||||
|
||||
/** Run one isolated binary operation through the production session factory. */
|
||||
export const runDirectAnalysis = executeDirectAnalysis.bind(
|
||||
undefined,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
/** Run one isolated native, artifact or managed operation. */
|
||||
export const runProviderAnalysis = executeProviderAnalysis.bind(
|
||||
undefined,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
/** Execute managed metadata without constructing native provider candidates. */
|
||||
export const runManagedProviderExecution = executeManagedProvider.bind(
|
||||
undefined,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
/** Report production provider candidates without opening a target. */
|
||||
export const runProviderStatus = executeProviderStatus.bind(
|
||||
undefined,
|
||||
dependencies,
|
||||
);
|
||||
|
||||
/** Report production session operations without opening a target. */
|
||||
export const runCapabilityStatus = executeCapabilityStatus.bind(
|
||||
undefined,
|
||||
dependencies,
|
||||
);
|
||||
|
|
@ -6,7 +6,7 @@ import { realpathSync } from "node:fs";
|
|||
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
||||
|
||||
import { parseConfig } from "./config.js";
|
||||
import { createBinarySession } from "./application/runtime.js";
|
||||
import { createBinarySession } from "./composition/binary.js";
|
||||
import { createLogger } from "./logger.js";
|
||||
import { projectAnalysisError } from "./domain/analysisErrorProjection.js";
|
||||
import type { RuntimeDependencies } from "./main/types.js";
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { AnalysisInputError } from "../domain/analysisErrorCore.js";
|
|||
import { err } from "../domain/result.js";
|
||||
import type { Logger } from "../logger.js";
|
||||
import { registerEvidenceTools } from "./registerEvidenceTools.js";
|
||||
import { runManagedProviderExecution } from "../application/DirectAnalysis.js";
|
||||
import { runManagedProviderExecution } from "../composition/directAnalysis.js";
|
||||
import { isManagedToolName } from "../contracts/managed/managedToolContracts.js";
|
||||
|
||||
/** Register execution-free managed PE/CLI inspection. */
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
|
|||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { writeAnalysisSnapshot } from "../../../src/application/binary/AnalysisSnapshotFiles.js";
|
||||
import { runDirectAnalysis } from "../../../src/application/DirectAnalysis.js";
|
||||
import { runDirectAnalysis } from "../../../src/composition/directAnalysis.js";
|
||||
import type { AnalysisSnapshot } from "../../../src/domain/analysisSnapshot.js";
|
||||
import {
|
||||
snapshotBinding,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { describe, expect, it } from "vitest";
|
|||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { runProviderAnalysis } from "../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../src/composition/directAnalysis.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import {
|
||||
identifyRuntimes,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { expect, it } from "vitest";
|
|||
import { z } from "zod";
|
||||
|
||||
import { parseConfig } from "../../../src/config.js";
|
||||
import { createBinarySession } from "../../../src/application/runtime.js";
|
||||
import { createBinarySession } from "../../../src/composition/binary.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
|
||||
import { artifactInventoryResultSchema } from "../../../src/domain/artifactGraph.js";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
|||
import { expect, it } from "vitest";
|
||||
import { z } from "zod";
|
||||
import { parseConfig } from "../../../src/config.js";
|
||||
import { createBinarySession } from "../../../src/application/runtime.js";
|
||||
import { createBinarySession } from "../../../src/composition/binary.js";
|
||||
import { keyedArchiveResultSchema } from "../../../src/domain/apple/keyedArchive.js";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from "@zip.js/zip.js";
|
||||
import { afterEach, expect, it } from "vitest";
|
||||
|
||||
import { runProviderAnalysis } from "../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../src/composition/directAnalysis.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { join } from "node:path";
|
|||
import { describe, expect } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createBinarySession } from "../../../src/application/runtime.js";
|
||||
import { createBinarySession } from "../../../src/composition/binary.js";
|
||||
import { parseConfig } from "../../../src/config.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import type { StdioServerHandle } from "@modelcontextprotocol/server/stdio";
|
|||
import pino from "pino";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createManagedBinarySession } from "../../../src/application/runtime.js";
|
||||
import { createManagedBinarySession } from "../../../src/composition/binary.js";
|
||||
import { MCP_SHUTDOWN_FAILED } from "../../../src/main/messages.js";
|
||||
import { createShutdown } from "../../../src/main/shutdown.js";
|
||||
import type { RuntimeDependencies } from "../../../src/main/types.js";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest";
|
|||
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { runProviderAnalysis } from "../../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../../src/composition/directAnalysis.js";
|
||||
import { parseEvidence } from "../../../../src/domain/evidence.js";
|
||||
import { buildManagedPeFixture } from "../../../../src/dotnet/ManagedPe.fixture.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { describe, expect, it } from "vitest";
|
|||
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { runProviderAnalysis } from "../../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../../src/composition/directAnalysis.js";
|
||||
import { ArtifactProvider } from "../../../../src/artifacts/ArtifactProvider.js";
|
||||
import { artifactInventoryResultSchema } from "../../../../src/domain/artifactGraph.js";
|
||||
import { artifactInspectionResultSchema } from "../../../../src/domain/artifactInspection.js";
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { describe, expect, it } from "vitest";
|
|||
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { runProviderAnalysis } from "../../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../../src/composition/directAnalysis.js";
|
||||
import {
|
||||
ArtifactPathRegistry,
|
||||
normalizeArtifactPath,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { createPackageWithOptions } from "@electron/asar";
|
|||
import { expect, it } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
import { runProviderAnalysis } from "../../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../../src/composition/directAnalysis.js";
|
||||
import { artifactInspectionResultSchema } from "../../../../src/domain/artifactInspection.js";
|
||||
import { parseEvidence } from "../../../../src/domain/evidence.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from "../../support/applicationSessionFixture.js";
|
||||
|
||||
import { projectAppleApplicationEvidence } from "../../../src/application/apple/AppleApplicationService.js";
|
||||
import { runProviderAnalysis } from "../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../src/composition/directAnalysis.js";
|
||||
import { appleApplicationProjectionResultSchema } from "../../../src/domain/apple/appleApplication.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import {
|
|||
} from "../../support/applicationSessionFixture.js";
|
||||
|
||||
import { projectAndroidApplicationEvidence } from "../../../src/application/android/AndroidApplicationService.js";
|
||||
import { runProviderAnalysis } from "../../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../../src/composition/directAnalysis.js";
|
||||
import { androidApplicationProjectionResultSchema } from "../../../src/domain/android/androidApplication.js";
|
||||
import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,152 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
runDirectAnalysis,
|
||||
runManagedProviderExecution,
|
||||
} from "../../src/application/DirectAnalysis.js";
|
||||
import { runCapabilityStatus } from "../../src/application/DirectAnalysisStatus.js";
|
||||
import type { DirectAnalysisDependencies } from "../../src/application/DirectAnalysisDependencies.js";
|
||||
import type { BinarySession } from "../../src/application/binary/BinarySession.js";
|
||||
import { parseEvidence } from "../../src/domain/evidence.js";
|
||||
import { observed } from "../fixtures/analysisExecution.js";
|
||||
import {
|
||||
createBinarySessionTargets,
|
||||
createTestBinarySession,
|
||||
} from "../fixtures/binarySession.js";
|
||||
|
||||
const recordingFactories = (failure?: Error) => {
|
||||
const sessions: BinarySession[] = [];
|
||||
let clientsClosed = 0;
|
||||
const createSession = () => {
|
||||
const session = createTestBinarySession(() => ({
|
||||
execute: (operation) => {
|
||||
if (failure !== undefined && operation === "read_bytes")
|
||||
return Promise.reject(failure);
|
||||
return Promise.resolve(observed({ operation }));
|
||||
},
|
||||
close: () => {
|
||||
clientsClosed += 1;
|
||||
return Promise.resolve();
|
||||
},
|
||||
}));
|
||||
sessions.push(session);
|
||||
return session;
|
||||
};
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
createBinarySession: createSession,
|
||||
createManagedBinarySession: createSession,
|
||||
};
|
||||
return { dependencies, sessions, clientsClosed: () => clientsClosed };
|
||||
};
|
||||
|
||||
describe("one-shot analysis factory boundary", () => {
|
||||
it("keeps independent commands and releases their actual session clients", async () => {
|
||||
const [path] = await createBinarySessionTargets();
|
||||
const factories = recordingFactories();
|
||||
const first = parseEvidence(
|
||||
await runDirectAnalysis(factories.dependencies, path, "read_bytes", {}),
|
||||
);
|
||||
const second = parseEvidence(
|
||||
await runDirectAnalysis(factories.dependencies, path, "read_bytes", {}),
|
||||
);
|
||||
expect(first).toMatchObject({
|
||||
operation: "read_bytes",
|
||||
normalized_result: { operation: "read_bytes" },
|
||||
subject: { local_path: path },
|
||||
});
|
||||
expect(second.subject).toEqual(first.subject);
|
||||
expect(factories.sessions).toHaveLength(2);
|
||||
expect(factories.sessions[0]).not.toBe(factories.sessions[1]);
|
||||
expect(factories.clientsClosed()).toBe(2);
|
||||
for (const session of factories.sessions) {
|
||||
expect(session.activeTarget()).toBeUndefined();
|
||||
expect(session.evidenceById(first.evidence_id)).toBeUndefined();
|
||||
expect(session.evidenceById(second.evidence_id)).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves thrown failures while releasing clients and cancellation listeners", async () => {
|
||||
const [path] = await createBinarySessionTargets();
|
||||
const failure = new Error("fixture execution failed");
|
||||
const factories = recordingFactories(failure);
|
||||
const listeners = process.listenerCount("SIGINT");
|
||||
await expect(
|
||||
runDirectAnalysis(factories.dependencies, path, "read_bytes", {}),
|
||||
).rejects.toBe(failure);
|
||||
expect(factories.clientsClosed()).toBe(1);
|
||||
expect(process.listenerCount("SIGINT")).toBe(listeners);
|
||||
expect(factories.sessions[0]?.activeTarget()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("does not acquire a client after cancellation before target opening", async () => {
|
||||
const [path] = await createBinarySessionTargets();
|
||||
const factories = recordingFactories();
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
const result = await runDirectAnalysis(
|
||||
factories.dependencies,
|
||||
path,
|
||||
"read_bytes",
|
||||
{},
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
error: "Analysis failed",
|
||||
code: "cancelled",
|
||||
});
|
||||
expect(factories.clientsClosed()).toBe(0);
|
||||
expect(factories.sessions[0]?.activeTarget()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps managed execution independent of native construction", async () => {
|
||||
const [path] = await createBinarySessionTargets();
|
||||
const factories = recordingFactories();
|
||||
const result = await runManagedProviderExecution(
|
||||
{
|
||||
...factories.dependencies,
|
||||
createBinarySession: () => {
|
||||
throw new Error("native factory must stay unused");
|
||||
},
|
||||
},
|
||||
path,
|
||||
"inspect_managed_artifact",
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
value: { result: { operation: "inspect_managed_artifact" } },
|
||||
});
|
||||
expect(factories.clientsClosed()).toBe(1);
|
||||
expect(factories.sessions[0]?.activeTarget()).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("status factory configuration", () => {
|
||||
it("passes the selected environment and logger without opening a target", async () => {
|
||||
const factories = recordingFactories();
|
||||
const selected: string[] = [];
|
||||
await runCapabilityStatus(
|
||||
{
|
||||
createBinarySession: (config, logger) => {
|
||||
selected.push(config.analysisProvider);
|
||||
expect(logger).toBeDefined();
|
||||
return factories.dependencies.createBinarySession(config, logger);
|
||||
},
|
||||
},
|
||||
undefined,
|
||||
{ REA_ANALYSIS_PROVIDER: "ghidra" },
|
||||
);
|
||||
expect(selected).toEqual(["ghidra"]);
|
||||
expect(factories.clientsClosed()).toBe(0);
|
||||
expect(factories.sessions[0]?.activeTarget()).toBeUndefined();
|
||||
});
|
||||
|
||||
it("retains configuration diagnostics before acquiring any session", async () => {
|
||||
const factories = recordingFactories();
|
||||
const result = await runCapabilityStatus(
|
||||
factories.dependencies,
|
||||
undefined,
|
||||
{ REA_ANALYSIS_PROVIDER: "invalid_provider" },
|
||||
);
|
||||
expect(result).toHaveProperty("error");
|
||||
expect(factories.sessions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
|
@ -8,6 +8,40 @@ import { describe, expect, it } from "vitest";
|
|||
import { inspectModuleBoundaries } from "../../scripts/lib/module-boundaries.mjs";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
|
||||
describe("binary production construction ownership", () => {
|
||||
it.each([
|
||||
"hopper/HopperProvider.js",
|
||||
"ghidra/GhidraProvider.js",
|
||||
"ida/IdaProvider.js",
|
||||
"artifacts/ArtifactProvider.js",
|
||||
"dotnet/ManagedStaticProvider.js",
|
||||
"native/NativeMacOSProvider.js",
|
||||
])("keeps %s in production composition", (provider) => {
|
||||
const source = `import { Provider } from "../${provider}";`;
|
||||
for (const file of ["src/application/runtime.ts", "src/server/probe.ts"])
|
||||
expect(
|
||||
inspectModuleBoundaries(file, source, process.cwd()),
|
||||
).toMatchObject([{ boundary: "provider-construction" }]);
|
||||
expect(
|
||||
inspectModuleBoundaries(
|
||||
"src/composition/binary.ts",
|
||||
source,
|
||||
process.cwd(),
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it("rejects the former runtime composition exception", () => {
|
||||
expect(
|
||||
inspectModuleBoundaries(
|
||||
"src/application/runtime.ts",
|
||||
'export { createBinarySession } from "../composition/binary.js";',
|
||||
process.cwd(),
|
||||
),
|
||||
).toMatchObject([{ boundary: "application-composition" }]);
|
||||
});
|
||||
});
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const verifier = resolve("scripts/verify-module-boundaries.mjs");
|
||||
|
||||
|
|
@ -93,7 +127,7 @@ describe("incremental module import boundaries", () => {
|
|||
"../../android/JadxProvider.js",
|
||||
false,
|
||||
],
|
||||
["src/application/runtime.ts", "../android/JadxProvider.js", true],
|
||||
["src/application/runtime.ts", "../android/JadxProvider.js", false],
|
||||
[
|
||||
"src/application/android/runtime.ts",
|
||||
"../../android/JadxProvider.js",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { ELECTRON_TOOL_CONTRACTS } from "../../dist/contracts/javascript/electro
|
|||
import { JAVASCRIPT_RUNTIME_OBSERVATION_TOOL_CONTRACTS } from "../../dist/contracts/javascript/javascriptRuntimeObservationToolContracts.js";
|
||||
import { TOOL_CONTRACTS } from "../../dist/contracts/toolContracts.js";
|
||||
import { parseConfig } from "../../dist/config.js";
|
||||
import { createBinarySession } from "../../dist/application/runtime.js";
|
||||
import { createBinarySession } from "../../dist/composition/binary.js";
|
||||
|
||||
// Mutate only this child's presentation arrays, before importing registration.
|
||||
if (process.argv[2] === "reversed") {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { join } from "node:path";
|
|||
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { runProviderAnalysis } from "../../src/application/DirectAnalysis.js";
|
||||
import { runProviderAnalysis } from "../../src/composition/directAnalysis.js";
|
||||
import type { AnalysisError } from "../../src/domain/analysisErrorBase.js";
|
||||
import { type Evidence, parseEvidence } from "../../src/domain/evidence.js";
|
||||
import type { Result } from "../../src/domain/result.js";
|
||||
|
|
|
|||
Loading…
Reference in New Issue