From 329325049c8eaacb584729ed3a229e400d4f47c2 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:16:02 +0800 Subject: [PATCH] refactor(cli): retain typed analysis outcomes through delivery --- src/application/DirectAnalysis.ts | 68 ++++++++++--------- src/cli/adbCommands.ts | 3 +- src/cli/analysisEvidenceDelivery.ts | 16 ++--- src/cli/analysisViewCommands.ts | 8 ++- src/cli/androidCommands.ts | 3 +- src/cli/apktoolCommands.ts | 3 +- src/cli/artifactCommands.ts | 11 +-- src/cli/binaryDiagnosticsCommands.ts | 17 ++--- src/cli/browserAdvancedCommands.ts | 5 +- src/cli/browserCommands.ts | 7 +- src/cli/captureCommands.ts | 5 +- src/cli/coreBinaryCommands.ts | 15 ++-- src/cli/coreNativeCommands.ts | 21 +++--- src/cli/cutterCommands.ts | 5 +- src/cli/decompileDirectoryCommand.ts | 3 +- src/cli/electronCommands.ts | 13 ++-- src/cli/environmentCommands.ts | 13 +--- src/cli/evmCommands.ts | 3 +- src/cli/finishAnalysisCommand.ts | 8 +-- src/cli/firmwareCommands.ts | 3 +- src/cli/flutterCommands.ts | 3 +- src/cli/fridaCommands.ts | 7 +- src/cli/javascriptApplicationAnalysis.ts | 10 ++- src/cli/javascriptRecoveryCommands.ts | 3 +- src/cli/javascriptRuntimeCommands.ts | 5 +- src/cli/jebCommands.ts | 3 +- src/cli/managedCommands.ts | 18 +++-- src/cli/nativeCallCommands.ts | 3 +- src/cli/pcapCommands.ts | 3 +- src/cli/projectCliAnalysisResult.ts | 7 ++ src/cli/reverseEngineeringCommands.ts | 23 +++++-- src/cli/signalCommands.ts | 5 +- src/cli/sourceToBundleCommand.ts | 7 +- src/cli/sqliteCommands.ts | 3 +- src/cli/utilityCommands.ts | 11 +-- src/cli/wasmCommands.ts | 3 +- src/cli/webModuleCommands.ts | 3 +- src/cli/webRuntimeCommands.ts | 5 +- src/cli/webScriptCommands.ts | 6 +- src/cli/webSourceLocationCommands.ts | 3 +- src/cliBrowserScenarioCommands.ts | 7 +- .../mobileFrameworkConventions.test.ts | 5 +- .../boundary/cli/directAnalysisStatus.test.ts | 4 +- .../cli/finishAnalysisCommand.test.ts | 18 +++-- tests/boundary/cli/jsonOutput.test.ts | 27 +++++--- .../cli/managedMemberComparisonCli.test.ts | 5 +- .../cli/nativeLoadImageSummaryCli.test.ts | 2 +- .../cli/wasmLateCancellationCli.test.ts | 3 +- .../filesystem/android/application.test.ts | 45 +++--------- .../filesystem/appleApplication.test.ts | 33 ++++++--- .../filesystem/artifactSelectedName.test.ts | 5 +- .../filesystem/asarHeaderAdmission.test.ts | 17 +++-- .../filesystem/asarSelectedCompanion.test.ts | 5 +- .../composedWorkflowEvidenceLinks.test.ts | 13 ++-- .../directAnalysisPermissions.test.ts | 37 +++++----- .../directAnalysisSnapshotReplay.test.ts | 65 +++++++++--------- .../filesystem/evidenceBundleFiles.test.ts | 57 ++++++++++++---- .../filesystem/flutterBuildProvider.test.ts | 3 +- .../filesystem/harmony/application.test.ts | 7 +- .../webArtifactReadOwnership.test.ts | 3 +- .../composedWorkflowEvidenceLinksMcp.test.ts | 8 ++- .../mcp/mobileApplicationGraphMcp.test.ts | 35 ++++++++-- tests/boundary/mcp/officialArguments.test.ts | 5 +- .../mcp/workflowSnapshotReplayMcp.test.ts | 7 +- .../bundleResourceComposition.test.ts | 3 +- .../managed/managedDirectAnalysis.test.ts | 15 +++- .../artifactProvider.archiveInventory.test.ts | 13 ++-- .../artifactProvider.archiveSafety.test.ts | 5 +- .../artifactProvider.directAnalysis.test.ts | 9 ++- .../artifactProvider.directoryProbes.test.ts | 5 +- .../composition/directAnalysisCleanup.test.ts | 41 ++++++----- .../directAnalysisFactories.test.ts | 29 ++++---- .../directAnalysisLimitations.test.ts | 3 +- tests/composition/directAnalysisXrefs.test.ts | 19 +++--- tests/fixtures/directAnalysisInterrupt.mjs | 5 +- .../directAnalysisStatFailure.test.ts | 5 +- .../firmwarePublicationDeadline.test.ts | 3 +- .../managedBundleReadGuards.test.ts | 10 ++- tests/support/applicationSessionFixture.ts | 11 ++- 79 files changed, 581 insertions(+), 372 deletions(-) create mode 100644 src/cli/projectCliAnalysisResult.ts diff --git a/src/application/DirectAnalysis.ts b/src/application/DirectAnalysis.ts index 15b21576f4..c25d83f95d 100644 --- a/src/application/DirectAnalysis.ts +++ b/src/application/DirectAnalysis.ts @@ -132,7 +132,7 @@ export const runDirectAnalysis = async ( readonly architecture?: BinaryArchitecture; readonly entryPoint?: string; } = {}, -): Promise => +): Promise> => 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 => { +): Promise> => { 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 => { +): Promise> => { 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: { diff --git a/src/cli/adbCommands.ts b/src/cli/adbCommands.ts index 6b8bfff58e..8e13341bab 100644 --- a/src/cli/adbCommands.ts +++ b/src/cli/adbCommands.ts @@ -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), ), ); diff --git a/src/cli/analysisEvidenceDelivery.ts b/src/cli/analysisEvidenceDelivery.ts index 5130a3b884..718ed7d9df 100644 --- a/src/cli/analysisEvidenceDelivery.ts +++ b/src/cli/analysisEvidenceDelivery.ts @@ -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, input: CliEvidenceDelivery, ): Promise => { 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); diff --git a/src/cli/analysisViewCommands.ts b/src/cli/analysisViewCommands.ts index eacaf09705..676b12591a 100644 --- a/src/cli/analysisViewCommands.ts +++ b/src/cli/analysisViewCommands.ts @@ -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; }), ), }); diff --git a/src/cli/androidCommands.ts b/src/cli/androidCommands.ts index 6f0c3152a9..895754e46e 100644 --- a/src/cli/androidCommands.ts +++ b/src/cli/androidCommands.ts @@ -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"); diff --git a/src/cli/apktoolCommands.ts b/src/cli/apktoolCommands.ts index 26eb0c885b..f795b24640 100644 --- a/src/cli/apktoolCommands.ts +++ b/src/cli/apktoolCommands.ts @@ -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), ), ); diff --git a/src/cli/artifactCommands.ts b/src/cli/artifactCommands.ts index 565a52b9d7..2434133774 100644 --- a/src/cli/artifactCommands.ts +++ b/src/cli/artifactCommands.ts @@ -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), ), }); }; diff --git a/src/cli/binaryDiagnosticsCommands.ts b/src/cli/binaryDiagnosticsCommands.ts index cb25ec4fe2..5ec804f21b 100644 --- a/src/cli/binaryDiagnosticsCommands.ts +++ b/src/cli/binaryDiagnosticsCommands.ts @@ -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), ); }), }); diff --git a/src/cli/browserAdvancedCommands.ts b/src/cli/browserAdvancedCommands.ts index f54fa2e783..227822f270 100644 --- a/src/cli/browserAdvancedCommands.ts +++ b/src/cli/browserAdvancedCommands.ts @@ -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); }), ), }); diff --git a/src/cli/browserCommands.ts b/src/cli/browserCommands.ts index b06fbb49bf..23652bf53a 100644 --- a/src/cli/browserCommands.ts +++ b/src/cli/browserCommands.ts @@ -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); }), ), }); diff --git a/src/cli/captureCommands.ts b/src/cli/captureCommands.ts index 8ad700a415..bfd0324786 100644 --- a/src/cli/captureCommands.ts +++ b/src/cli/captureCommands.ts @@ -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); }), ), }); diff --git a/src/cli/coreBinaryCommands.ts b/src/cli/coreBinaryCommands.ts index d88a9b9828..e90a6e59ca 100644 --- a/src/cli/coreBinaryCommands.ts +++ b/src/cli/coreBinaryCommands.ts @@ -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), ), }); }; diff --git a/src/cli/coreNativeCommands.ts b/src/cli/coreNativeCommands.ts index fdec5c415e..fe752a9922 100644 --- a/src/cli/coreNativeCommands.ts +++ b/src/cli/coreNativeCommands.ts @@ -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), ), }); }; diff --git a/src/cli/cutterCommands.ts b/src/cli/cutterCommands.ts index b80461b145..40457eaa05 100644 --- a/src/cli/cutterCommands.ts +++ b/src/cli/cutterCommands.ts @@ -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); }), ), }); diff --git a/src/cli/decompileDirectoryCommand.ts b/src/cli/decompileDirectoryCommand.ts index 4e049512b2..378f7f56e1 100644 --- a/src/cli/decompileDirectoryCommand.ts +++ b/src/cli/decompileDirectoryCommand.ts @@ -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), ), }); }; diff --git a/src/cli/electronCommands.ts b/src/cli/electronCommands.ts index 783f87c1c4..c066ccea5e 100644 --- a/src/cli/electronCommands.ts +++ b/src/cli/electronCommands.ts @@ -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); }), ), }); diff --git a/src/cli/environmentCommands.ts b/src/cli/environmentCommands.ts index e2b0aefbaf..d893cd0b61 100644 --- a/src/cli/environmentCommands.ts +++ b/src/cli/environmentCommands.ts @@ -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> => { 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), + ...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)); diff --git a/src/cli/evmCommands.ts b/src/cli/evmCommands.ts index e57970bf38..8cf53caa06 100644 --- a/src/cli/evmCommands.ts +++ b/src/cli/evmCommands.ts @@ -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), ), ), }); diff --git a/src/cli/finishAnalysisCommand.ts b/src/cli/finishAnalysisCommand.ts index 85f9155212..046917cb47 100644 --- a/src/cli/finishAnalysisCommand.ts +++ b/src/cli/finishAnalysisCommand.ts @@ -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> => { +): Promise>> => { const projectFailure = (error: AnalysisError) => projectAnalysisError(options.redactFailure?.(error) ?? error); let result: Result; @@ -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)); }; diff --git a/src/cli/firmwareCommands.ts b/src/cli/firmwareCommands.ts index 5b0e274cfc..0c0a828ee6 100644 --- a/src/cli/firmwareCommands.ts +++ b/src/cli/firmwareCommands.ts @@ -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( diff --git a/src/cli/flutterCommands.ts b/src/cli/flutterCommands.ts index fe1c298df4..beb7c46447 100644 --- a/src/cli/flutterCommands.ts +++ b/src/cli/flutterCommands.ts @@ -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), ), ), }); diff --git a/src/cli/fridaCommands.ts b/src/cli/fridaCommands.ts index bb35af72f3..b74f185fc9 100644 --- a/src/cli/fridaCommands.ts +++ b/src/cli/fridaCommands.ts @@ -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); }), ), }); diff --git a/src/cli/javascriptApplicationAnalysis.ts b/src/cli/javascriptApplicationAnalysis.ts index df2b80e813..88f482fe80 100644 --- a/src/cli/javascriptApplicationAnalysis.ts +++ b/src/cli/javascriptApplicationAnalysis.ts @@ -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", diff --git a/src/cli/javascriptRecoveryCommands.ts b/src/cli/javascriptRecoveryCommands.ts index b7263519b1..da59e8e50b 100644 --- a/src/cli/javascriptRecoveryCommands.ts +++ b/src/cli/javascriptRecoveryCommands.ts @@ -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), ), ), }); diff --git a/src/cli/javascriptRuntimeCommands.ts b/src/cli/javascriptRuntimeCommands.ts index 19260c444d..0e542057d3 100644 --- a/src/cli/javascriptRuntimeCommands.ts +++ b/src/cli/javascriptRuntimeCommands.ts @@ -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); }, ), ), diff --git a/src/cli/jebCommands.ts b/src/cli/jebCommands.ts index 0aab2617a7..888e0493b2 100644 --- a/src/cli/jebCommands.ts +++ b/src/cli/jebCommands.ts @@ -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), ), ); diff --git a/src/cli/managedCommands.ts b/src/cli/managedCommands.ts index ca9f2b2917..79f34a7c3a 100644 --- a/src/cli/managedCommands.ts +++ b/src/cli/managedCommands.ts @@ -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 }; }), ), }); diff --git a/src/cli/nativeCallCommands.ts b/src/cli/nativeCallCommands.ts index e518c7afcc..f8d5ee96ba 100644 --- a/src/cli/nativeCallCommands.ts +++ b/src/cli/nativeCallCommands.ts @@ -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); }), }); }; diff --git a/src/cli/pcapCommands.ts b/src/cli/pcapCommands.ts index a2e1fc447f..954943bebe 100644 --- a/src/cli/pcapCommands.ts +++ b/src/cli/pcapCommands.ts @@ -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), ), ); diff --git a/src/cli/projectCliAnalysisResult.ts b/src/cli/projectCliAnalysisResult.ts new file mode 100644 index 0000000000..5e5f838ed3 --- /dev/null +++ b/src/cli/projectCliAnalysisResult.ts @@ -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 = ( + result: Result, +): Value | JsonValue => (result.ok ? result.value : result.error); diff --git a/src/cli/reverseEngineeringCommands.ts b/src/cli/reverseEngineeringCommands.ts index efdf21d409..a645bb3285 100644 --- a/src/cli/reverseEngineeringCommands.ts +++ b/src/cli/reverseEngineeringCommands.ts @@ -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) { diff --git a/src/cli/signalCommands.ts b/src/cli/signalCommands.ts index 1673d5734d..2ee5c42307 100644 --- a/src/cli/signalCommands.ts +++ b/src/cli/signalCommands.ts @@ -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); }), ), }); diff --git a/src/cli/sourceToBundleCommand.ts b/src/cli/sourceToBundleCommand.ts index 82933714ec..92f61e8647 100644 --- a/src/cli/sourceToBundleCommand.ts +++ b/src/cli/sourceToBundleCommand.ts @@ -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 }; }), ), }); diff --git a/src/cli/sqliteCommands.ts b/src/cli/sqliteCommands.ts index 2e69ba1aec..72cce0429d 100644 --- a/src/cli/sqliteCommands.ts +++ b/src/cli/sqliteCommands.ts @@ -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), ), ), }); diff --git a/src/cli/utilityCommands.ts b/src/cli/utilityCommands.ts index aa2b105e08..e84cc46a1e 100644 --- a/src/cli/utilityCommands.ts +++ b/src/cli/utilityCommands.ts @@ -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), ), }); }; diff --git a/src/cli/wasmCommands.ts b/src/cli/wasmCommands.ts index 9748f03e63..a2651fb73b 100644 --- a/src/cli/wasmCommands.ts +++ b/src/cli/wasmCommands.ts @@ -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), ), ), }); diff --git a/src/cli/webModuleCommands.ts b/src/cli/webModuleCommands.ts index fc1ab27036..cef3f31a3a 100644 --- a/src/cli/webModuleCommands.ts +++ b/src/cli/webModuleCommands.ts @@ -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), ), ), }); diff --git a/src/cli/webRuntimeCommands.ts b/src/cli/webRuntimeCommands.ts index 083ca57ad9..10592be4bb 100644 --- a/src/cli/webRuntimeCommands.ts +++ b/src/cli/webRuntimeCommands.ts @@ -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); }), ), }); diff --git a/src/cli/webScriptCommands.ts b/src/cli/webScriptCommands.ts index 17303ebb43..cade5933a7 100644 --- a/src/cli/webScriptCommands.ts +++ b/src/cli/webScriptCommands.ts @@ -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 }; }), ), }); diff --git a/src/cli/webSourceLocationCommands.ts b/src/cli/webSourceLocationCommands.ts index 56e8bd412b..136083fb53 100644 --- a/src/cli/webSourceLocationCommands.ts +++ b/src/cli/webSourceLocationCommands.ts @@ -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), ), ), }); diff --git a/src/cliBrowserScenarioCommands.ts b/src/cliBrowserScenarioCommands.ts index 51a412f3ed..2f3ec96975 100644 --- a/src/cliBrowserScenarioCommands.ts +++ b/src/cliBrowserScenarioCommands.ts @@ -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, diff --git a/tests/acceptance/applications/mobileFrameworkConventions.test.ts b/tests/acceptance/applications/mobileFrameworkConventions.test.ts index 0691067ccf..e99597010b 100644 --- a/tests/acceptance/applications/mobileFrameworkConventions.test.ts +++ b/tests/acceptance/applications/mobileFrameworkConventions.test.ts @@ -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({ diff --git a/tests/boundary/cli/directAnalysisStatus.test.ts b/tests/boundary/cli/directAnalysisStatus.test.ts index 720b8062c2..1b1ebcebf2 100644 --- a/tests/boundary/cli/directAnalysisStatus.test.ts +++ b/tests/boundary/cli/directAnalysisStatus.test.ts @@ -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 = ""; diff --git a/tests/boundary/cli/finishAnalysisCommand.test.ts b/tests/boundary/cli/finishAnalysisCommand.test.ts index c91584918a..3ef34f1dfe 100644 --- a/tests/boundary/cli/finishAnalysisCommand.test.ts +++ b/tests/boundary/cli/finishAnalysisCommand.test.ts @@ -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), diff --git a/tests/boundary/cli/jsonOutput.test.ts b/tests/boundary/cli/jsonOutput.test.ts index c5f18af6a0..68c145ce17 100644 --- a/tests/boundary/cli/jsonOutput.test.ts +++ b/tests/boundary/cli/jsonOutput.test.ts @@ -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) => { diff --git a/tests/boundary/cli/managedMemberComparisonCli.test.ts b/tests/boundary/cli/managedMemberComparisonCli.test.ts index ea90549406..2e2d25ce23 100644 --- a/tests/boundary/cli/managedMemberComparisonCli.test.ts +++ b/tests/boundary/cli/managedMemberComparisonCli.test.ts @@ -216,7 +216,10 @@ const registeredComparisonCommand = () => { return cli; }, } as unknown as Parameters[0]; - registerManagedCommands(cli, silentLogger, async () => null); + registerManagedCommands(cli, silentLogger, async () => ({ + ok: false, + error: null, + })); return commands.get(CLI_COMMANDS.compareManagedMembers)!; }; diff --git a/tests/boundary/cli/nativeLoadImageSummaryCli.test.ts b/tests/boundary/cli/nativeLoadImageSummaryCli.test.ts index a88c190fbb..76db77590c 100644 --- a/tests/boundary/cli/nativeLoadImageSummaryCli.test.ts +++ b/tests/boundary/cli/nativeLoadImageSummaryCli.test.ts @@ -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 = ""; diff --git a/tests/boundary/cli/wasmLateCancellationCli.test.ts b/tests/boundary/cli/wasmLateCancellationCli.test.ts index c782870de4..4947906295 100644 --- a/tests/boundary/cli/wasmLateCancellationCli.test.ts +++ b/tests/boundary/cli/wasmLateCancellationCli.test.ts @@ -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), diff --git a/tests/boundary/filesystem/android/application.test.ts b/tests/boundary/filesystem/android/application.test.ts index 0bb3fb4a4a..4f4bb04408 100644 --- a/tests/boundary/filesystem/android/application.test.ts +++ b/tests/boundary/filesystem/android/application.test.ts @@ -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] }), diff --git a/tests/boundary/filesystem/appleApplication.test.ts b/tests/boundary/filesystem/appleApplication.test.ts index 418fd1aa3f..a63f1b8b6f 100644 --- a/tests/boundary/filesystem/appleApplication.test.ts +++ b/tests/boundary/filesystem/appleApplication.test.ts @@ -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("")); 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], diff --git a/tests/boundary/filesystem/artifactSelectedName.test.ts b/tests/boundary/filesystem/artifactSelectedName.test.ts index 4b8b4dc6fb..bcf997b118 100644 --- a/tests/boundary/filesystem/artifactSelectedName.test.ts +++ b/tests/boundary/filesystem/artifactSelectedName.test.ts @@ -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( diff --git a/tests/boundary/filesystem/asarHeaderAdmission.test.ts b/tests/boundary/filesystem/asarHeaderAdmission.test.ts index 29d6839dcf..79b62db3e9 100644 --- a/tests/boundary/filesystem/asarHeaderAdmission.test.ts +++ b/tests/boundary/filesystem/asarHeaderAdmission.test.ts @@ -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 }, diff --git a/tests/boundary/filesystem/asarSelectedCompanion.test.ts b/tests/boundary/filesystem/asarSelectedCompanion.test.ts index e1bbb31f08..9fbcc0f3f2 100644 --- a/tests/boundary/filesystem/asarSelectedCompanion.test.ts +++ b/tests/boundary/filesystem/asarSelectedCompanion.test.ts @@ -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( diff --git a/tests/boundary/filesystem/composedWorkflowEvidenceLinks.test.ts b/tests/boundary/filesystem/composedWorkflowEvidenceLinks.test.ts index 5ebeec3d24..120b6c0b88 100644 --- a/tests/boundary/filesystem/composedWorkflowEvidenceLinks.test.ts +++ b/tests/boundary/filesystem/composedWorkflowEvidenceLinks.test.ts @@ -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"); diff --git a/tests/boundary/filesystem/directAnalysisPermissions.test.ts b/tests/boundary/filesystem/directAnalysisPermissions.test.ts index e603d64f51..34effe4d11 100644 --- a/tests/boundary/filesystem/directAnalysisPermissions.test.ts +++ b/tests/boundary/filesystem/directAnalysisPermissions.test.ts @@ -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); }); }); diff --git a/tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts b/tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts index 3f3504ee79..e50875e1b9 100644 --- a/tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts +++ b/tests/boundary/filesystem/directAnalysisSnapshotReplay.test.ts @@ -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 => { "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); diff --git a/tests/boundary/filesystem/evidenceBundleFiles.test.ts b/tests/boundary/filesystem/evidenceBundleFiles.test.ts index c5de3f9cd0..d3319f822d 100644 --- a/tests/boundary/filesystem/evidenceBundleFiles.test.ts +++ b/tests/boundary/filesystem/evidenceBundleFiles.test.ts @@ -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([]); diff --git a/tests/boundary/filesystem/flutterBuildProvider.test.ts b/tests/boundary/filesystem/flutterBuildProvider.test.ts index 0220a28c78..5cefb09d1d 100644 --- a/tests/boundary/filesystem/flutterBuildProvider.test.ts +++ b/tests/boundary/filesystem/flutterBuildProvider.test.ts @@ -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: { diff --git a/tests/boundary/filesystem/harmony/application.test.ts b/tests/boundary/filesystem/harmony/application.test.ts index ddfa250039..662dea627a 100644 --- a/tests/boundary/filesystem/harmony/application.test.ts +++ b/tests/boundary/filesystem/harmony/application.test.ts @@ -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", diff --git a/tests/boundary/filesystem/webArtifactReadOwnership.test.ts b/tests/boundary/filesystem/webArtifactReadOwnership.test.ts index 397194ac2c..b7cdbd20a4 100644 --- a/tests/boundary/filesystem/webArtifactReadOwnership.test.ts +++ b/tests/boundary/filesystem/webArtifactReadOwnership.test.ts @@ -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] }, diff --git a/tests/boundary/mcp/composedWorkflowEvidenceLinksMcp.test.ts b/tests/boundary/mcp/composedWorkflowEvidenceLinksMcp.test.ts index 88c1383b20..88fd62affb 100644 --- a/tests/boundary/mcp/composedWorkflowEvidenceLinksMcp.test.ts +++ b/tests/boundary/mcp/composedWorkflowEvidenceLinksMcp.test.ts @@ -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( diff --git a/tests/boundary/mcp/mobileApplicationGraphMcp.test.ts b/tests/boundary/mcp/mobileApplicationGraphMcp.test.ts index a8132588a9..8691ba8cce 100644 --- a/tests/boundary/mcp/mobileApplicationGraphMcp.test.ts +++ b/tests/boundary/mcp/mobileApplicationGraphMcp.test.ts @@ -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, diff --git a/tests/boundary/mcp/officialArguments.test.ts b/tests/boundary/mcp/officialArguments.test.ts index 8621f13ce6..7f3caae7cd 100644 --- a/tests/boundary/mcp/officialArguments.test.ts +++ b/tests/boundary/mcp/officialArguments.test.ts @@ -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); diff --git a/tests/boundary/mcp/workflowSnapshotReplayMcp.test.ts b/tests/boundary/mcp/workflowSnapshotReplayMcp.test.ts index 3fcd36b5a8..3739230002 100644 --- a/tests/boundary/mcp/workflowSnapshotReplayMcp.test.ts +++ b/tests/boundary/mcp/workflowSnapshotReplayMcp.test.ts @@ -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)), diff --git a/tests/boundary/providers/bundleResourceComposition.test.ts b/tests/boundary/providers/bundleResourceComposition.test.ts index 448c04e1df..521aae94ba 100644 --- a/tests/boundary/providers/bundleResourceComposition.test.ts +++ b/tests/boundary/providers/bundleResourceComposition.test.ts @@ -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, diff --git a/tests/boundary/providers/managed/managedDirectAnalysis.test.ts b/tests/boundary/providers/managed/managedDirectAnalysis.test.ts index df23e261d0..a209d4721f 100644 --- a/tests/boundary/providers/managed/managedDirectAnalysis.test.ts +++ b/tests/boundary/providers/managed/managedDirectAnalysis.test.ts @@ -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", diff --git a/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts b/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts index 506212f201..59a192b9ca 100644 --- a/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts +++ b/tests/boundary/providers/native/artifactProvider.archiveInventory.test.ts @@ -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"); diff --git a/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts b/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts index 95ea672de8..dd406e1bb5 100644 --- a/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts +++ b/tests/boundary/providers/native/artifactProvider.archiveSafety.test.ts @@ -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", diff --git a/tests/boundary/providers/native/artifactProvider.directAnalysis.test.ts b/tests/boundary/providers/native/artifactProvider.directAnalysis.test.ts index 9f032b327c..ac184b35d8 100644 --- a/tests/boundary/providers/native/artifactProvider.directAnalysis.test.ts +++ b/tests/boundary/providers/native/artifactProvider.directAnalysis.test.ts @@ -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"); diff --git a/tests/boundary/providers/native/artifactProvider.directoryProbes.test.ts b/tests/boundary/providers/native/artifactProvider.directoryProbes.test.ts index fc4fc45195..34566555eb 100644 --- a/tests/boundary/providers/native/artifactProvider.directoryProbes.test.ts +++ b/tests/boundary/providers/native/artifactProvider.directoryProbes.test.ts @@ -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 => { const evidence = parseEvidence( - await runProviderAnalysis(path, "inspect_artifact", {}), + await runProviderAnalysis(path, "inspect_artifact", {}).then( + projectCliAnalysisResult, + ), ); const inspection = artifactInspectionResultSchema.parse( evidence.normalized_result, diff --git a/tests/composition/directAnalysisCleanup.test.ts b/tests/composition/directAnalysisCleanup.test.ts index c29635052e..940354f1e3 100644 --- a/tests/composition/directAnalysisCleanup.test.ts +++ b/tests/composition/directAnalysisCleanup.test.ts @@ -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", diff --git a/tests/composition/directAnalysisFactories.test.ts b/tests/composition/directAnalysisFactories.test.ts index 006b418a0a..b4c58f1411 100644 --- a/tests/composition/directAnalysisFactories.test.ts +++ b/tests/composition/directAnalysisFactories.test.ts @@ -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 } }); diff --git a/tests/composition/directAnalysisLimitations.test.ts b/tests/composition/directAnalysisLimitations.test.ts index 1975559f57..5420fbf37f 100644 --- a/tests/composition/directAnalysisLimitations.test.ts +++ b/tests/composition/directAnalysisLimitations.test.ts @@ -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.", diff --git a/tests/composition/directAnalysisXrefs.test.ts b/tests/composition/directAnalysisXrefs.test.ts index 13802cddc7..f32601ffe8 100644 --- a/tests/composition/directAnalysisXrefs.test.ts +++ b/tests/composition/directAnalysisXrefs.test.ts @@ -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); }, diff --git a/tests/fixtures/directAnalysisInterrupt.mjs b/tests/fixtures/directAnalysisInterrupt.mjs index b3a4032d72..aa5c20c303 100644 --- a/tests/fixtures/directAnalysisInterrupt.mjs +++ b/tests/fixtures/directAnalysisInterrupt.mjs @@ -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 || diff --git a/tests/process-global/directAnalysisStatFailure.test.ts b/tests/process-global/directAnalysisStatFailure.test.ts index 510cc8eeac..11c4dac63c 100644 --- a/tests/process-global/directAnalysisStatFailure.test.ts +++ b/tests/process-global/directAnalysisStatFailure.test.ts @@ -1,3 +1,4 @@ +import { projectCliAnalysisResult } from "../../src/cli/projectCliAnalysisResult.js"; import { afterEach, expect, it, vi } from "vitest"; const statFailures = vi.hoisted(() => new Map()); @@ -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(); diff --git a/tests/process-global/firmwarePublicationDeadline.test.ts b/tests/process-global/firmwarePublicationDeadline.test.ts index 762b229cf9..e0dea71d36 100644 --- a/tests/process-global/firmwarePublicationDeadline.test.ts +++ b/tests/process-global/firmwarePublicationDeadline.test.ts @@ -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); } diff --git a/tests/process-global/managedBundleReadGuards.test.ts b/tests/process-global/managedBundleReadGuards.test.ts index cca6a7f4c3..18d3676e95 100644 --- a/tests/process-global/managedBundleReadGuards.test.ts +++ b/tests/process-global/managedBundleReadGuards.test.ts @@ -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)) diff --git a/tests/support/applicationSessionFixture.ts b/tests/support/applicationSessionFixture.ts index 4867d410f2..bdb5155342 100644 --- a/tests/support/applicationSessionFixture.ts +++ b/tests/support/applicationSessionFixture.ts @@ -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 => - 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, + ), ); };