mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(cli): route static artifact analysis without native engines
Static .app decoding failed under auto selection when multiple deep engines were installed. Compose the existing artifact provider without native candidates and reuse the shared one-shot target, Evidence and cleanup lifecycle. Preserve the selected provider environment. Validation: real NIB filesystem and built CLI cases, artifact direct workflow and direct-analysis lifecycle tests pass (57 tests with MCP controls).
This commit is contained in:
@@ -10,7 +10,10 @@ import type { Logger } from "pino";
|
||||
import { createEvidence } from "../domain/evidence.js";
|
||||
import type { Evidence } from "../domain/evidence.js";
|
||||
import type { NativeToolName } from "../contracts/native/nativeToolContracts.js";
|
||||
import type { ArtifactAnalysisOperation } from "../contracts/artifactToolContracts.js";
|
||||
import {
|
||||
ARTIFACT_ANALYSIS_OPERATIONS,
|
||||
type ArtifactAnalysisOperation,
|
||||
} from "../contracts/artifactToolContracts.js";
|
||||
import {
|
||||
isManagedToolName,
|
||||
type ManagedToolName,
|
||||
@@ -79,6 +82,10 @@ type DirectAnalysisTool =
|
||||
| "trace_native_ui_action"
|
||||
| "trace_native_values";
|
||||
|
||||
const ARTIFACT_ANALYSIS_OPERATION_SET: ReadonlySet<string> = new Set(
|
||||
ARTIFACT_ANALYSIS_OPERATIONS,
|
||||
);
|
||||
|
||||
/**
|
||||
* Open one binary, execute one tool, and always release provider resources.
|
||||
* Unlike MCP mode, every CLI invocation is intentionally isolated and does not
|
||||
@@ -129,16 +136,26 @@ export const runProviderAnalysis = async (
|
||||
logger?: Logger,
|
||||
signal?: AbortSignal,
|
||||
]
|
||||
): Promise<JsonValue> =>
|
||||
isManagedToolName(tool)
|
||||
? runManagedProviderAnalysis(dependencies, path, tool, arguments_, signal)
|
||||
: withProcessCancellation(signal, (operationSignal) =>
|
||||
runAnalysis(dependencies, path, tool, arguments_, {
|
||||
logger,
|
||||
snapshotPath: undefined,
|
||||
signal: operationSignal,
|
||||
}),
|
||||
);
|
||||
): Promise<JsonValue> => {
|
||||
if (isManagedToolName(tool))
|
||||
return runManagedProviderAnalysis(
|
||||
dependencies,
|
||||
path,
|
||||
tool,
|
||||
arguments_,
|
||||
signal,
|
||||
);
|
||||
return withProcessCancellation(signal, (operationSignal) =>
|
||||
runAnalysis(dependencies, path, tool, arguments_, {
|
||||
logger,
|
||||
snapshotPath: undefined,
|
||||
signal: operationSignal,
|
||||
...(ARTIFACT_ANALYSIS_OPERATION_SET.has(tool)
|
||||
? { createSession: dependencies.createArtifactBinarySession }
|
||||
: {}),
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
/** Execute managed metadata inspection in an isolated managed-only session. */
|
||||
export const runManagedProviderExecution = async (
|
||||
@@ -205,6 +222,7 @@ const runAnalysis = async (
|
||||
readonly logger: Logger;
|
||||
readonly snapshotPath: string | undefined;
|
||||
readonly signal: AbortSignal;
|
||||
readonly createSession?: () => BinarySession | Promise<BinarySession>;
|
||||
readonly providerId?: AnalysisProviderSelector;
|
||||
readonly formatHint?: ExecutableFormatHint;
|
||||
readonly architecture?: BinaryArchitecture;
|
||||
@@ -212,9 +230,14 @@ const runAnalysis = async (
|
||||
},
|
||||
): Promise<JsonValue> => {
|
||||
const { logger, signal, snapshotPath } = options;
|
||||
const config = dependencies.readConfiguration();
|
||||
if (!config.ok) return cliError(config.error);
|
||||
const session = await dependencies.createBinarySession(config.value, logger);
|
||||
let session: BinarySession;
|
||||
if (options.createSession !== undefined) {
|
||||
session = await options.createSession();
|
||||
} else {
|
||||
const config = dependencies.readConfiguration();
|
||||
if (!config.ok) return cliError(config.error);
|
||||
session = await dependencies.createBinarySession(config.value, logger);
|
||||
}
|
||||
return withSessionCleanup(
|
||||
session,
|
||||
async () => {
|
||||
|
||||
@@ -11,6 +11,10 @@ export interface DirectAnalysisDependencies {
|
||||
config: AppConfig,
|
||||
logger: Logger,
|
||||
) => BinarySession | Promise<BinarySession>;
|
||||
/** Create an artifact-only session without selecting native analysis engines. */
|
||||
readonly createArtifactBinarySession: () =>
|
||||
| BinarySession
|
||||
| Promise<BinarySession>;
|
||||
readonly createManagedBinarySession: () =>
|
||||
| BinarySession
|
||||
| Promise<BinarySession>;
|
||||
|
||||
@@ -41,3 +41,17 @@ export const createManagedBinarySession = (): BinarySession =>
|
||||
composeBinarySession(new AnalysisProviderRegistry([]), [
|
||||
new ManagedStaticProvider(),
|
||||
]);
|
||||
|
||||
/** Compose the artifact operations without candidates for deep analysis engines. */
|
||||
export const createArtifactBinarySession = (
|
||||
selectedEnvironment: Readonly<NodeJS.ProcessEnv>,
|
||||
platform: NodeJS.Platform = process.platform,
|
||||
): Promise<BinarySession> =>
|
||||
import("../artifacts/ArtifactProvider.js").then(({ ArtifactProvider }) =>
|
||||
composeBinarySession(new AnalysisProviderRegistry([]), [
|
||||
new ArtifactProvider(
|
||||
snapshotEnvironment(selectedEnvironment, platform),
|
||||
platform,
|
||||
),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -28,6 +28,10 @@ export const createDirectAnalysis = (
|
||||
const { createManagedBinarySession } = await import("./binary.js");
|
||||
return createManagedBinarySession();
|
||||
},
|
||||
createArtifactBinarySession: async () => {
|
||||
const { createArtifactBinarySession } = await import("./binary.js");
|
||||
return createArtifactBinarySession(environment);
|
||||
},
|
||||
};
|
||||
const runDirectAnalysis: BoundOperation<
|
||||
DirectAnalysisRuntime["runDirectAnalysis"]
|
||||
|
||||
@@ -243,6 +243,7 @@ const createSnapshotReplayFixture = async (
|
||||
return {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: () => createTestBinarySession(provider),
|
||||
createArtifactBinarySession: () => createTestBinarySession(provider),
|
||||
createManagedBinarySession: () => createTestBinarySession(provider),
|
||||
};
|
||||
},
|
||||
@@ -924,6 +925,7 @@ describe("workflow snapshot profile and cancellation binding", () => {
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: () => session,
|
||||
createArtifactBinarySession: () => session,
|
||||
createManagedBinarySession: () => session,
|
||||
};
|
||||
const result = await runDirectAnalysis(
|
||||
|
||||
@@ -220,7 +220,7 @@ describe.skipIf(process.platform !== "darwin")(
|
||||
REA_ANALYSIS_PROVIDER: "auto",
|
||||
},
|
||||
});
|
||||
expect(output.exitCode).toBe(0);
|
||||
expect(output.exitCode, JSON.stringify(output)).toBe(0);
|
||||
const result = interfaceBuilderAnalysisSchema.parse(
|
||||
parseEvidence(output.json).normalized_result,
|
||||
);
|
||||
|
||||
@@ -70,6 +70,7 @@ describe("official operation arguments", () => {
|
||||
const dependencies = {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: createSession,
|
||||
createArtifactBinarySession: createSession,
|
||||
createManagedBinarySession: createSession,
|
||||
};
|
||||
const client = await connect({ execute });
|
||||
|
||||
@@ -330,6 +330,7 @@ describe("MCP composed workflow snapshot replay", () => {
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: () => createWorkflowSession(provider),
|
||||
createArtifactBinarySession: () => createWorkflowSession(provider),
|
||||
createManagedBinarySession: () => createWorkflowSession(provider),
|
||||
};
|
||||
const replay = await runDirectAnalysis(
|
||||
|
||||
@@ -6,11 +6,51 @@ import { expect, it } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
import { createDirectAnalysis } from "../../../../src/composition/directAnalysis.js";
|
||||
import { createArtifactBinarySession } from "../../../../src/composition/binary.js";
|
||||
import { thinMach } from "../../../../src/domain/binaryTarget.fixture.js";
|
||||
import { interfaceBuilderAnalysisSchema } from "../../../../src/domain/apple/interfaceBuilderGraph.js";
|
||||
import { artifactInspectionResultSchema } from "../../../../src/domain/artifactInspection.js";
|
||||
import { parseEvidence } from "../../../../src/domain/evidence.js";
|
||||
|
||||
const { runProviderAnalysis } = createDirectAnalysis({});
|
||||
|
||||
it("runs static .app decoding without registering native analysis engines", async () => {
|
||||
const root = await createTestTempDirectory("rea-artifact-only-direct-");
|
||||
const app = join(root, "Fixture.app");
|
||||
const contents = join(app, "Contents");
|
||||
await mkdir(join(contents, "MacOS"), { recursive: true });
|
||||
await mkdir(join(contents, "Resources"), { recursive: true });
|
||||
await writeFile(
|
||||
join(contents, "Info.plist"),
|
||||
"<plist><dict><key>CFBundleExecutable</key><string>Fixture</string></dict></plist>",
|
||||
);
|
||||
await writeFile(
|
||||
join(contents, "MacOS", "Fixture"),
|
||||
thinMach(0xfeedfacf, 0x0100000c),
|
||||
);
|
||||
|
||||
const staticSession = await createArtifactBinarySession({
|
||||
REA_ANALYSIS_PROVIDER: "auto",
|
||||
});
|
||||
expect(staticSession.status()).toMatchObject({
|
||||
analysis_provider_candidates: [],
|
||||
});
|
||||
await staticSession.close();
|
||||
|
||||
const evidence = parseEvidence(
|
||||
await createDirectAnalysis({
|
||||
REA_ANALYSIS_PROVIDER: "auto",
|
||||
}).runProviderAnalysis(app, "decode_interface_builder", {}),
|
||||
);
|
||||
expect(evidence.operation).toBe("decode_interface_builder");
|
||||
expect(evidence.provider.id).toBe("rea-artifact-graph");
|
||||
const result = interfaceBuilderAnalysisSchema.parse(
|
||||
evidence.normalized_result,
|
||||
);
|
||||
expect(result.documents).toEqual([]);
|
||||
expect(result.graph.target_sha256).toBe(evidence.subject?.digest.sha256);
|
||||
});
|
||||
|
||||
it("inspects an artifact through the direct CLI workflow without an REA grant", async () => {
|
||||
const root = await createTestTempDirectory("rea-artifact-direct-");
|
||||
const source = join(root, "source");
|
||||
|
||||
@@ -60,6 +60,7 @@ const factories = (
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: () => session,
|
||||
createArtifactBinarySession: () => session,
|
||||
createManagedBinarySession: () => session,
|
||||
};
|
||||
return { dependencies, session };
|
||||
|
||||
@@ -34,6 +34,7 @@ const recordingFactories = (failure?: Error) => {
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: createSession,
|
||||
createArtifactBinarySession: createSession,
|
||||
createManagedBinarySession: createSession,
|
||||
};
|
||||
return { dependencies, sessions, clientsClosed: () => clientsClosed };
|
||||
|
||||
@@ -82,6 +82,7 @@ const fixture = (
|
||||
const dependencies: DirectAnalysisDependencies = {
|
||||
readConfiguration: () => parseConfig({}),
|
||||
createBinarySession: createSession,
|
||||
createArtifactBinarySession: createSession,
|
||||
createManagedBinarySession: createSession,
|
||||
};
|
||||
return { dependencies, closed: () => closed };
|
||||
|
||||
Reference in New Issue
Block a user