mirror of
https://github.com/morluto/rea.git
synced 2026-10-19 04:19:54 -05:00
Allow the composed one-shot dispatch workflow to open a covered Mach-O without selecting a deep provider. The registry still owns selector precedence; explicit request/environment choices remain binding, while non-Mach-O and unsupported hosts retain deep symbol fallback. Requested snapshots keep ordinary binding and profile semantics. Share runtime composition and target admission with the native byte reader. Keep target parsing, resource ownership and Evidence on the original session. Validation: 13 focused files, 192 passed and 2 skipped; npm run check passed. The six previously blocked native property CLI controls now pass. Ten measured CLI workflows preserved exact metadata facts; warm raw reply token medians 1344/927, elapsed .475/.547s, CPU .691/.745s, sampled family RSS351.2/359.3MiB baseline/candidate. Baseline used explicit Ghidra workaround; true cold and full-agent costs unmeasured. No speed/memory improvement or Windows claim. Refs #4555
300 lines
10 KiB
TypeScript
300 lines
10 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
runDirectAnalysis,
|
|
runManagedProviderExecution,
|
|
runProviderAnalysis,
|
|
} from "../../src/application/DirectAnalysis.js";
|
|
import type { DirectAnalysisDependencies } from "../../src/application/DirectAnalysisDependencies.js";
|
|
import { parseConfig } from "../../src/config/parseConfig.js";
|
|
import { analysisCliErrorEnvelopeSchema } from "../../src/contracts/errorSchemas.js";
|
|
import type { AnalysisError } from "../../src/domain/analysisErrorBase.js";
|
|
import { AnalysisTimeoutError } from "../../src/domain/analysisErrorCore.js";
|
|
import { projectAnalysisError } from "../../src/domain/analysisErrorProjection.js";
|
|
import { parseEvidence } from "../../src/domain/evidence.js";
|
|
import { ProviderCleanupError } from "../../src/domain/providerCleanupError.js";
|
|
import { err, ok } from "../../src/domain/result.js";
|
|
import { isCliOperationFailure } from "../../src/cliLogging.js";
|
|
import { observed } from "../fixtures/analysisExecution.js";
|
|
import {
|
|
createBinarySessionTargets,
|
|
createTestBinarySession,
|
|
} from "../fixtures/binarySession.js";
|
|
|
|
const cleanupError = new ProviderCleanupError(
|
|
"fixture",
|
|
["fixture-provider-process"],
|
|
{ reason: "process exit could not be confirmed" },
|
|
{ remediationAction: "Close the retained fixture process before retrying." },
|
|
);
|
|
|
|
const primaryError = new AnalysisTimeoutError("read_bytes", 100, {
|
|
capturedOutput: {
|
|
stdout: "retained observation",
|
|
stderr: "",
|
|
truncated: false,
|
|
},
|
|
});
|
|
|
|
const factories = (
|
|
options: {
|
|
readonly executionError?: AnalysisError;
|
|
readonly thrownExecution?: Error;
|
|
readonly cleanupError?: AnalysisError;
|
|
readonly thrownCleanup?: Error;
|
|
} = {},
|
|
) => {
|
|
const session = createTestBinarySession(() => ({
|
|
execute: async (operation) => {
|
|
if (operation === "health") return observed(null);
|
|
if (options.thrownExecution !== undefined) throw options.thrownExecution;
|
|
return options.executionError === undefined
|
|
? observed({ operation, observation: "retained result" })
|
|
: err(options.executionError);
|
|
},
|
|
close: async () => {
|
|
if (options.thrownCleanup !== undefined) throw options.thrownCleanup;
|
|
return options.cleanupError === undefined
|
|
? ok(null)
|
|
: err(options.cleanupError);
|
|
},
|
|
}));
|
|
const dependencies: DirectAnalysisDependencies = {
|
|
readConfiguration: () => parseConfig({}),
|
|
createBinarySession: () => session,
|
|
createArtifactBinarySession: () => session,
|
|
createNativeDispatchBinarySession: () => session,
|
|
createNativeBinarySession: () => session,
|
|
createManagedBinarySession: () => session,
|
|
};
|
|
return { dependencies, session };
|
|
};
|
|
|
|
describe("one-shot analysis cleanup outcomes", () => {
|
|
it("retains successful evidence and reports CLI failure when close fails", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies, session } = factories({ cleanupError });
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
{},
|
|
);
|
|
expect(result).toMatchObject({
|
|
error: "Analysis failed",
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
resources: ["fixture-provider-process"],
|
|
partial_observation: {
|
|
operation: "read_bytes",
|
|
subject: { local_path: path },
|
|
normalized_result: { observation: "retained result" },
|
|
raw_result: null,
|
|
},
|
|
},
|
|
});
|
|
expect(isCliOperationFailure(result)).toBe(true);
|
|
expect(session.activeTarget()).toBeUndefined();
|
|
});
|
|
|
|
it("preserves primary failure diagnostics alongside an unsuccessful close", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies } = factories({
|
|
executionError: primaryError,
|
|
cleanupError,
|
|
});
|
|
const primary = projectAnalysisError(primaryError);
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
{},
|
|
);
|
|
const callerError = analysisCliErrorEnvelopeSchema.parse(result);
|
|
expect(callerError).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
category: primary.category,
|
|
message: primary.message,
|
|
remediation: {
|
|
action: "Close the retained fixture process before retrying.",
|
|
},
|
|
details: {
|
|
execution_failure: primary.code,
|
|
captured_output: primary.details?.captured_output,
|
|
cleanup_error: projectAnalysisError(cleanupError),
|
|
},
|
|
});
|
|
expect(callerError.details).not.toHaveProperty("primary_error");
|
|
expect(
|
|
JSON.stringify(callerError).match(/retained observation/g),
|
|
).toHaveLength(1);
|
|
expect(isCliOperationFailure(result)).toBe(true);
|
|
});
|
|
|
|
it("reports provider close rejection with the successful observation", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies } = factories({
|
|
thrownCleanup: new Error("unexpected close rejection"),
|
|
});
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
{},
|
|
);
|
|
expect(result).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
resources: ["provider-client"],
|
|
diagnostics: { reason: "provider client close rejected unexpectedly" },
|
|
partial_observation: {
|
|
normalized_result: { observation: "retained result" },
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it.each([false, true])(
|
|
"retains original cleanup diagnostics when final close fails: %s",
|
|
async (closeFails) => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const executionError = new AnalysisTimeoutError("read_bytes", 100, {
|
|
cleanup: {
|
|
reason: "snapshot removal failed during execution",
|
|
resources: ["primary-snapshot"],
|
|
},
|
|
capturedOutput: primaryError.capturedOutput,
|
|
});
|
|
const primary = projectAnalysisError(executionError);
|
|
const { dependencies } = factories({
|
|
executionError,
|
|
...(closeFails ? { cleanupError } : {}),
|
|
});
|
|
const result = analysisCliErrorEnvelopeSchema.parse(
|
|
await runDirectAnalysis(dependencies, path, "read_bytes", {}),
|
|
);
|
|
expect(result).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
...primary.details,
|
|
...(closeFails
|
|
? { cleanup_error: projectAnalysisError(cleanupError) }
|
|
: {}),
|
|
},
|
|
});
|
|
expect(
|
|
JSON.stringify(result).match(/retained observation/g),
|
|
).toHaveLength(1);
|
|
if (!closeFails)
|
|
expect(result.details).not.toHaveProperty("cleanup_error");
|
|
},
|
|
);
|
|
|
|
it("retains both unexpected execution failure and cleanup failure", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const thrownExecution = new Error("unexpected execution rejection");
|
|
const { dependencies } = factories({ thrownExecution, cleanupError });
|
|
await expect(
|
|
runDirectAnalysis(dependencies, path, "read_bytes", {}),
|
|
).rejects.toMatchObject({
|
|
cause: thrownExecution,
|
|
errors: [thrownExecution, cleanupError],
|
|
});
|
|
});
|
|
|
|
it("keeps successful evidence unchanged after confirmed cleanup", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies, session } = factories();
|
|
const result = await runDirectAnalysis(
|
|
dependencies,
|
|
path,
|
|
"read_bytes",
|
|
{},
|
|
);
|
|
expect(parseEvidence(result)).toMatchObject({
|
|
operation: "read_bytes",
|
|
normalized_result: { observation: "retained result" },
|
|
});
|
|
expect(isCliOperationFailure(result)).toBe(false);
|
|
expect(session.activeTarget()).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe("managed one-shot cleanup outcomes", () => {
|
|
it("returns a managed cleanup error retaining the complete successful execution", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies } = factories({ cleanupError });
|
|
const result = await runManagedProviderExecution(
|
|
dependencies,
|
|
path,
|
|
"inspect_managed_artifact",
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) return;
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
remediation: {
|
|
action: "Close the retained fixture process before retrying.",
|
|
},
|
|
details: {
|
|
resources: ["fixture-provider-process"],
|
|
diagnostics: {
|
|
cleanup_error: projectAnalysisError(cleanupError),
|
|
partial_observation: {
|
|
result: {
|
|
operation: "inspect_managed_artifact",
|
|
observation: "retained result",
|
|
},
|
|
rawResult: null,
|
|
provider: { id: "fixture" },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("retains the typed managed primary error when close also fails", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies } = factories({
|
|
executionError: primaryError,
|
|
cleanupError,
|
|
});
|
|
const result = await runManagedProviderExecution(
|
|
dependencies,
|
|
path,
|
|
"inspect_managed_artifact",
|
|
);
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) return;
|
|
expect(result.error.cause).toBeInstanceOf(AggregateError);
|
|
expect(result.error.cause).toMatchObject({
|
|
cause: primaryError,
|
|
errors: [primaryError, cleanupError],
|
|
});
|
|
expect(projectAnalysisError(result.error)).toMatchObject({
|
|
code: "cleanup_incomplete",
|
|
details: {
|
|
diagnostics: {
|
|
primary_error: projectAnalysisError(primaryError),
|
|
cleanup_error: projectAnalysisError(cleanupError),
|
|
},
|
|
},
|
|
});
|
|
});
|
|
|
|
it("exposes managed cleanup failure to the CLI error classifier", async () => {
|
|
const [path] = await createBinarySessionTargets();
|
|
const { dependencies } = factories({ cleanupError });
|
|
const result = await runProviderAnalysis(
|
|
dependencies,
|
|
path,
|
|
"inspect_managed_artifact",
|
|
{},
|
|
);
|
|
expect(isCliOperationFailure(result)).toBe(true);
|
|
expect(result).toMatchObject({
|
|
error: "Analysis failed",
|
|
code: "cleanup_incomplete",
|
|
});
|
|
});
|
|
});
|