refactor(cli): retain typed analysis outcomes through delivery

This commit is contained in:
morluto committed 2026-10-27 09:16:02 +08:00
1 parent 0e1500dc8d
commit 329325049c
79 files changed
+581 -372

No files matched your search

+37 -31
View File
@@ -132,7 +132,7 @@ export const runDirectAnalysis = async (
readonly architecture?: BinaryArchitecture;
readonly entryPoint?: string;
} = {},
): Promise<JsonValue> =>
): Promise<Result<Evidence, JsonValue>> =>
withProcessCancellation(options.signal, (signal) =>
runAnalysis(dependencies, path, tool, arguments_, {
logger: options.logger ?? silentLogger,
@@ -175,31 +175,35 @@ export const runProviderAnalysis = async (
readonly evidenceBundlePath?: string;
},
]
): Promise<JsonValue> => {
): Promise<Result<Evidence, JsonValue>> => {
if (delivery !== undefined && tool !== "inspect_managed_members")
return cliError(
new AnalysisInputError(tool, undefined, [
{
path: ["delivery"],
reason: "invalid_value",
message:
"Evidence delivery options are only supported by inspect_managed_members",
},
]),
return err(
cliError(
new AnalysisInputError(tool, undefined, [
{
path: ["delivery"],
reason: "invalid_value",
message:
"Evidence delivery options are only supported by inspect_managed_members",
},
]),
),
);
if (
delivery?.detail === "summary" &&
delivery.evidenceBundlePath === undefined
)
return cliError(
new AnalysisInputError(tool, undefined, [
{
path: ["evidence_bundle"],
reason: "missing_argument",
message:
"summary detail requires --evidence-bundle so complete Evidence remains available to later CLI commands",
},
]),
return err(
cliError(
new AnalysisInputError(tool, undefined, [
{
path: ["evidence_bundle"],
reason: "missing_argument",
message:
"summary detail requires --evidence-bundle so complete Evidence remains available to later CLI commands",
},
]),
),
);
return withProcessCancellation(signal, async (operationSignal) => {
// The selected CLI path is a request fact. Keep it out of provider inputs
@@ -215,14 +219,16 @@ export const runProviderAnalysis = async (
operationSignal,
);
await dispatchQueuedInterrupts();
if (!original.ok) return cliError(original.error);
if (!original.ok) return err(cliError(original.error));
const evidence = withEvidenceCallerInputPath(original.value, path);
if (operationSignal.aborted)
return cliError(
new AnalysisCancelledError(tool, {
cause: operationSignal.reason,
partialObservation: evidence,
}),
return err(
cliError(
new AnalysisCancelledError(tool, {
cause: operationSignal.reason,
partialObservation: evidence,
}),
),
);
const result =
delivery?.evidenceBundlePath === undefined
@@ -233,7 +239,7 @@ export const runProviderAnalysis = async (
delivery.detail,
operationSignal,
);
return result.ok ? result.value : cliError(result.error);
return result.ok ? ok(result.value) : err(cliError(result.error));
}
return runAnalysis(dependencies, path, tool, arguments_, {
logger,
@@ -321,7 +327,7 @@ const runAnalysis = async (
readonly architecture?: BinaryArchitecture;
readonly entryPoint?: string;
},
): Promise<JsonValue> => {
): Promise<Result<Evidence, JsonValue>> => {
const { logger, signal, snapshotPath } = options;
const evidenceParameters = options.evidenceParameters ?? arguments_;
let session: BinarySession;
@@ -329,7 +335,7 @@ const runAnalysis = async (
session = await options.createSession();
} else {
const config = dependencies.readConfiguration();
if (!config.ok) return cliError(config.error);
if (!config.ok) return err(cliError(config.error));
session = await (
tool === "inspect_native_dispatch_metadata" && snapshotPath === undefined
? dependencies.createNativeDispatchBinarySession
@@ -549,9 +555,9 @@ const runAnalysis = async (
outcome.cleanupError.retainDiagnostics({
analysis_snapshot: { ...publishedSnapshot },
});
return directAnalysisCleanupFailure(result, outcome.cleanupError);
return err(directAnalysisCleanupFailure(result, outcome.cleanupError));
}
return result.ok ? result.value : cliError(result.error);
return result.ok ? ok(result.value) : err(cliError(result.error));
};
const executeAnalysisTool = async (input: {
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
import { AdbDeviceAnalysisService } from "../application/adb/AdbDeviceAnalysisService.js";
@@ -33,7 +34,7 @@ export const registerAdbCommands = (
"adb",
operation,
signal,
),
).then(projectCliAnalysisResult),
),
);
+5 -11
View File
@@ -18,6 +18,7 @@ import { inspectAnalysisViewValidated } from "../application/analysisView/Analys
import { localPathStringSchema } from "../domain/localPath.js";
import { absoluteCallerPath } from "../filesystem/callerPath.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import type { ZodSafeParseResult } from "zod";
@@ -33,13 +34,6 @@ export const analysisEvidenceDeliveryOptions = {
),
};
const isEvidence = (value: JsonValue): value is Evidence =>
typeof value === "object" &&
value !== null &&
!Array.isArray(value) &&
"evidence_id" in value &&
"normalized_result" in value;
interface CliEvidenceDelivery {
readonly view?: AnalysisViewRequest;
readonly providerId: string;
@@ -143,7 +137,7 @@ export const deliverCliAnalysisEvidence = async (
signal,
undefined,
{ redactFailure },
);
).then(projectCliAnalysisResult);
const complete = await finishAnalysisCommand(
run,
service,
@@ -171,7 +165,7 @@ export const deliverCliAnalysisEvidence = async (
/** Publish already finalized CLI analysis without opening a second provider/session. */
export const publishCliAnalysisEvidence = async (
value: JsonValue,
result: Result<Evidence, JsonValue>,
input: CliEvidenceDelivery,
): Promise<JsonValue> => {
const { operation, signal, detail, evidenceOutput } = input;
@@ -179,8 +173,8 @@ export const publishCliAnalysisEvidence = async (
projectAnalysisError(
redactExplicitFailure(error, input.sensitiveValues ?? []),
);
if (!isEvidence(value)) return value;
const complete = parseEvidence(value);
if (!result.ok) return result.error;
const complete = parseEvidence(result.value);
const inputError = cliEvidenceDeliveryInputError(input);
if (inputError !== undefined) {
inputError.retainPartialObservation(complete);
+5 -3
View File
@@ -119,9 +119,11 @@ export const registerAnalysisViewCommands = (
);
// Preserve the inline command's existing error label without copying
// the authenticated completed Evidence or its partial observation.
return bundlePath === undefined && "code" in result
? { error: "Application workflow failed", ...result }
: result;
return result.ok
? result.value
: bundlePath === undefined
? { error: "Application workflow failed", ...result.error }
: result.error;
}),
),
});
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { AndroidAnalysisService } from "../application/android/AndroidAnalysisService.js";
import { createAndroidAnalysisProvider } from "../composition/android.js";
@@ -26,7 +27,7 @@ export const registerAndroidCommands = (
"jadx",
operation,
signal,
),
).then(projectCliAnalysisResult),
),
);
const path = z.string().describe("Local standalone APK path");
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { ApktoolResourceAnalysisService } from "../application/apktool/ApktoolResourceAnalysisService.js";
import type { ApktoolResourceAnalysisPort } from "../application/apktool/ApktoolResourceAnalysisPort.js";
@@ -32,7 +33,7 @@ export const registerApktoolCommands = (
"apktool",
operation,
signal,
),
).then(projectCliAnalysisResult),
),
);
+6 -5
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import {
analysisEvidenceDeliveryOptions,
cliEvidenceDeliveryInputError,
@@ -63,7 +64,7 @@ export const registerArtifactCommands = (
...(options.root === undefined ? {} : { root: options.root }),
},
logger,
),
).then(projectCliAnalysisResult),
),
});
};
@@ -103,7 +104,7 @@ const registerDylibResolutionCommand = (
: { architecture: options.architecture }),
},
logger,
),
).then(projectCliAnalysisResult),
),
});
};
@@ -138,7 +139,7 @@ const registerAssetCatalogCommand = (
"inspect_asset_catalog",
{ offset: options.offset, limit: options.limit },
logger,
),
).then(projectCliAnalysisResult),
),
});
};
@@ -188,7 +189,7 @@ const registerInterfaceBuilderCommand = (
max_connections: options.maxConnections,
},
logger,
),
).then(projectCliAnalysisResult),
),
});
};
@@ -220,7 +221,7 @@ const registerExtractionCommand = (
output_root: createArtifactExtractionDestination(),
},
logger,
),
).then(projectCliAnalysisResult),
),
});
};
+9 -8
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { inspectDevicetreeInputSchema } from "../domain/firmware/devicetree.js";
import { inspectPeLoadConfigInputSchema } from "../domain/native/peLoadConfig.js";
import { inspectByteEntropyInputSchema } from "../domain/native/byteEntropy.js";
@@ -76,7 +77,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-elf-notes",
"inspect_elf_notes",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
@@ -120,7 +121,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-apple-ips",
"inspect_apple_ips",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
@@ -157,7 +158,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-kernel-module",
"inspect_kernel_module",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
@@ -192,7 +193,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-pe-load-config",
"inspect_pe_load_config",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
@@ -230,7 +231,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-byte-entropy",
"inspect_byte_entropy",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -265,7 +266,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-pe-certificates",
"inspect_pe_certificates",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
@@ -300,7 +301,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-pe-imports",
"inspect_pe_imports",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
@@ -335,7 +336,7 @@ export const registerBinaryDiagnosticsCommands = (
"rea-pe-resources",
"inspect_pe_resources",
signal,
),
).then(projectCliAnalysisResult),
);
}),
});
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { Cli, z } from "incur";
import { browserCliError } from "./browserCliError.js";
@@ -96,7 +97,7 @@ const registerWebMcp = (
provider.identity().id,
"discover_webmcp_tools",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -207,7 +208,7 @@ const registerScreenshot = (
provider.identity().id,
"capture_web_screenshot",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+4 -3
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { webBundleDeliveryOptions } from "./javascriptRouteDelivery.js";
import {
deliverCliAnalysisEvidence,
@@ -87,7 +88,7 @@ const registerTargetList = (
provider.identity().id,
"list_browser_targets",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -144,7 +145,7 @@ const registerPageInspection = (
provider.identity().id,
"inspect_web_page",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -283,7 +284,7 @@ const registerObservationSession = (
provider.identity().id,
"observe_web_session",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { inspectPacketFieldsInputSchema } from "../domain/captures/packetFields.js";
import { z } from "incur";
import type { PacketCaptureService } from "../application/captures/PacketCaptureService.js";
@@ -62,7 +63,7 @@ export const registerCaptureCommands = (
"tshark-capture",
"inspect_packet_fields",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
@@ -117,7 +118,7 @@ export const registerCaptureCommands = (
"tshark-capture",
"inspect_packet_capture",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+8 -7
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { realpath, stat } from "node:fs/promises";
import { findAppBundleResourceRoot } from "../application/AppBundleExecutable.js";
@@ -166,7 +167,7 @@ const registerOverviewCommands = (
"binary_overview",
{},
directAnalysisOptionsFromCli(logger, options),
);
).then(projectCliAnalysisResult);
});
},
});
@@ -183,7 +184,7 @@ const registerOverviewCommands = (
"binary_overview",
{},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -244,7 +245,7 @@ const registerDecompileCommand = (
),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -321,7 +322,7 @@ const registerXrefsCommand = (
),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -371,7 +372,7 @@ const registerTraceCommand = (
case_sensitive: options.caseSensitive,
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -475,7 +476,7 @@ const registerInstructionsCommand = (
),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -536,7 +537,7 @@ const registerSearchCommand = (
case_sensitive: options.caseSensitive,
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
+11 -10
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { nativeDispatchMetadataInputSchema } from "../domain/native/objcSwiftMetadata.js";
import { z } from "incur";
import {
@@ -68,7 +69,7 @@ export const registerCoreNativeCommands = (
...(options.cursor === undefined ? {} : { cursor: options.cursor }),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
cli.command(CLI_COMMANDS.inspectNativeLoadImage, {
@@ -123,7 +124,7 @@ export const registerCoreNativeCommands = (
"read_bytes",
{ address: args.address, length: options.length },
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
cli.command(CLI_COMMANDS.addressToFileOffset, {
@@ -142,7 +143,7 @@ export const registerCoreNativeCommands = (
"address_to_file_offset",
{ address: args.address },
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
cli.command(CLI_COMMANDS.traceNativeValues, {
@@ -220,7 +221,7 @@ export const registerCoreNativeCommands = (
limit: options.limit,
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
cli.command(CLI_COMMANDS.inspectNativeDataType, {
@@ -245,7 +246,7 @@ export const registerCoreNativeCommands = (
: { address: options.address }),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
for (const [command, operation] of [
@@ -271,7 +272,7 @@ export const registerCoreNativeCommands = (
operation,
{ address: args.address },
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
}
@@ -312,7 +313,7 @@ const registerNativeDispatchMetadataCommand = (
: { max_input_bytes: options.maxInputBytes }),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -370,7 +371,7 @@ const registerNativeUiActionCommand = (
max_edges: options.maxEdges,
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -413,7 +414,7 @@ const registerNativeApiCommand = (
),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
@@ -459,7 +460,7 @@ const registerAnnotationCommand = (
: { inline_comment: options["inline-comment"] }),
},
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import type { Logger } from "pino";
@@ -33,7 +34,7 @@ export const registerCutterCommands = (
"cutter",
"list_cutter_sessions",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -80,7 +81,7 @@ export const registerCutterCommands = (
"cutter",
"cutter_command",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import type { DirectAnalysis } from "../composition/directAnalysis.js";
@@ -87,7 +88,7 @@ export const registerDecompileDirectoryCommand = (
"decompile_all",
decompileAllArguments(options),
directAnalysisOptionsFromCli(logger, options),
),
).then(projectCliAnalysisResult),
),
});
};
+7 -6
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { parseCliAnalysisEvidenceDelivery } from "./analysisEvidenceDelivery.js";
import { javaScriptRoutesDeliverySchema } from "../domain/analysisView/javascriptRoutesView.js";
import { isAbsolute, resolve, sep } from "node:path";
@@ -78,7 +79,7 @@ const registerElectronFuseCommand = (
service.provider.identity.id,
"inspect_electron_fuses",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -158,9 +159,9 @@ const registerElectronActiveCommand = (
"capture_electron_scenario",
signal,
);
return "code" in result
? { error: "Electron analysis failed", ...result }
: result;
return result.ok
? result.value
: { error: "Electron analysis failed", ...result.error };
},
),
),
@@ -249,7 +250,7 @@ const registerElectronTargetList = (
provider.identity().id,
"list_electron_targets",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -298,7 +299,7 @@ const registerElectronPageInspection = (
provider.identity().id,
"inspect_electron_page",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+3 -10
View File
@@ -330,12 +330,6 @@ const conflictingRules = (): JsonValue =>
]),
);
const isEvidenceValue = (value: unknown): boolean =>
typeof value === "object" &&
value !== null &&
"evidence_id" in value &&
"normalized_result" in value;
/** Inspect a HAR as inspect-web-network-capture does and serialize the Evidence as it prints it; a refusal is the command's output. */
const inspectBuildHar = async (
capturePath: string,
@@ -345,7 +339,6 @@ const inspectBuildHar = async (
): Promise<Result<EnvironmentExportFile, JsonValue>> => {
const { createWebNetworkCaptureService } =
await import("../composition/webNetworkCaptures.js");
const { parseEvidence } = await import("../domain/evidence.js");
const { environmentExportEvidenceFile } =
await import("../application/environment/environmentExport.js");
const { ENVIRONMENT_BUILD_OPERATION } =
@@ -368,15 +361,15 @@ const inspectBuildHar = async (
"inspect_web_network_capture",
signal,
);
if (!isEvidenceValue(inspected))
if (!inspected.ok)
return err({
error: "Application workflow failed",
...(inspected as Record<string, JsonValue>),
...inspected.error,
});
const file = await environmentExportEvidenceFile(
"evidence",
capturePath,
parseEvidence(inspected),
inspected.value,
ENVIRONMENT_BUILD_OPERATION,
);
return file.ok ? ok(file.value) : err(cliError(file.error));
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { evmInterfaceModule } from "../composition/evm.js";
import { logCliCommand } from "../cliLogging.js";
@@ -46,7 +47,7 @@ export const registerEvmCommands = (
"evmole-interface",
"inspect_evm_interface",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
+4 -4
View File
@@ -8,7 +8,7 @@ import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
import type { Result } from "../domain/result.js";
import { err, ok, type Result } from "../domain/result.js";
import { dispatchQueuedInterrupts } from "../process/queuedInterrupts.js";
/** Finish a one-shot CLI operation while retaining primary results through close. */
@@ -30,7 +30,7 @@ export const finishAnalysisCommand = async <
observation: AnalysisPartialObservation,
) => Value | undefined;
} = {},
): Promise<Value | ReturnType<typeof projectAnalysisError>> => {
): Promise<Result<Value, ReturnType<typeof projectAnalysisError>>> => {
const projectFailure = (error: AnalysisError) =>
projectAnalysisError(options.redactFailure?.(error) ?? error);
let result: Result<Value, AnalysisError>;
@@ -78,7 +78,7 @@ export const finishAnalysisCommand = async <
}
}
await dispatchQueuedInterrupts();
if (initialFailure !== undefined) return initialFailure;
if (initialFailure !== undefined) return err(initialFailure);
if (
!result.ok &&
result.error instanceof ProviderCleanupError &&
@@ -126,5 +126,5 @@ export const finishAnalysisCommand = async <
).bindCleanupDependencies([result.error, cleanup]),
};
}
return result.ok ? result.value : projectFailure(result.error);
return result.ok ? ok(result.value) : err(projectFailure(result.error));
};
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { FirmwareAnalysisService } from "../application/firmware/FirmwareAnalysisService.js";
import { createFirmwareAnalysisProvider } from "../composition/firmware.js";
@@ -36,7 +37,7 @@ export const registerFirmwareCommands = (
operation === "inspect_firmware_regions" ? "binwalk" : "unblob",
operation,
signal,
),
).then(projectCliAnalysisResult),
),
);
const path = localPathStringSchema.describe(
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { evidenceSchema } from "../domain/evidence.js";
import { z } from "incur";
import { FlutterBuildAnalysisService } from "../application/flutter/FlutterBuildAnalysisService.js";
@@ -88,7 +89,7 @@ export const registerFlutterCommands = (
return parsed.success ? parsed.data : undefined;
},
},
),
).then(projectCliAnalysisResult),
),
),
});
+4 -3
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { FridaInstrumentationService } from "../application/frida/FridaInstrumentationService.js";
@@ -96,7 +97,7 @@ export const registerFridaCommands = (
"frida",
"list_frida_devices",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
@@ -135,7 +136,7 @@ export const registerFridaCommands = (
"frida",
"list_frida_processes",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -284,7 +285,7 @@ export const registerFridaCommands = (
"frida",
"instrument_with_frida",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+7 -3
View File
@@ -134,10 +134,14 @@ export const runCliJavaScriptApplicationAnalysis = async (
),
);
}
const value = result.ok ? result.value : cliError(result.error);
const finalized = result.ok
? result
: { ok: false as const, error: cliError(result.error) };
return delivery === undefined
? value
: publishCliAnalysisEvidence(value, {
? finalized.ok
? finalized.value
: finalized.error
: publishCliAnalysisEvidence(finalized, {
...delivery,
providerId: JAVASCRIPT_APPLICATION_PROVIDER.id,
operation: "analyze_javascript_application",
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { javascriptRecoveryInputSchema } from "../domain/javascript/javascriptRecovery.js";
import { z } from "incur";
import { JavaScriptRecoveryService } from "../application/javascript/JavaScriptRecoveryService.js";
@@ -65,7 +66,7 @@ export const registerJavaScriptRecoveryCommands = (
"wakaru",
"recover_javascript_sources",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { Cli, z } from "incur";
import {
@@ -75,7 +76,7 @@ export const registerJavaScriptRuntimeObservationCommands = (
"list_javascript_runtime_targets",
signal,
"V8 Inspector resources",
);
).then(projectCliAnalysisResult);
},
),
),
@@ -123,7 +124,7 @@ export const registerJavaScriptRuntimeObservationCommands = (
"observe_javascript_runtime",
signal,
"V8 Inspector resources",
);
).then(projectCliAnalysisResult);
},
),
),
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { JebAnalysisService } from "../application/jeb/JebAnalysisService.js";
import type { JebAnalysisPort } from "../application/jeb/JebAnalysisPort.js";
@@ -28,7 +29,7 @@ export const registerJebCommands = (
"jeb",
operation,
signal,
),
).then(projectCliAnalysisResult),
),
);
+12 -6
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { compareManagedMemberPaths } from "../application/managed/ManagedMemberComparisonService.js";
@@ -44,7 +45,12 @@ const registerInspectManagedArtifact = (
}),
run: ({ args }) =>
logCliCommand(logger, "inspect-managed-artifact", () =>
runProviderAnalysis(args.path, "inspect_managed_artifact", {}, logger),
runProviderAnalysis(
args.path,
"inspect_managed_artifact",
{},
logger,
).then(projectCliAnalysisResult),
),
});
};
@@ -90,7 +96,7 @@ const registerInspectManagedMembers = (
? {}
: { evidenceBundlePath: options.evidenceBundle }),
},
),
).then(projectCliAnalysisResult),
),
),
});
@@ -114,7 +120,7 @@ const registerInspectManagedNativeBoundaries = (
"inspect_managed_native_boundaries",
{},
logger,
),
).then(projectCliAnalysisResult),
),
});
};
@@ -152,9 +158,9 @@ const registerCompareManagedMembers = (
signal,
"Managed member comparison resources",
);
return "evidence_id" in result
? result
: { error: "Managed member comparison failed", ...result };
return result.ok
? result.value
: { error: "Managed member comparison failed", ...result.error };
}),
),
});
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import type { DirectAnalysis } from "../composition/directAnalysis.js";
@@ -51,7 +52,7 @@ export const registerNativeCallCommands = (
OPERATION,
z.record(z.string(), jsonValueSchema).parse(parsed.data),
logger,
);
).then(projectCliAnalysisResult);
}),
});
};
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { PcapAnalysisService } from "../application/pcap/PcapAnalysisService.js";
import type { PcapAnalysisPort } from "../application/pcap/PcapAnalysisPort.js";
@@ -27,7 +28,7 @@ export const registerPcapCommands = (
"pcap",
operation,
signal,
),
).then(projectCliAnalysisResult),
),
);
+7
View File
@@ -0,0 +1,7 @@
import type { JsonValue } from "../domain/jsonValue.js";
import type { Result } from "../domain/result.js";
/** Flatten a finalized outcome only when returning the command's JSON output. */
export const projectCliAnalysisResult = <Value>(
result: Result<Value, JsonValue>,
): Value | JsonValue => (result.ok ? result.value : result.error);
+18 -5
View File
@@ -185,7 +185,10 @@ export const registerReverseEngineeringCommands = (
),
);
return result.ok
? publishCliAnalysisEvidence(result.value, delivery)
? publishCliAnalysisEvidence(
{ ok: true, value: result.value },
delivery,
)
: projectAnalysisError(result.error);
}),
),
@@ -225,7 +228,10 @@ export const registerReverseEngineeringCommands = (
),
);
return result.ok
? publishCliAnalysisEvidence(result.value, delivery)
? publishCliAnalysisEvidence(
{ ok: true, value: result.value },
delivery,
)
: projectAnalysisError(result.error);
},
),
@@ -309,7 +315,11 @@ export const registerReverseEngineeringCommands = (
const started = await manager.start(signal, {
inferiorTty: options.inferiorTty,
});
if (!started.ok) return projectAnalysisError(started.error);
if (!started.ok)
return {
ok: false as const,
error: projectAnalysisError(started.error),
};
sessionId = started.value.session_id;
const executed = await manager.execute(
sessionId,
@@ -317,8 +327,11 @@ export const registerReverseEngineeringCommands = (
signal,
);
return executed.ok
? executed.value.evidence
: projectAnalysisError(executed.error);
? { ok: true as const, value: executed.value.evidence }
: {
ok: false as const,
error: projectAnalysisError(executed.error),
};
} finally {
let cleanupFailed = false;
if (sessionId !== undefined) {
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { inspectSignalDecodersInputSchema } from "../domain/signals/signalDecoders.js";
import { z } from "incur";
import type { SignalRecordingService } from "../application/signals/SignalRecordingService.js";
@@ -64,7 +65,7 @@ export const registerSignalCommands = (
"sigrok-recording",
"inspect_signal_decoders",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
@@ -119,7 +120,7 @@ export const registerSignalCommands = (
"sigrok-recording",
"decode_signal_recording",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+4 -3
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import type { Logger } from "pino";
import type { CliInstance } from "./types.js";
@@ -68,9 +69,9 @@ export const registerSourceToBundleCommand = (
"compare_source_to_bundle",
signal,
);
return "evidence_id" in result
? result
: { error: "Application workflow failed", ...result };
return result.ok
? result.value
: { error: "Application workflow failed", ...result.error };
}),
),
});
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { createSqliteDatabaseService } from "../composition/sqlite.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
@@ -80,7 +81,7 @@ export const registerSqliteCommands = (
"sqlite",
"inspect_sqlite_database",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
+6 -5
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import type { DirectAnalysis } from "../composition/directAnalysis.js";
@@ -108,7 +109,7 @@ const registerNativeUiCommands = (
"observe_native_ui",
nativeUiScope(options),
logger,
),
).then(projectCliAnalysisResult),
),
});
const operation = "capture_native_ui_scenario";
@@ -152,7 +153,7 @@ const registerNativeUiCommands = (
? scenario
: { ...scenario, native_ui_deadline_ms: deadline },
logger,
);
).then(projectCliAnalysisResult);
}),
});
};
@@ -209,7 +210,7 @@ const registerNativeCommands = (
? {}
: { native_command_timeout_ms: options.nativeCommandTimeoutMs },
logger,
),
).then(projectCliAnalysisResult),
),
});
}
@@ -239,7 +240,7 @@ const registerNativeCommands = (
: { native_command_timeout_ms: options.nativeCommandTimeoutMs }),
},
logger,
),
).then(projectCliAnalysisResult),
),
});
cli.command(CLI_COMMANDS.demangleSwift, {
@@ -263,7 +264,7 @@ const registerNativeCommands = (
: { native_command_timeout_ms: options.nativeCommandTimeoutMs }),
},
logger,
),
).then(projectCliAnalysisResult),
),
});
};
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { compareWasmInterfaces } from "../application/wasm/WasmInterfaceComparisonService.js";
import { parseCliJsonInput } from "../cliJsonInput.js";
@@ -85,7 +86,7 @@ export const registerWasmCommands = (
"wabt-artifact",
"inspect_wasm_artifact",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { createWebModuleTraceService } from "../composition/webModules.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
@@ -67,7 +68,7 @@ export const registerWebModuleCommands = (
"web-module-trace",
"trace_web_module_imports",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { Cli, z } from "incur";
import { createWebRuntimeService } from "../composition/webRuntime.js";
import { browserCliError } from "./browserCliError.js";
@@ -70,7 +71,7 @@ export const registerWebRuntimeCommands = (
service.provider.identity().id,
"observe_web_execution",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
@@ -111,7 +112,7 @@ export const registerWebRuntimeCommands = (
service.provider.identity().id,
"inspect_web_event_listeners",
signal,
);
).then(projectCliAnalysisResult);
}),
),
});
+3 -3
View File
@@ -47,9 +47,9 @@ export const registerWebScriptCommands = (
signal,
"Web script export resources",
);
return "evidence_id" in result
? result
: { error: "Script export failed", ...result };
return result.ok
? result.value
: { error: "Script export failed", ...result.error };
}),
),
});
+2 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
import { z } from "incur";
import { createWebSourceLocationService } from "../composition/webSourceLocations.js";
import { webSourceLocationInputSchema } from "../domain/webSourceLocation.js";
@@ -62,7 +63,7 @@ export const registerWebSourceLocationCommands = (
"web-source-location",
"trace_web_source_location",
signal,
),
).then(projectCliAnalysisResult),
),
),
});
+5 -2
View File
@@ -83,8 +83,11 @@ export const registerBrowserScenarioCommands = (
undefined,
{ failureProjection: "initial" },
);
if (!("evidence_id" in result))
return { error: "Browser scenario capture failed", ...result };
if (!result.ok)
return {
error: "Browser scenario capture failed",
...result.error,
};
return publishCliAnalysisEvidence(result, {
providerId: "rea-playwright-browser-scenario",
operation: OPERATION,
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import {
@@ -89,7 +90,9 @@ cliTest(
await writer.add(name, new Uint8ArrayReader(fixture.nativeBytes));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const input = { inventory_evidence: [inventory] };
const result = await cli.run({
@@ -43,7 +43,7 @@ it.each(["capabilities", "providers"])(
readConfiguration: () => parseConfig({}),
createBinarySession: () => session,
}),
runProviderAnalysis: async () => null,
runProviderAnalysis: async () => ({ ok: false, error: null }),
},
);
let stdout = "";
@@ -132,7 +132,7 @@ it.each(["capabilities", "providers"])(
}),
createBinarySession: () => session,
}),
runProviderAnalysis: async () => null,
runProviderAnalysis: async () => ({ ok: false, error: null }),
},
);
let stdout = "";
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { expect, it } from "vitest";
import { finishAnalysisCommand } from "../../../src/cli/finishAnalysisCommand.js";
@@ -24,7 +25,7 @@ it("preserves producer-owned cleanup failure when service close does not resolve
new AbortController().signal,
);
expect(result).toEqual(expected);
expect(result).toEqual(err(expected));
expect(failure.cleanupIncomplete).toBe(true);
});
@@ -44,8 +45,11 @@ it("returns completed partial Evidence when cancellation arrives during close",
);
expect(result).toMatchObject({
code: "cancelled",
details: { partial_observation: { evidence_id: evidence.evidence_id } },
ok: false,
error: {
code: "cancelled",
details: { partial_observation: { evidence_id: evidence.evidence_id } },
},
});
});
@@ -62,7 +66,7 @@ it("preserves the primary operation error when cancellation arrives during close
"fixture-provider",
"inspect_web_page",
controller.signal,
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "unreadable_output",
@@ -92,7 +96,7 @@ it("retains late cancellation and completed Evidence when close also fails", asy
"fixture-provider",
"inspect_web_page",
controller.signal,
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "cleanup_incomplete",
category: "cancelled",
@@ -134,7 +138,7 @@ it.each([false, true])(
controller.signal,
undefined,
{ recoverCompletedObservation: (observation) => observation },
);
).then(projectCliAnalysisResult);
if (!cancel) expect(result).toEqual(evidence);
else
expect(result).toMatchObject({
@@ -188,7 +192,7 @@ it.each(["unresolved", "ordinary-partial", "initial-projection"] as const)(
return observation;
},
},
);
).then(projectCliAnalysisResult);
expect(attemptedRecovery).toBe(false);
expect(result).toEqual(
kind === "initial-projection" ? initial : projectAnalysisError(failure),
+18 -9
View File
@@ -280,7 +280,10 @@ describe("CLI result output surface", () => {
if (output === undefined)
throw new Error("Missing structured output surface");
const cli = Cli.create("rea-output-test", { sync: false });
registerManagedCommands(cli, silentLogger, async () => failure);
registerManagedCommands(cli, silentLogger, async () => ({
ok: false,
error: failure,
}));
streamCliCommandResults(cli, output);
let fallback = "";
const previousExitCode = process.exitCode;
@@ -408,12 +411,15 @@ describe("full-output token windows", () => {
if (output === undefined) throw new Error("Missing TOON output surface");
const cli = Cli.create("rea-output-test", { sync: false });
registerManagedCommands(cli, silentLogger, async () => ({
code: "target_unavailable",
category: "unavailable",
message: "The selected artifact is missing.",
retryable: false,
remediation: { action: "Check that the path exists." },
details: { source: "artifact-open" },
ok: false,
error: {
code: "target_unavailable",
category: "unavailable",
message: "The selected artifact is missing.",
retryable: false,
remediation: { action: "Check that the path exists." },
details: { source: "artifact-open" },
},
}));
streamCliCommandResults(cli, output);
const previousExitCode = process.exitCode;
@@ -667,7 +673,7 @@ describe("CLI result output surface", () => {
registerManagedCommands(cli, silentLogger, async (...[path, operation]) => {
expect(path).toBe("fixture.dll");
expect(operation).toBe("inspect_managed_members");
return value;
return { ok: false, error: value };
});
// A terminal destination exercises the indented encoding past the limit.
Object.defineProperty(destination, "isTTY", { value: true });
@@ -707,7 +713,10 @@ describe("CLI result output surface", () => {
if (tokenOutput === undefined)
throw new Error("Missing token-count output surface");
const tokenCli = Cli.create("rea-output-test", { sync: false });
registerManagedCommands(tokenCli, silentLogger, async () => value);
registerManagedCommands(tokenCli, silentLogger, async () => ({
ok: false,
error: value,
}));
streamCliCommandResults(tokenCli, tokenOutput);
await tokenCli.serve(tokenArgv, {
stdout: (text) => {
@@ -216,7 +216,10 @@ const registeredComparisonCommand = () => {
return cli;
},
} as unknown as Parameters<typeof registerManagedCommands>[0];
registerManagedCommands(cli, silentLogger, async () => null);
registerManagedCommands(cli, silentLogger, async () => ({
ok: false,
error: null,
}));
return commands.get(CLI_COMMANDS.compareManagedMembers)!;
};
@@ -31,7 +31,7 @@ it("preserves the relative request path in a load-image CLI summary and its comp
),
selectedPath,
);
const run = vi.fn(async () => parent);
const run = vi.fn(async () => ({ ok: true as const, value: parent }));
const cli = Cli.create("rea-load-image-cli-test", { sync: false });
registerCoreNativeCommands(cli, pino({ enabled: false }), run);
let stdout = "";
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { EventEmitter } from "node:events";
import { expect, it } from "vitest";
import { WasmArtifactService } from "../../../src/application/wasm/WasmArtifactService.js";
@@ -31,7 +32,7 @@ it("delivers cancellation with completed WASM Evidence through the CLI boundary"
"wabt-artifact",
"inspect_wasm_artifact",
signal,
),
).then(projectCliAnalysisResult),
{
on: events.on.bind(events),
off: events.off.bind(events),
@@ -13,15 +13,12 @@ import { describe, expect, it } from "vitest";
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
import {
createNonApplicationZipInventory,
inventoryApplicationPackage,
requireSuccessfulProjection,
} from "../../../support/applicationSessionFixture.js";
import { projectAndroidApplicationEvidence } from "../../../../src/application/android/AndroidApplicationService.js";
import { createDirectAnalysis } from "../../../../src/composition/directAnalysis.js";
import { androidApplicationProjectionResultSchema } from "../../../../src/domain/android/androidApplication.js";
import { parseEvidence } from "../../../../src/domain/evidence.js";
const { runProviderAnalysis } = createDirectAnalysis({});
describe("Android application projection", () => {
it("projects deterministic APK components and explicit bridge hypotheses", async () => {
@@ -50,9 +47,7 @@ describe("Android application projection", () => {
await writer.add("META-INF/FIXTURE.RSA", new TextReader("opaque signing"));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const first = projectAndroidApplicationEvidence({
inventory_evidence: [inventory],
});
@@ -144,9 +139,7 @@ describe("Android application projection", () => {
await writer.add(entry, new TextReader("unrecognized bytes"));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const result = projectAndroidApplicationEvidence({
inventory_evidence: [inventory],
});
@@ -194,9 +187,7 @@ it("projects APK v1 metadata candidates at any META-INF depth", async () => {
await writer.add(entry, new TextReader(`content:${entry}`));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const projection = androidApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
projectAndroidApplicationEvidence({ inventory_evidence: [inventory] }),
@@ -239,9 +230,7 @@ describe("APK family without a root manifest", () => {
for (const [entry, text] of Object.entries(entries))
await writer.add(entry, new TextReader(text));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
return androidApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
projectAndroidApplicationEvidence({ inventory_evidence: [inventory] }),
@@ -269,9 +258,7 @@ it("retains encrypted native library paths as unknown component evidence", async
{ password: "fixture-only" },
);
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const projection = androidApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
projectAndroidApplicationEvidence({ inventory_evidence: [inventory] }),
@@ -314,9 +301,7 @@ describe("case-distinct APK resource names", () => {
await writer.add("res/2f.xml", new TextReader("lower"));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
expect(inventory.normalized_result).toMatchObject({
coverage: { scope: "container-entries", status: "complete" },
});
@@ -358,9 +343,7 @@ describe("Android bridge candidate coverage", () => {
await writer.add("lib/arm64-v8a/libnative.so", new TextReader("native"));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const result = projectAndroidApplicationEvidence({
inventory_evidence: [inventory],
});
@@ -392,9 +375,7 @@ describe("Android bridge candidate coverage", () => {
);
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const result = projectAndroidApplicationEvidence({
inventory_evidence: [inventory],
});
@@ -439,9 +420,7 @@ it("does not infer a runtime from library-named directories or their text descen
await writer.add(`assets/${name}/NOTICE.txt`, new TextReader("inert text"));
}
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const graph = androidApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
projectAndroidApplicationEvidence({ inventory_evidence: [inventory] }),
@@ -460,9 +439,7 @@ it("retains an encrypted root manifest occurrence without claiming absence", asy
password: "fixture-only",
});
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
);
const inventory = await inventoryApplicationPackage(path);
const graph = androidApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
projectAndroidApplicationEvidence({ inventory_evidence: [inventory] }),
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { mkdir, symlink, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
@@ -58,7 +59,9 @@ async function createCompleteAppleProjection() {
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const fragments = fragmentInventoryEvidence(inventory, 3);
const result = projectAppleApplicationEvidence({
@@ -94,7 +97,9 @@ describe("Apple application projection", () => {
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(inventory.normalized_result).toMatchObject({
coverage: { scope: "container-entries", status: "complete" },
@@ -147,7 +152,9 @@ describe("Apple application projection", () => {
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const first = projectAppleApplicationEvidence({
inventory_evidence: [inventory],
@@ -216,7 +223,9 @@ describe("Apple application projection", () => {
await writer.add("AndroidManifest.xml", new TextReader("<manifest/>"));
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(
projectAppleApplicationEvidence({ inventory_evidence: [inventory] }),
@@ -250,7 +259,9 @@ async function projectArchive(
);
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
return appleApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
@@ -352,7 +363,9 @@ describe("macOS application projection", () => {
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const projection = appleApplicationProjectionResultSchema.parse(
requireSuccessfulProjection(
@@ -403,7 +416,9 @@ describe("macOS application projection", () => {
);
const inventory = parseEvidence(
await runProviderAnalysis(app, "inventory_artifact", {}),
await runProviderAnalysis(app, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const result = projectAppleApplicationEvidence({
inventory_evidence: [inventory],
@@ -476,7 +491,9 @@ describe("Apple application projection completeness", () => {
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const result = projectAppleApplicationEvidence({
inventory_evidence: [inventory],
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { readFile, symlink, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
@@ -48,7 +49,9 @@ it.skipIf(process.platform === "win32").each([
expect(admitted.value.format).toBe(format);
const { runProviderAnalysis } = createDirectAnalysis({});
const observed = parseEvidence(
await runProviderAnalysis(selected, "inventory_artifact", {}),
await runProviderAnalysis(selected, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(observed.subject?.local_path).toBe(selected);
expect(
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { mkdir, open, readFile, truncate, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { createPackage } from "@electron/asar";
@@ -293,7 +294,9 @@ it("applies the caller-selected environment, not the ambient one, to inventory a
vi.stubEnv("REA_ASAR_MAX_HEADER_BYTES", "1");
for (const operation of ["inventory_artifact", "inspect_artifact"] as const) {
await expect(
createDirectAnalysis(lowered).runProviderAnalysis(archive, operation, {}),
createDirectAnalysis(lowered)
.runProviderAnalysis(archive, operation, {})
.then(projectCliAnalysisResult),
).resolves.toMatchObject({
code: "truncated",
message: recovery,
@@ -304,7 +307,9 @@ it("applies the caller-selected environment, not the ambient one, to inventory a
},
});
await expect(
createDirectAnalysis({}).runProviderAnalysis(archive, operation, {}),
createDirectAnalysis({})
.runProviderAnalysis(archive, operation, {})
.then(projectCliAnalysisResult),
).resolves.toMatchObject({ operation });
}
const refused = await analyzeJavaScriptApplication(
@@ -365,11 +370,9 @@ it("refuses invalid settings at startup validation and at each owner", async ()
const { archive } = await createFixture();
const invalid = { REA_ASAR_MAX_HEADER_BYTES: "0" };
await expect(
createDirectAnalysis(invalid).runProviderAnalysis(
archive,
"inventory_artifact",
{},
),
createDirectAnalysis(invalid)
.runProviderAnalysis(archive, "inventory_artifact", {})
.then(projectCliAnalysisResult),
).resolves.toMatchObject({ code: "configuration_invalid" });
const analyzed = await analyzeJavaScriptApplication(
{ input_path: archive },
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
import { createDirectAnalysis } from "../../../src/composition/directAnalysis.js";
@@ -179,7 +180,9 @@ it.skipIf(process.platform === "win32")(
await symlink(stored, selected);
const { runProviderAnalysis } = createDirectAnalysis({});
const observed = parseEvidence(
await runProviderAnalysis(selected, "inventory_artifact", {}),
await runProviderAnalysis(selected, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(observed.subject?.local_path).toBe(selected);
const inventory = artifactInventoryResultSchema.parse(
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { executeComposedWorkflow } from "../../../src/application/WorkflowEvidence.js";
import { parseAnalysisSnapshot } from "../../../src/domain/analysisSnapshot.js";
import { ok } from "../../../src/domain/result.js";
@@ -24,7 +25,7 @@ it.each([0, 1, 12])(
"binary_overview",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
);
expect(current.evidence_links).toHaveLength(1);
const snapshot = parseAnalysisSnapshot(
@@ -42,7 +43,7 @@ it.each([0, 1, 12])(
"binary_overview",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
);
expect(unique.evidence_id).toBe(current.evidence_id);
expect(unique.evidence_links).toEqual(current.evidence_links);
@@ -77,7 +78,7 @@ it.each([0, 1, 12])(
"binary_overview",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
);
expect(delivered.normalized_result).toEqual(current.normalized_result);
expect([current.evidence_id, historical.evidence_id]).not.toContain(
@@ -98,7 +99,7 @@ it.each([0, 1, 12])(
"binary_overview",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
);
expect(refreshed.evidence_id).toBe(delivered.evidence_id);
expect(refreshed.evidence_links).toEqual(delivered.evidence_links);
@@ -134,7 +135,9 @@ it.each([false, true])(
};
try {
const evidence = parseEvidence(
await runDirectAnalysis(dependencies, path, "binary_overview", {}),
await runDirectAnalysis(dependencies, path, "binary_overview", {}).then(
projectCliAnalysisResult,
),
);
expect(evidence.normalized_result).toEqual(summary);
expect(evidence.confidence).toBe("derived");
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { chmod, writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -39,14 +40,16 @@ describe("direct analysis snapshot files", () => {
vi.stubEnv("REA_ANALYSIS_PROVIDER", "environment-provider");
await expect(
createDirectAnalysis(process.env).runDirectAnalysis(
targetPath,
"binary_overview",
{},
{
providerId: "request-provider",
},
),
createDirectAnalysis(process.env)
.runDirectAnalysis(
targetPath,
"binary_overview",
{},
{
providerId: "request-provider",
},
)
.then(projectCliAnalysisResult),
).resolves.toMatchObject({
error: "Analysis failed",
code: "capability_unavailable",
@@ -96,14 +99,16 @@ describe("direct analysis snapshot files", () => {
vi.stubEnv("HOPPER_LAUNCHER_PATH", launcherPath);
await expect(
createDirectAnalysis(process.env).runDirectAnalysis(
process.execPath,
"binary_overview",
{},
{
snapshotPath,
},
),
createDirectAnalysis(process.env)
.runDirectAnalysis(
process.execPath,
"binary_overview",
{},
{
snapshotPath,
},
)
.then(projectCliAnalysisResult),
).resolves.not.toEqual(evidence);
});
});
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { parseConfig } from "../../../src/config/parseConfig.js";
import { readFile, symlink, writeFile } from "node:fs/promises";
import { basename, dirname, join } from "node:path";
@@ -398,7 +399,7 @@ describe("snapshot output cleanup lifecycle", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(parseEvidence(output).operation).toBe("binary_overview");
const saved = await readAnalysisSnapshot(fixture.snapshotPath);
if (!saved.ok) throw saved.error;
@@ -484,7 +485,7 @@ const verifyWindowsSnapshotAdmission = async (
"search_strings",
arguments_,
options,
);
).then(projectCliAnalysisResult);
expect(first).toMatchObject({ operation: "search_strings" });
expect(fixture.starts).toHaveLength(1);
if (selectedPath === "junction")
@@ -496,7 +497,7 @@ const verifyWindowsSnapshotAdmission = async (
"search_strings",
arguments_,
options,
);
).then(projectCliAnalysisResult);
expect(second).toMatchObject({ error: "Analysis failed" });
expect(fixture.starts).toHaveLength(2);
expect(
@@ -529,7 +530,7 @@ it.each([
scenario.tool,
scenario.arguments,
options,
),
).then(projectCliAnalysisResult),
);
const saved = await readAnalysisSnapshot(fixture.snapshotPath);
if (!saved.ok) throw saved.error;
@@ -556,7 +557,7 @@ it.each([
scenario.tool,
scenario.arguments,
options,
),
).then(projectCliAnalysisResult),
);
expect(refreshed.limitations).toEqual(
expect.arrayContaining(first.limitations),
@@ -571,7 +572,7 @@ it.each([
scenario.tool,
scenario.arguments,
options,
);
).then(projectCliAnalysisResult);
expect(replayed).toEqual(refreshed);
expect(fixture.starts).toEqual(starts);
expect(fixture.calls).toEqual(calls);
@@ -613,7 +614,7 @@ describe("direct analysis composed snapshot replay", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(fixture.resolutions).toHaveLength(1);
const first = await runDirectAnalysis(
@@ -622,7 +623,7 @@ describe("direct analysis composed snapshot replay", () => {
scenario.tool,
scenario.arguments,
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(first).toMatchObject({ operation: scenario.tool });
expect(fixture.resolutions).toHaveLength(2);
expect(fixture.starts).toHaveLength(2);
@@ -634,7 +635,7 @@ describe("direct analysis composed snapshot replay", () => {
scenario.tool,
scenario.arguments,
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(replay).toEqual(first);
expect(fixture.resolutions).toHaveLength(3);
expect(fixture.starts).toHaveLength(2);
@@ -675,7 +676,7 @@ describe("direct analysis composed snapshot replay", () => {
scenario.tool,
scenario.arguments,
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(first).toMatchObject({ operation: scenario.tool });
const callsAfterFirst = [...fixture.calls];
const startsAfterFirst = [...fixture.starts];
@@ -693,7 +694,7 @@ describe("direct analysis composed snapshot replay", () => {
scenario.tool,
scenario.arguments,
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(second).toEqual(first);
expect(fixture.calls).toEqual(callsAfterFirst);
@@ -729,7 +730,7 @@ describe("Ghidra composed workflow snapshot replay", () => {
tool,
parameters,
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(first).toMatchObject({ operation: tool });
const snapshot = await readAnalysisSnapshot(fixture.snapshotPath);
if (!snapshot.ok) throw snapshot.error;
@@ -744,7 +745,7 @@ describe("Ghidra composed workflow snapshot replay", () => {
tool,
parameters,
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(replay).toEqual(first);
if (process.platform === "win32") {
// The first provider call in a new CLI session admits its source.
@@ -770,7 +771,7 @@ describe("binary overview snapshot replay", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(parseEvidence(first).caller_input_path).toBe(fixture.path);
expect(fixture.calls.toSorted()).toEqual(operations.toSorted());
expect(fixture.starts).toHaveLength(1);
@@ -799,7 +800,7 @@ describe("binary overview snapshot replay", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(second).toEqual(first);
expect(fixture.calls.toSorted()).toEqual(operations.toSorted());
expect(fixture.starts).toHaveLength(1);
@@ -812,7 +813,7 @@ describe("binary overview snapshot replay", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
const firstEvidence = parseEvidence(first);
const relocatedEvidence = parseEvidence(relocated);
expect(relocatedEvidence).toMatchObject({
@@ -868,7 +869,7 @@ it.skipIf(process.platform === "win32")(
"search_strings",
parameters,
options,
),
).then(projectCliAnalysisResult),
);
expect(first).toMatchObject({
caller_input_path: alias,
@@ -911,7 +912,7 @@ it.skipIf(process.platform === "win32")(
"search_strings",
parameters,
options,
),
).then(projectCliAnalysisResult),
);
expect(replay).toMatchObject({
evidence_id: first.evidence_id,
@@ -953,7 +954,7 @@ it.skipIf(process.platform === "win32")(
"search_strings",
parameters,
options,
),
).then(projectCliAnalysisResult),
);
expect(historicalReplay).toMatchObject({
evidence_id: first.evidence_id,
@@ -1028,14 +1029,14 @@ describe("preview and active route policies", () => {
const first = parseEvidence(
await runDirectAnalysis(dependencies, fixture.path, tool, arguments_, {
snapshotPath: fixture.snapshotPath,
}),
}).then(projectCliAnalysisResult),
);
const starts = fixture.starts.length;
const queries = fixture.queries.length;
const second = parseEvidence(
await runDirectAnalysis(dependencies, fixture.path, tool, arguments_, {
snapshotPath: fixture.snapshotPath,
}),
}).then(projectCliAnalysisResult),
);
if (replayed) expect(second).toEqual(first);
expect(second).toMatchObject({
@@ -1069,7 +1070,7 @@ describe("snapshot replay provider identity", () => {
"search_strings",
{ pattern: "fixture", document: "fixture" },
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
const callsAfterFirst = [...fixture.calls];
const startsAfterFirst = [...fixture.starts];
const firstSnapshot = await readAnalysisSnapshot(fixture.snapshotPath);
@@ -1084,7 +1085,7 @@ describe("snapshot replay provider identity", () => {
"search_strings",
{ pattern: "fixture", document: "fixture" },
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(fixture.calls).toEqual(callsAfterFirst);
expect(fixture.starts).toEqual(startsAfterFirst);
expect(second).toEqual(first);
@@ -1103,7 +1104,7 @@ describe("snapshot replay provider identity", () => {
"search_strings",
{ pattern: "fixture", document: "fixture" },
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(changedProfileResult).not.toEqual(first);
expect(changedProfileResult).toMatchObject({
code: "evidence_integrity_mismatch",
@@ -1130,7 +1131,7 @@ describe("snapshot replay provider identity", () => {
"search_strings",
{ pattern: "fixture", document: "fixture" },
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(changedProviderResult).not.toEqual(first);
expect(changedProviderResult).toMatchObject({
code: "evidence_integrity_mismatch",
@@ -1154,7 +1155,7 @@ const assertEvidenceSurvivesSnapshotSaveFailure = async (): Promise<void> => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "invalid_request",
@@ -1189,7 +1190,7 @@ describe("workflow snapshot profile and cancellation binding", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
const loaded = await readAnalysisSnapshot(fixture.snapshotPath);
if (!loaded.ok) throw loaded.error;
const current = loaded.value.evidence_bundle.records.find(
@@ -1213,7 +1214,7 @@ describe("workflow snapshot profile and cancellation binding", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(refreshed).toMatchObject({
normalized_result: { document: "fixture" },
});
@@ -1233,7 +1234,7 @@ describe("workflow snapshot profile and cancellation binding", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
const controller = new AbortController();
const router = createTestProviderRouter(provider);
@@ -1258,7 +1259,7 @@ describe("workflow snapshot profile and cancellation binding", () => {
"binary_overview",
{},
{ snapshotPath: fixture.snapshotPath, signal: controller.signal },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
error: "Analysis failed",
@@ -1493,7 +1494,7 @@ it("retains a native dispatch workflow snapshot without a deep provider profile"
"inspect_native_dispatch_metadata",
{ max_records: 100 },
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
operation: "inspect_native_dispatch_metadata",
@@ -1514,7 +1515,7 @@ it("retains a native dispatch workflow snapshot without a deep provider profile"
"inspect_native_dispatch_metadata",
{ max_records: 100 },
{ snapshotPath: fixture.snapshotPath },
);
).then(projectCliAnalysisResult);
expect(replay).toEqual(result);
expect(dispatchCalls).toBe(1);
@@ -61,6 +61,27 @@ const bundle = (result = true) =>
),
]);
it("preserves tagged refusal JSON without interpreting Evidence-shaped fields", async () => {
const root = await createTestTempDirectory("rea-refused-delivery-");
const refusal = {
code: "unreadable_output",
evidence_id: "caller-selected diagnostic",
normalized_result: { partial: true },
};
const delivered = await publishCliAnalysisEvidence(
{ ok: false, error: refusal },
{
providerId: "fixture",
operation: "inspect_artifact",
signal: new AbortController().signal,
detail: "summary",
evidenceOutput: join(root, "refused.json"),
},
);
expect(delivered).toBe(refusal);
expect(await readdir(root)).toEqual([]);
});
it("preserves direct duplicate rejection and CLI identity merging for repeated inspection sources", async () => {
const root = await createTestTempDirectory("rea-repeated-delivery-");
const input = join(root, "input.txt");
@@ -91,14 +112,17 @@ it("preserves direct duplicate rejection and CLI identity merging for repeated i
deliverAnalysisEvidence(complete, join(root, "direct.json")),
).rejects.toBeInstanceOf(DuplicateEvidenceRecordError);
const path = join(root, "cli.json");
const delivered = await publishCliAnalysisEvidence(complete, {
providerId: "fixture",
operation: "inspect_artifact",
signal: new AbortController().signal,
detail: "complete",
evidenceOutput: path,
sourceEvidence: [withEvidenceCallerInputPath(complete, "./old"), source],
});
const delivered = await publishCliAnalysisEvidence(
{ ok: true, value: complete },
{
providerId: "fixture",
operation: "inspect_artifact",
signal: new AbortController().signal,
detail: "complete",
evidenceOutput: path,
sourceEvidence: [withEvidenceCallerInputPath(complete, "./old"), source],
},
);
expect(delivered).toEqual(complete);
const saved = await readEvidenceBundle(path);
if (!saved.ok) throw saved.error;
@@ -126,13 +150,16 @@ it("retains a malformed artifact parent and refuses publication through both del
);
if (direct.ok) throw new Error("Expected invalid artifact closure");
const projected = projectAnalysisError(direct.error);
const cli = await publishCliAnalysisEvidence(evidence, {
providerId: "fixture",
operation: "inspect_artifact",
signal: new AbortController().signal,
detail: "complete",
evidenceOutput: join(root, "cli.json"),
});
const cli = await publishCliAnalysisEvidence(
{ ok: true, value: evidence },
{
providerId: "fixture",
operation: "inspect_artifact",
signal: new AbortController().signal,
detail: "complete",
evidenceOutput: join(root, "cli.json"),
},
);
expect(cli).toEqual(projected);
expect(JSON.stringify(projected)).toContain(evidence.evidence_id);
expect(await readdir(root)).toEqual([]);
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { createReadStream } from "node:fs";
import {
open,
@@ -781,7 +782,7 @@ it("retains failed snapshot cleanup and retries it on close", async () => {
"flutter",
"identify_flutter_build",
new AbortController().signal,
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "cleanup_incomplete",
details: {
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../../src/cli/projectCliAnalysisResult.js";
import { readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { TextReader, Uint8ArrayWriter, ZipWriter } from "@zip.js/zip.js";
@@ -299,7 +300,11 @@ describe("HarmonyOS packaging boundaries", () => {
path,
JSON.stringify({ log: { version: "1.2", creator: {}, entries: [] } }),
);
const result = await runProviderAnalysis(path, "inventory_artifact", {});
const result = await runProviderAnalysis(
path,
"inventory_artifact",
{},
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
error: "Analysis failed",
code: "target_unavailable",
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { createHash } from "node:crypto";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -177,7 +178,7 @@ it("retains cancelled source read ownership until service close retries it", asy
"web-module-artifacts",
"trace_web_module_imports",
new AbortController().signal,
);
).then(projectCliAnalysisResult);
expect(finalized).toMatchObject({
code: "cleanup_incomplete",
details: { resources: [sourcePath] },
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { expect, it } from "vitest";
import { createServer } from "../../../src/server/createServer.js";
@@ -9,7 +10,12 @@ import { composedWorkflowFixture } from "../../fixtures/composedWorkflowEvidence
it("returns the same consumed atomic provenance through CLI application and MCP boundaries", async () => {
const cli = await composedWorkflowFixture();
const direct = parseEvidence(
await runDirectAnalysis(cli.dependencies, cli.path, "binary_overview", {}),
await runDirectAnalysis(
cli.dependencies,
cli.path,
"binary_overview",
{},
).then(projectCliAnalysisResult),
);
const fixture = await composedWorkflowFixture();
const server = createServer(
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -86,7 +87,11 @@ it("advertises and executes Android and Apple inventory projections", async () =
const root = await createTestTempDirectory("rea-mobile-graph-mcp-");
const apkInventory = parseEvidence(
await runProviderAnalysis(await writeApk(root), "inventory_artifact", {}),
await runProviderAnalysis(
await writeApk(root),
"inventory_artifact",
{},
).then(projectCliAnalysisResult),
);
const apkSources = fragmentInventoryEvidence(apkInventory, 2).toReversed();
const android = await client.callTool({
@@ -135,7 +140,11 @@ it("advertises and executes Android and Apple inventory projections", async () =
);
const ipaInventory = parseEvidence(
await runProviderAnalysis(await writeIpa(root), "inventory_artifact", {}),
await runProviderAnalysis(
await writeIpa(root),
"inventory_artifact",
{},
).then(projectCliAnalysisResult),
);
const apple = await client.callTool({
name: "project_apple_application_graph",
@@ -152,7 +161,11 @@ it("projects a single inventory page as partial and links only that source", asy
const client = await connect();
const root = await createTestTempDirectory("rea-mobile-partial-page-mcp-");
const inventory = parseEvidence(
await runProviderAnalysis(await writeApk(root), "inventory_artifact", {}),
await runProviderAnalysis(
await writeApk(root),
"inventory_artifact",
{},
).then(projectCliAnalysisResult),
);
const facts = artifactInventoryResultSchema.parse(
inventory.normalized_result,
@@ -271,10 +284,18 @@ it("rejects authentic pages from different roots without retaining either", asyn
const client = await connect();
const root = await createTestTempDirectory("rea-mobile-mixed-pages-mcp-");
const apk = parseEvidence(
await runProviderAnalysis(await writeApk(root), "inventory_artifact", {}),
await runProviderAnalysis(
await writeApk(root),
"inventory_artifact",
{},
).then(projectCliAnalysisResult),
);
const ipa = parseEvidence(
await runProviderAnalysis(await writeIpa(root), "inventory_artifact", {}),
await runProviderAnalysis(
await writeIpa(root),
"inventory_artifact",
{},
).then(projectCliAnalysisResult),
);
const rejected = await client.callTool({
@@ -337,7 +358,9 @@ it.each([
});
await writeFile(path, await writer.close());
const inventory = parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
const reply = await client.callTool({
name: operation,
+4 -1
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { parseConfig } from "../../../src/config/parseConfig.js";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { describe, expect, it, onTestFinished } from "vitest";
@@ -86,7 +87,9 @@ describe("official operation arguments", () => {
],
] as const) {
const cli = parseEvidence(
await runDirectAnalysis(dependencies, path, name, cliParameters),
await runDirectAnalysis(dependencies, path, name, cliParameters).then(
projectCliAnalysisResult,
),
);
const mcp = await client.callTool({ name, arguments: mcpParameters });
expect(mcp.isError).not.toBe(true);
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { parseConfig } from "../../../src/config/parseConfig.js";
import { access, chmod, readFile, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
@@ -341,7 +342,7 @@ describe("MCP composed workflow snapshot replay", () => {
"binary_overview",
{},
{ snapshotPath },
);
).then(projectCliAnalysisResult);
expect(output).not.toHaveProperty("caller_input_path");
expect(replay).toEqual({ ...output, caller_input_path: targetPath });
@@ -642,9 +643,7 @@ const makeProvider = (
]
: operation === "list_documents"
? ["fixture"]
: operation === "list_procedures"
? ["0x1000"]
: [{ address: "0x1000", value: "fixture" }];
: [{ address: "0x1000", value: "fixture" }];
return ok(createAnalysisExecution(result, identity));
},
close: () => Promise.resolve(ok(null)),
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../src/cli/projectCliAnalysisResult.js";
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
@@ -59,7 +60,7 @@ it("routes a CLI app resource path through the resource session and preserves nu
bundlePath,
"inspect_keyed_archive",
{ path: "Resources/Archive.plist" },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
operation: "inspect_keyed_archive",
subject: null,
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../../src/cli/projectCliAnalysisResult.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -18,7 +19,9 @@ describe("managed direct-analysis path boundary", () => {
await writeFile(path, buildManagedPeFixture());
const evidence = parseEvidence(
await runProviderAnalysis(path, "inspect_managed_artifact", {}),
await runProviderAnalysis(path, "inspect_managed_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(evidence).toMatchObject({
operation: "inspect_managed_artifact",
@@ -31,7 +34,9 @@ describe("managed direct-analysis path boundary", () => {
});
const memberEvidence = parseEvidence(
await runProviderAnalysis(path, "inspect_managed_members", {}),
await runProviderAnalysis(path, "inspect_managed_members", {}).then(
projectCliAnalysisResult,
),
);
expect(memberEvidence).toMatchObject({
operation: "inspect_managed_members",
@@ -44,7 +49,11 @@ describe("managed direct-analysis path boundary", () => {
});
const boundaryEvidence = parseEvidence(
await runProviderAnalysis(path, "inspect_managed_native_boundaries", {}),
await runProviderAnalysis(
path,
"inspect_managed_native_boundaries",
{},
).then(projectCliAnalysisResult),
);
expect(boundaryEvidence).toMatchObject({
operation: "inspect_managed_native_boundaries",
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../../src/cli/projectCliAnalysisResult.js";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -34,7 +35,9 @@ describe("artifact archive inventory", () => {
zipResult.occurrences.map(({ artifact_kind }) => artifact_kind),
).toEqual(expect.arrayContaining(["javascript", "dynamic-library"]));
const cliEvidence = parseEvidence(
await runProviderAnalysis(zipPath, "inventory_artifact", {}),
await runProviderAnalysis(zipPath, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(cliEvidence).toMatchObject({
operation: "inventory_artifact",
@@ -42,7 +45,9 @@ describe("artifact archive inventory", () => {
subject: { format: "apk" },
});
const inspectionEvidence = parseEvidence(
await runProviderAnalysis(zipPath, "inspect_artifact", {}),
await runProviderAnalysis(zipPath, "inspect_artifact", {}).then(
projectCliAnalysisResult,
),
);
const inspection = artifactInspectionResultSchema.parse(
inspectionEvidence.normalized_result,
@@ -81,7 +86,7 @@ describe("artifact archive inventory", () => {
{},
undefined,
cancellation.signal,
),
).then(projectCliAnalysisResult),
).resolves.toMatchObject({ code: "cancelled" });
await expect(
runProviderAnalysis(
@@ -90,7 +95,7 @@ describe("artifact archive inventory", () => {
{},
undefined,
cancellation.signal,
),
).then(projectCliAnalysisResult),
).resolves.toMatchObject({ code: "cancelled" });
const source = join(root, "asar-source");
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../../src/cli/projectCliAnalysisResult.js";
import { lstat, readFile, writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -69,7 +70,9 @@ describe("artifact archive safety", () => {
);
expect(
parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
),
).toMatchObject({
operation: "inventory_artifact",
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../../src/cli/projectCliAnalysisResult.js";
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -42,7 +43,9 @@ it("runs static .app decoding without registering native analysis engines", asyn
const evidence = parseEvidence(
await createDirectAnalysis({
REA_ANALYSIS_PROVIDER: "auto",
}).runProviderAnalysis(app, "decode_interface_builder", {}),
})
.runProviderAnalysis(app, "decode_interface_builder", {})
.then(projectCliAnalysisResult),
);
expect(evidence.operation).toBe("decode_interface_builder");
expect(evidence.provider.id).toBe("rea-artifact-graph");
@@ -64,7 +67,9 @@ it("inspects an artifact through the direct CLI workflow without an REA grant",
await createPackageWithOptions(source, archive, {});
const evidence = parseEvidence(
await runProviderAnalysis(archive, "inspect_artifact", {}),
await runProviderAnalysis(archive, "inspect_artifact", {}).then(
projectCliAnalysisResult,
),
);
expect(evidence.operation).toBe("inspect_artifact");
expect(evidence.provider.id).toBe("rea-artifact-graph");
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../../../src/cli/projectCliAnalysisResult.js";
import { mkdir, symlink, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
@@ -32,7 +33,9 @@ const writeTree = async (
const probeOperations = async (path: string): Promise<string[]> => {
const evidence = parseEvidence(
await runProviderAnalysis(path, "inspect_artifact", {}),
await runProviderAnalysis(path, "inspect_artifact", {}).then(
projectCliAnalysisResult,
),
);
const inspection = artifactInspectionResultSchema.parse(
evidence.normalized_result,
+23 -18
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import {
mkdir,
open,
@@ -163,7 +164,7 @@ it.each([false, true])(
{},
undefined,
controller.signal,
),
).then(projectCliAnalysisResult),
);
expect(result.code).toBe(closeFails ? "cleanup_incomplete" : "cancelled");
expect(result.category).toBe("cancelled");
@@ -214,7 +215,7 @@ describe("one-shot finalization cancellation", () => {
path,
tool,
parameters,
),
).then(projectCliAnalysisResult),
);
const controller = new AbortController();
const reason = { request: "cancel during native close" };
@@ -227,7 +228,7 @@ describe("one-shot finalization cancellation", () => {
tool,
parameters,
{ signal: controller.signal },
);
).then(projectCliAnalysisResult);
expect(result).toEqual({
error: "Analysis failed",
...projectAnalysisError(
@@ -257,7 +258,7 @@ describe("one-shot finalization cancellation", () => {
"read_bytes",
{},
{ signal: controller.signal },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: closeFails ? "cleanup_incomplete" : "provider_timeout",
message: projectAnalysisError(primaryError).message,
@@ -284,7 +285,7 @@ describe("one-shot finalization cancellation", () => {
"read_bytes",
{},
{ signal: controller.signal },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "cleanup_incomplete",
category: "cancelled",
@@ -341,7 +342,7 @@ describe("snapshot publication finalization", () => {
"read_bytes",
{},
{ signal: controller.signal, snapshotPath },
);
).then(projectCliAnalysisResult);
const loaded = await readAnalysisSnapshot(snapshotPath);
if (!loaded.ok) throw loaded.error;
const evidence = loaded.value.evidence_bundle.records.find(
@@ -403,7 +404,7 @@ describe("snapshot publication finalization", () => {
"read_bytes",
{},
{ signal: controller.signal, snapshotPath: failure.path },
),
).then(projectCliAnalysisResult),
);
expect(result.code).toBe(projectAnalysisError(failure).code);
expect(result.details).toMatchObject({
@@ -428,7 +429,7 @@ describe("one-shot analysis cleanup outcomes", () => {
path,
"read_bytes",
{},
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
error: "Analysis failed",
code: "cleanup_incomplete",
@@ -458,7 +459,7 @@ describe("one-shot analysis cleanup outcomes", () => {
path,
"read_bytes",
{},
);
).then(projectCliAnalysisResult);
const callerError = analysisCliErrorEnvelopeSchema.parse(result);
expect(callerError).toMatchObject({
code: "cleanup_incomplete",
@@ -490,7 +491,7 @@ describe("one-shot analysis cleanup outcomes", () => {
path,
"read_bytes",
{},
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "cleanup_incomplete",
details: {
@@ -520,7 +521,9 @@ describe("one-shot analysis cleanup outcomes", () => {
...(closeFails ? { cleanupError } : {}),
});
const result = analysisCliErrorEnvelopeSchema.parse(
await runDirectAnalysis(dependencies, path, "read_bytes", {}),
await runDirectAnalysis(dependencies, path, "read_bytes", {}).then(
projectCliAnalysisResult,
),
);
expect(result).toMatchObject({
code: "cleanup_incomplete",
@@ -544,7 +547,9 @@ describe("one-shot analysis cleanup outcomes", () => {
const thrownExecution = new Error("unexpected execution rejection");
const { dependencies } = factories({ thrownExecution, cleanupError });
await expect(
runDirectAnalysis(dependencies, path, "read_bytes", {}),
runDirectAnalysis(dependencies, path, "read_bytes", {}).then(
projectCliAnalysisResult,
),
).rejects.toMatchObject({
cause: thrownExecution,
errors: [thrownExecution, cleanupError],
@@ -559,7 +564,7 @@ describe("one-shot analysis cleanup outcomes", () => {
path,
"read_bytes",
{},
);
).then(projectCliAnalysisResult);
expect(parseEvidence(result)).toMatchObject({
operation: "read_bytes",
normalized_result: { observation: "retained result" },
@@ -580,7 +585,7 @@ it.each(["complete", "summary"] as const)(
path,
"inspect_managed_members",
{},
),
).then(projectCliAnalysisResult),
);
const controller = new AbortController();
const reason = new Error("cancel during managed close");
@@ -600,7 +605,7 @@ it.each(["complete", "summary"] as const)(
detail,
evidenceBundlePath: join(dirname(path), "bundle.json"),
},
)
).then(projectCliAnalysisResult)
: await runProviderAnalysis(
dependencies,
path,
@@ -608,7 +613,7 @@ it.each(["complete", "summary"] as const)(
{},
undefined,
controller.signal,
);
).then(projectCliAnalysisResult);
expect(result).toEqual({
error: "Analysis failed",
...projectAnalysisError(
@@ -640,7 +645,7 @@ it("preserves a managed provider failure when cancellation arrives during close"
{},
undefined,
controller.signal,
);
).then(projectCliAnalysisResult);
expect(result).toEqual({
error: "Analysis failed",
...projectAnalysisError(failure),
@@ -727,7 +732,7 @@ describe("managed one-shot cleanup outcomes", () => {
path,
"inspect_managed_artifact",
{},
);
).then(projectCliAnalysisResult);
expect(isCliOperationFailure(result)).toBe(true);
expect(result).toMatchObject({
error: "Analysis failed",
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { EvidenceFileError } from "../../src/domain/evidenceErrors.js";
import { err, ok as resultOk } from "../../src/domain/result.js";
import { dirname, join } from "node:path";
@@ -68,7 +69,7 @@ it("requires a durable summary destination before acquiring a managed session",
undefined,
undefined,
{ detail: "summary" },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "invalid_request",
details: {
@@ -97,7 +98,9 @@ it.each([
{ detail: "summary", evidenceBundlePath: "/fixture/bundle.json" },
] as const;
// @ts-expect-error JavaScript callers can bypass managed-members-only delivery.
const result = await runProviderAnalysis(...invocation);
const result = await runProviderAnalysis(...invocation).then(
projectCliAnalysisResult,
);
expect(result).toMatchObject({
code: "invalid_request",
details: {
@@ -117,7 +120,9 @@ describe("one-shot analysis factory boundary", () => {
const interruptListeners = process.listenerCount("SIGINT");
const terminateListeners = process.listenerCount("SIGTERM");
await expect(
runDirectAnalysis(factories.dependencies, path, "read_bytes", {}),
runDirectAnalysis(factories.dependencies, path, "read_bytes", {}).then(
projectCliAnalysisResult,
),
).rejects.toBe(failure);
expect(factories.clientsClosed()).toBe(1);
expect(process.listenerCount("SIGINT")).toBe(interruptListeners);
@@ -136,7 +141,7 @@ describe("one-shot analysis factory boundary", () => {
"read_bytes",
{},
{ signal: controller.signal },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
error: "Analysis failed",
code: "cancelled",
@@ -194,7 +199,7 @@ describe("one-shot analysis factory boundary", () => {
"inspect_native_dispatch_metadata",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
).rejects.toBe(expected);
expect(factories.sessions).toEqual([]);
},
@@ -227,7 +232,7 @@ describe("one-shot analysis factory boundary", () => {
path,
tool,
arguments_,
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
operation: tool,
normalized_result: { operation: tool },
@@ -256,7 +261,7 @@ describe("one-shot analysis factory boundary", () => {
path,
tool,
{},
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({ operation: tool });
expect(factories.clientsClosed()).toBe(1);
},
@@ -279,7 +284,7 @@ it("preserves a managed execution profile in direct analysis Evidence", async ()
path,
"inspect_managed_artifact",
{},
);
).then(projectCliAnalysisResult);
expect(evidence).toMatchObject({ analysis_profile: profile });
});
@@ -298,7 +303,7 @@ it("direct analysis snapshot target mismatch retains actionable snapshot recover
"read_bytes",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
).not.toHaveProperty("code");
const mismatch = await runDirectAnalysis(
factories.dependencies,
@@ -306,7 +311,7 @@ it("direct analysis snapshot target mismatch retains actionable snapshot recover
"read_bytes",
{},
{ snapshotPath },
);
).then(projectCliAnalysisResult);
expect(mismatch).toMatchObject({
code: "evidence_integrity_mismatch",
remediation: {
@@ -321,7 +326,7 @@ it("direct analysis snapshot target mismatch retains actionable snapshot recover
"read_bytes",
{},
{ snapshotPath },
),
).then(projectCliAnalysisResult),
).not.toHaveProperty("code");
});
@@ -362,7 +367,7 @@ it("retries snapshot file cleanup before final close even when a published ident
"read_bytes",
{},
{ snapshotPath: join(dirname(path), "excluded.snapshot") },
);
).then(projectCliAnalysisResult);
expect(events[0]).toBe("file-cleanup");
expect(events).toContain("session-close");
expect(result).toMatchObject({ details: { published: true, bytes: 42 } });
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { expect, it } from "vitest";
import { parseConfig } from "../../src/config/parseConfig.js";
import { runDirectAnalysis } from "../../src/application/DirectAnalysis.js";
@@ -42,7 +43,7 @@ it("retains provider coverage caveats in a composed CLI result and closes the se
path,
"binary_overview",
{},
);
).then(projectCliAnalysisResult);
expect(output).toMatchObject({
limitations: [
"Derived by an REA composed workflow.",
+10 -9
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { parseConfig } from "../../src/config/parseConfig.js";
import { describe, expect, it } from "vitest";
import { runDirectAnalysis } from "../../src/application/DirectAnalysis.js";
@@ -114,7 +115,7 @@ describe("direct CLI cross-reference selectors", () => {
path,
"xrefs",
{ address: "table_only", extra: true },
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "invalid_request",
details: { operation: "xrefs", issues },
@@ -132,7 +133,7 @@ describe("direct CLI cross-reference selectors", () => {
const evidence = parseEvidence(
await runDirectAnalysis(provider.dependencies, path, "xrefs", {
address,
}),
}).then(projectCliAnalysisResult),
);
expect(evidence.parameters).toEqual({ address });
expect(evidence.normalized_result).toEqual(["0x4000"]);
@@ -160,7 +161,7 @@ describe("direct CLI cross-reference selectors", () => {
const evidence = parseEvidence(
await runDirectAnalysis(provider.dependencies, path, "xrefs", {
address,
}),
}).then(projectCliAnalysisResult),
);
expect(evidence.parameters).toEqual({ address });
expect(evidence.normalized_result).toEqual(["0x4000"]);
@@ -178,7 +179,7 @@ describe("direct CLI cross-reference name resolution", () => {
const evidence = parseEvidence(
await runDirectAnalysis(provider.dependencies, path, "xrefs", {
address,
}),
}).then(projectCliAnalysisResult),
);
expect(evidence.parameters).toEqual({ address });
expect(evidence.normalized_result).toEqual(["0x4000"]);
@@ -197,7 +198,7 @@ describe("direct CLI cross-reference name resolution", () => {
const empty = parseEvidence(
await runDirectAnalysis(provider.dependencies, path, "xrefs", {
address: "unreferenced",
}),
}).then(projectCliAnalysisResult),
);
expect(empty.normalized_result).toEqual([]);
const missing = await runDirectAnalysis(
@@ -207,7 +208,7 @@ describe("direct CLI cross-reference name resolution", () => {
{
address: "missing_symbol",
},
);
).then(projectCliAnalysisResult);
expect(missing).toMatchObject({
code: "invalid_request",
details: { operation: "xrefs", issues: [{ path: ["address"] }] },
@@ -226,7 +227,7 @@ describe("direct CLI cross-reference name resolution", () => {
{
address: "ambiguous",
},
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({ code: "invalid_request" });
expect(JSON.stringify(result)).toContain("0x1000");
expect(JSON.stringify(result)).toContain("0x2000");
@@ -245,7 +246,7 @@ describe("direct CLI cross-reference name resolution", () => {
const evidence = parseEvidence(
await runDirectAnalysis(provider.dependencies, path, "xrefs", {
address: "table_only",
}),
}).then(projectCliAnalysisResult),
);
expect(evidence.parameters).toEqual({ address: "table_only" });
expect(evidence.normalized_result).toEqual(["0x4000"]);
@@ -270,7 +271,7 @@ describe("direct CLI cross-reference name resolution", () => {
{
address: "table_only",
},
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({ code });
expect(provider.closed()).toBe(1);
},
+3 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../dist/cli/projectCliAnalysisResult.js";
import { parseConfig } from "../../dist/config/parseConfig.js";
import { access, writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -97,13 +98,13 @@ const result =
? await runDirectAnalysis(dependencies, target, "read_bytes", {
address: "0x0",
length: 1,
})
}).then(projectCliAnalysisResult)
: await runProviderAnalysis(
dependencies,
target,
mode === "managed" ? "inspect_managed_artifact" : "procedure_address",
mode === "managed" ? {} : { procedure: "fixture" },
);
).then(projectCliAnalysisResult);
clearInterval(keepAlive);
if (
process.listenerCount("SIGINT") !== interruptListeners ||
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { afterEach, expect, it, vi } from "vitest";
const statFailures = vi.hoisted(() => new Map<string, unknown>());
@@ -69,7 +70,7 @@ it.each([
path,
"inspect_keyed_archive",
{ path: "Resources/Archive.plist" },
);
).then(projectCliAnalysisResult);
expect(output).toMatchObject({
code: projectedCode,
@@ -108,7 +109,7 @@ it("rethrows an unclassified stat failure instead of treating it as a file", asy
await expect(
runProviderAnalysis(dependencies, path, "inspect_keyed_archive", {
path: "Resources/Archive.plist",
}),
}).then(projectCliAnalysisResult),
).rejects.toBe(cause);
expect(session.open).not.toHaveBeenCalled();
expect(session.openBundleResources).not.toHaveBeenCalled();
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { access, readFile } from "node:fs/promises";
import { join } from "node:path";
import { afterEach, expect, it, vi } from "vitest";
@@ -347,7 +348,7 @@ test.each([false, true])(
"binwalk",
"inspect_firmware_regions",
new AbortController().signal,
);
).then(projectCliAnalysisResult);
expect(delivered).toMatchObject({ code: "cleanup_incomplete" });
expect(JSON.stringify(delivered).split("binwalk 3.1.0")).toHaveLength(2);
}
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { getHeapStatistics } from "node:v8";
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
import { createServer } from "../../src/server/createServer.js";
@@ -324,12 +325,17 @@ it("projects a rejected managed parent by reference without cloning under low he
detail: "summary",
evidenceBundlePath: join(root, "parent.json"),
},
);
).then(projectCliAnalysisResult);
expect(result).toMatchObject({
code: "resource_constraint",
details: { operation: "inspect_managed_members" },
});
if (typeof result !== "object" || result === null || Array.isArray(result))
if (
typeof result !== "object" ||
result === null ||
Array.isArray(result) ||
!("details" in result)
)
throw new Error("Expected CLI error");
const details = result.details;
if (typeof details !== "object" || details === null || Array.isArray(details))
+9 -2
View File
@@ -1,3 +1,4 @@
import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js";
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
@@ -53,7 +54,11 @@ export const writeApplicationZip = async (
export const inventoryApplicationPackage = async (
path: string,
): Promise<Evidence> =>
parseEvidence(await runProviderAnalysis(path, "inventory_artifact", {}));
parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
/**
* Project the same inventory Evidence twice and return the schema-parsed
@@ -93,6 +98,8 @@ export const createNonApplicationZipInventory = async (
await writer.add("one.js", new TextReader("one"));
await writeFile(path, await writer.close());
return parseEvidence(
await runProviderAnalysis(path, "inventory_artifact", {}),
await runProviderAnalysis(path, "inventory_artifact", {}).then(
projectCliAnalysisResult,
),
);
};