From ccdeda7a2779525e526f995be683ff91388e34f5 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Mon, 26 Oct 2026 17:35:18 +0800 Subject: [PATCH] fix(inputs): centralize admission and preserve caller selections Move shared request parsing into its application owner, migrate all consumers, and remove six duplicated admission blocks while preserving cancellation and validation policy. Keep operator-selected JavaScript paths in verifier parent Evidence while retaining canonical comparison guards. Validated with 204 passing tests, 3 Linux-x64-only skips, typecheck, lint, and test discovery. --- .../javascript/application-workflows.mjs | 4 +- .../verify/javascript/runtime-observation.mjs | 8 +- src/application/AnalysisInput.test.ts | 68 ++++++++++ src/application/AnalysisInput.ts | 24 ++++ src/application/FunctionAnalysisEvidence.ts | 29 ++--- .../ProviderOperationExecution.test.ts | 62 --------- src/application/ProviderOperationExecution.ts | 24 +--- .../ReconstructionCoverageService.ts | 17 +-- .../ReconstructionObligationLedgerRequest.ts | 22 ++-- src/application/WebModuleTraceService.ts | 8 +- src/application/WebRuntimeService.ts | 6 +- src/application/WebSourceLocationService.ts | 8 +- .../analysisView/AnalysisViewService.ts | 25 ++-- .../android/AndroidPackageInspection.ts | 4 +- .../apkSignature/ApkSignatureService.ts | 8 +- .../artifacts/DesktopApplicationService.ts | 8 +- .../binaryDiagnostics/AppleIpsService.ts | 8 +- .../binaryDiagnostics/BinaryLayoutService.ts | 8 +- .../binaryDiagnostics/ByteEntropyService.ts | 8 +- .../binaryDiagnostics/DevicetreeService.ts | 8 +- .../binaryDiagnostics/ElfNotesService.ts | 8 +- .../binaryDiagnostics/KernelModuleService.ts | 8 +- .../binaryDiagnostics/NativeExportsService.ts | 8 +- .../PeCertificatesService.ts | 8 +- .../binaryDiagnostics/PeImportsService.ts | 8 +- .../binaryDiagnostics/PeLoadConfigService.ts | 8 +- .../binaryDiagnostics/PeResourcesService.ts | 8 +- .../binaryDiagnostics/RecordedCrashService.ts | 8 +- .../captures/PacketCaptureService.ts | 10 +- .../electron/ElectronFuseService.ts | 8 +- src/application/erlang/BeamService.ts | 8 +- src/application/evm/EvmInterfaceService.ts | 8 +- src/application/flutter/DartAotInspection.ts | 4 +- .../frida/FridaInstrumentationService.ts | 4 +- src/application/go/GoBinaryService.ts | 8 +- .../il2cpp/Il2cppMetadataService.ts | 4 +- .../JavaScriptApplicationWorkflowService.ts | 8 +- .../javascript/JavaScriptRecoveryService.ts | 46 ++++--- .../managed/ManagedMemberComparisonService.ts | 19 ++- .../managed/ManagedReconstructionService.ts | 4 +- src/application/python/PythonPycService.ts | 8 +- .../reverse/ReverseEngineeringService.ts | 4 +- src/application/rust/RustBinaryService.ts | 8 +- .../signals/SignalRecordingService.ts | 10 +- .../sqlite/SqliteDatabaseService.ts | 4 +- src/application/wasm/WasmArtifactService.ts | 8 +- .../javascriptVerifierSelectedPaths.test.ts | 120 ++++++++++++++++++ 47 files changed, 381 insertions(+), 333 deletions(-) create mode 100644 src/application/AnalysisInput.test.ts create mode 100644 src/application/AnalysisInput.ts create mode 100644 tests/boundary/process/javascriptVerifierSelectedPaths.test.ts diff --git a/scripts/verify/javascript/application-workflows.mjs b/scripts/verify/javascript/application-workflows.mjs index 5a88b7e11e..725e48a9ab 100644 --- a/scripts/verify/javascript/application-workflows.mjs +++ b/scripts/verify/javascript/application-workflows.mjs @@ -42,8 +42,8 @@ const [leftPath, rightPath] = await Promise.all([ if (leftPath === rightPath) throw new Error("--left and --right must resolve to distinct paths"); const [left, right] = await Promise.all([ - analyze(leftPath), - analyze(rightPath), + analyze(options.left), + analyze(options.right), ]); const comparison = compareApplicationVersionsEvidence({ left, right }); if (!comparison.ok) throw comparison.error; diff --git a/scripts/verify/javascript/runtime-observation.mjs b/scripts/verify/javascript/runtime-observation.mjs index b9af11516c..5dcaf2ca71 100644 --- a/scripts/verify/javascript/runtime-observation.mjs +++ b/scripts/verify/javascript/runtime-observation.mjs @@ -36,14 +36,8 @@ const requestedLayers = [ ...options.cache.map((path) => ({ role: "cache", path })), ...options.assets.map((path) => ({ role: "assets", path })), ]; -const canonicalLayers = await Promise.all( - requestedLayers.map(async (layer) => ({ - ...layer, - path: await realpath(layer.path), - })), -); const staticLayers = []; -for (const layer of canonicalLayers) { +for (const layer of requestedLayers) { const analyzed = await analyzeJavaScriptApplicationWithOwnedScope({ input_path: layer.path, format: "auto", diff --git a/src/application/AnalysisInput.test.ts b/src/application/AnalysisInput.test.ts new file mode 100644 index 0000000000..207d39f1dd --- /dev/null +++ b/src/application/AnalysisInput.test.ts @@ -0,0 +1,68 @@ +import { expect, it, vi } from "vitest"; +import { z } from "zod"; +import { + AnalysisCancelledError, + AnalysisInputError, +} from "../domain/analysisErrorCore.js"; +import { parseAnalysisInput } from "./AnalysisInput.js"; + +it("admits direct inputs with schema defaults while preserving caller values", () => { + const raw = { + path: "/tmp/selected artifact", + nested: { input: "caller value" }, + }; + const result = parseAnalysisInput( + "inspect", + raw, + z.strictObject({ + path: z.string(), + nested: z.strictObject({ input: z.string() }), + detail: z.literal("full").default("full"), + }), + ); + if (!result.ok) throw result.error; + expect(result.value).toEqual({ ...raw, detail: "full" }); + expect(raw).not.toHaveProperty("detail"); +}); + +it("preserves direct-input issue paths and the original validation cause", () => { + const result = parseAnalysisInput( + "inspect", + { input: "", extra: "caller-only-marker" }, + z.strictObject({ input: z.string().min(1) }), + ); + if (result.ok || !(result.error instanceof AnalysisInputError)) + throw new Error("Expected input error"); + expect(result.error.operation).toBe("inspect"); + expect(result.error.issues).toEqual([ + { path: ["input"], reason: "out_of_range", minimum: 1 }, + { path: ["extra"], reason: "unknown_argument" }, + ]); + expect(result.error.cause).toBeInstanceOf(z.ZodError); + expect(JSON.stringify(result.error.issues)).not.toContain( + "caller-only-marker", + ); +}); + +it("honors direct-input admission cancellation before reading the caller input", () => { + const controller = new AbortController(); + const reason = { request: "cancel before validation" }; + controller.abort(reason); + const read = vi.fn(() => "caller input"); + const raw = { + get path() { + return read(); + }, + }; + const result = parseAnalysisInput( + "inspect", + raw, + z.strictObject({ path: z.string() }), + controller.signal, + ); + if (result.ok || !(result.error instanceof AnalysisCancelledError)) + throw new Error("Expected cancellation"); + expect(result.error.operation).toBe("inspect"); + expect(result.error.cause).toBe(reason); + expect(read).not.toHaveBeenCalled(); +}); diff --git a/src/application/AnalysisInput.ts b/src/application/AnalysisInput.ts new file mode 100644 index 0000000000..556dc99564 --- /dev/null +++ b/src/application/AnalysisInput.ts @@ -0,0 +1,24 @@ +import type { z } from "zod"; +import type { AnalysisError } from "../domain/analysisErrorBase.js"; +import { AnalysisCancelledError } from "../domain/analysisErrorCore.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; +import { err, ok, type Result } from "../domain/result.js"; + +/** Validate an application request, honoring cancellation before admission when supplied. */ +export const parseAnalysisInput = ( + operation: string, + rawInput: unknown, + schema: z.ZodType, + signal?: AbortSignal, +): Result => { + if (signal?.aborted) + return err(new AnalysisCancelledError(operation, { cause: signal.reason })); + const input = schema.safeParse(rawInput); + if (!input.success) + return err( + analysisInputErrorFromIssues(operation, input.error.issues, rawInput, { + cause: input.error, + }), + ); + return ok(input.data); +}; diff --git a/src/application/FunctionAnalysisEvidence.ts b/src/application/FunctionAnalysisEvidence.ts index 2502adde81..b97fa398ca 100644 --- a/src/application/FunctionAnalysisEvidence.ts +++ b/src/application/FunctionAnalysisEvidence.ts @@ -1,3 +1,4 @@ +import { parseAnalysisInput } from "./AnalysisInput.js"; import type { AnalysisOperationPort, ExecutionOptions, @@ -7,9 +8,6 @@ import type { BinaryTarget } from "../domain/binaryTargetTypes.js"; import { createEvidence } from "../domain/evidence.js"; import { parseFunctionDossier } from "../domain/hopperValues.js"; import type { JsonValue } from "../domain/jsonValue.js"; -import { err } from "../domain/result.js"; -import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; -import { AnalysisCancelledError } from "../domain/analysisErrorCore.js"; import { completeProviderEvidence } from "./ProviderOperationExecution.js"; /** @@ -23,23 +21,14 @@ export const executeFunctionAnalysisEvidence = async ( target: BinaryTarget | undefined, options?: ExecutionOptions, ) => { - if (options?.signal?.aborted) - return err( - new AnalysisCancelledError("analyze_function", { - cause: options.signal.reason, - }), - ); - const input = nativeProcedureInputSchema.safeParse(parameters); - if (!input.success) - return err( - analysisInputErrorFromIssues( - "analyze_function", - input.error.issues, - parameters, - { cause: input.error }, - ), - ); - const arguments_ = input.data; + const input = parseAnalysisInput( + "analyze_function", + parameters, + nativeProcedureInputSchema, + options?.signal, + ); + if (!input.ok) return input; + const arguments_ = input.value; const execution = await analysis.execute( "analyze_function", arguments_, diff --git a/src/application/ProviderOperationExecution.test.ts b/src/application/ProviderOperationExecution.test.ts index 8d32d48fc7..f3a36e1b5a 100644 --- a/src/application/ProviderOperationExecution.test.ts +++ b/src/application/ProviderOperationExecution.test.ts @@ -12,7 +12,6 @@ import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { executeProviderOperation, keepIncompleteObservation, - parseProviderOperationInput, } from "./ProviderOperationExecution.js"; it("projects request-schema issues without exposing the request envelope", async () => { @@ -179,64 +178,3 @@ it("freezes a retained partial so a later mutation cannot break projection", () details: { partial_observation: { steps: [] } }, }); }); - -it("admits direct inputs with schema defaults while preserving caller values", () => { - const raw = { - path: "/tmp/selected artifact", - nested: { input: "caller value" }, - }; - const result = parseProviderOperationInput( - "inspect", - raw, - z.strictObject({ - path: z.string(), - nested: z.strictObject({ input: z.string() }), - detail: z.literal("full").default("full"), - }), - ); - if (!result.ok) throw result.error; - expect(result.value).toEqual({ ...raw, detail: "full" }); - expect(raw).not.toHaveProperty("detail"); -}); - -it("preserves direct-input issue paths and the original validation cause", () => { - const result = parseProviderOperationInput( - "inspect", - { input: "", extra: "caller-only-marker" }, - z.strictObject({ input: z.string().min(1) }), - ); - if (result.ok || !(result.error instanceof AnalysisInputError)) - throw new Error("Expected input error"); - expect(result.error.operation).toBe("inspect"); - expect(result.error.issues).toEqual([ - { path: ["input"], reason: "out_of_range", minimum: 1 }, - { path: ["extra"], reason: "unknown_argument" }, - ]); - expect(result.error.cause).toBeInstanceOf(z.ZodError); - expect(JSON.stringify(result.error.issues)).not.toContain( - "caller-only-marker", - ); -}); - -it("honors direct-input admission cancellation before reading the caller input", () => { - const controller = new AbortController(); - const reason = { request: "cancel before validation" }; - controller.abort(reason); - const read = vi.fn(() => "caller input"); - const raw = { - get path() { - return read(); - }, - }; - const result = parseProviderOperationInput( - "inspect", - raw, - z.strictObject({ path: z.string() }), - controller.signal, - ); - if (result.ok || !(result.error instanceof AnalysisCancelledError)) - throw new Error("Expected cancellation"); - expect(result.error.operation).toBe("inspect"); - expect(result.error.cause).toBe(reason); - expect(read).not.toHaveBeenCalled(); -}); diff --git a/src/application/ProviderOperationExecution.ts b/src/application/ProviderOperationExecution.ts index be9478302b..ce441b57a9 100644 --- a/src/application/ProviderOperationExecution.ts +++ b/src/application/ProviderOperationExecution.ts @@ -14,10 +14,7 @@ import { isImmutableJsonSnapshot, } from "../domain/immutableJson.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; -import { - analysisInputErrorFromIssues, - analysisOperationInputErrorFromIssues, -} from "../domain/inputIssueProjection.js"; +import { analysisOperationInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../domain/result.js"; import type { AnalysisExecution, @@ -81,25 +78,6 @@ export const executeProviderOperation = async < return completeProviderEvidence(evidence, options?.signal); }; -/** Admit a direct provider input before acquiring any provider resources. */ -export const parseProviderOperationInput = ( - operation: string, - rawInput: unknown, - schema: z.ZodType, - signal?: AbortSignal, -): Result => { - if (signal?.aborted) - return err(new AnalysisCancelledError(operation, { cause: signal.reason })); - const input = schema.safeParse(rawInput); - if (!input.success) - return err( - analysisInputErrorFromIssues(operation, input.error.issues, rawInput, { - cause: input.error, - }), - ); - return ok(input.data); -}; - /** Classify completion after Evidence assembly, preserving facts on late abort. */ export const completeProviderEvidence = ( evidence: Evidence, diff --git a/src/application/ReconstructionCoverageService.ts b/src/application/ReconstructionCoverageService.ts index 906474b5f5..c19cef03a1 100644 --- a/src/application/ReconstructionCoverageService.ts +++ b/src/application/ReconstructionCoverageService.ts @@ -1,3 +1,4 @@ +import { parseAnalysisInput } from "./AnalysisInput.js"; import { z } from "zod"; import { reconstructionCoverageEvaluationInputSchema } from "../domain/reconstructionCoverageInput.js"; @@ -7,8 +8,7 @@ import { readReconstructionCoverageData, type ReconstructionClosureResult, } from "../domain/reconstructionCoverage.js"; -import { err, type Result } from "../domain/result.js"; -import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; +import type { Result } from "../domain/result.js"; const OPERATION = "evaluate_reconstruction_coverage"; const requestEnvelopeSchema = @@ -21,20 +21,15 @@ export const evaluateReconstructionCoverage = ( rawInput: unknown, nowEpochMs = Date.now(), ): Result => { - const parsed = requestEnvelopeSchema.safeParse(rawInput); - if (!parsed.success) - return err( - analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput, { - cause: parsed.error, - }), - ); - const coverage = readReconstructionCoverageData(parsed.data.coverage, [ + const parsed = parseAnalysisInput(OPERATION, rawInput, requestEnvelopeSchema); + if (!parsed.ok) return parsed; + const coverage = readReconstructionCoverageData(parsed.value.coverage, [ "coverage", ]); if (!coverage.ok) return coverage; return evaluateParsedReconstructionClosure( coverage.value, - parsed.data.boundary_id, + parsed.value.boundary_id, nowEpochMs, ); }; diff --git a/src/application/ReconstructionObligationLedgerRequest.ts b/src/application/ReconstructionObligationLedgerRequest.ts index 6a568f70a4..98e0f7e043 100644 --- a/src/application/ReconstructionObligationLedgerRequest.ts +++ b/src/application/ReconstructionObligationLedgerRequest.ts @@ -1,3 +1,4 @@ +import { parseAnalysisInput } from "./AnalysisInput.js"; import { z } from "zod"; import { AnalysisInputError } from "../domain/analysisErrorCore.js"; @@ -13,7 +14,6 @@ import { type ReconstructionObligationManifest, } from "../domain/reconstructionObligationLedgerSchemas.js"; import { err, ok, type Result } from "../domain/result.js"; -import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; const OPERATION = "build_reconstruction_obligation_ledger"; const requestSchema = reconstructionObligationLedgerInputSchema.extend({ @@ -36,19 +36,17 @@ export interface ResolvedReconstructionObligationRequest { export const resolveReconstructionObligationLedgerRequest = ( input: unknown, ): Result => { - const frame = requestSchema.safeParse(input); - if (!frame.success) - return err( - analysisInputErrorFromIssues(OPERATION, frame.error.issues, input, { - cause: frame.error, - }), - ); - const bundle = readEvidenceBundle(frame.data.evidence_bundle); + const frame = parseAnalysisInput(OPERATION, input, requestSchema); + if (!frame.ok) return frame; + const bundle = readEvidenceBundle(frame.value.evidence_bundle); if (!bundle.ok) return bundle; const records = new Map( bundle.value.records.map((record) => [record.evidence_id, record]), ); - for (const [index, obligation] of frame.data.reviewed_obligations.entries()) { + for (const [ + index, + obligation, + ] of frame.value.reviewed_obligations.entries()) { for (const [ referenceIndex, id, @@ -73,7 +71,7 @@ export const resolveReconstructionObligationLedgerRequest = ( } return ok({ sources: { bundle: bundle.value, records }, - reviewedObligations: frame.data.reviewed_obligations, - manifest: frame.data.manifest, + reviewedObligations: frame.value.reviewed_obligations, + manifest: frame.value.manifest, }); }; diff --git a/src/application/WebModuleTraceService.ts b/src/application/WebModuleTraceService.ts index 2014b59c07..84d27286ae 100644 --- a/src/application/WebModuleTraceService.ts +++ b/src/application/WebModuleTraceService.ts @@ -36,10 +36,8 @@ import type { WebModuleResolutionPort, } from "./WebModulePorts.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "./ProviderOperationExecution.js"; +import { completeProviderEvidence } from "./ProviderOperationExecution.js"; +import { parseAnalysisInput } from "./AnalysisInput.js"; const OPERATION = "trace_web_module_imports"; const artifactsSchema = selectedWebScriptArtifactsSchema.extend({ @@ -83,7 +81,7 @@ export class WebModuleTraceService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( OPERATION, rawInput, webModuleTraceInputSchema, diff --git a/src/application/WebRuntimeService.ts b/src/application/WebRuntimeService.ts index 53af27d648..b30a6097a0 100644 --- a/src/application/WebRuntimeService.ts +++ b/src/application/WebRuntimeService.ts @@ -18,8 +18,8 @@ import { import { completeProviderEvidence, keepIncompleteObservation, - parseProviderOperationInput, } from "./ProviderOperationExecution.js"; +import { parseAnalysisInput } from "./AnalysisInput.js"; /** Shared public workflows keep instrumentation and listener inspection independently selectable. */ export class WebRuntimeService { @@ -31,7 +31,7 @@ export class WebRuntimeService { options?: ExecutionOptions, ): Promise> { const operation = "observe_web_execution"; - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( operation, rawInput, observeWebExecutionInputSchema, @@ -64,7 +64,7 @@ export class WebRuntimeService { options?: ExecutionOptions, ): Promise> { const operation = "inspect_web_event_listeners"; - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( operation, rawInput, inspectWebEventListenersInputSchema, diff --git a/src/application/WebSourceLocationService.ts b/src/application/WebSourceLocationService.ts index ea64a191fd..58d20c556d 100644 --- a/src/application/WebSourceLocationService.ts +++ b/src/application/WebSourceLocationService.ts @@ -32,10 +32,8 @@ import type { WebSourceLocationArtifactPort, WebSourceMapPort, } from "./WebSourceLocationPorts.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "./ProviderOperationExecution.js"; +import { completeProviderEvidence } from "./ProviderOperationExecution.js"; +import { parseAnalysisInput } from "./AnalysisInput.js"; const OPERATION = "trace_web_source_location"; const artifactsSchema = selectedWebScriptArtifactsSchema.extend({ @@ -90,7 +88,7 @@ export class WebSourceLocationService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( OPERATION, rawInput, webSourceLocationInputSchema, diff --git a/src/application/analysisView/AnalysisViewService.ts b/src/application/analysisView/AnalysisViewService.ts index 531e1be5af..3c69a9d632 100644 --- a/src/application/analysisView/AnalysisViewService.ts +++ b/src/application/analysisView/AnalysisViewService.ts @@ -1,3 +1,4 @@ +import { parseAnalysisInput } from "../AnalysisInput.js"; import { isNativeObservationOperation } from "../../domain/native/nativeObservation.js"; import { setImmediate } from "node:timers/promises"; import { dispatchQueuedInterrupts } from "../../process/queuedInterrupts.js"; @@ -60,22 +61,18 @@ export const inspectAnalysisView = async ( lookup?: EvidenceLookup, options: AnalysisViewReadOptions = {}, ): Promise> => { - const parsed = inspectAnalysisViewInputSchema.safeParse(rawInput); - if (!parsed.success) - return err( - analysisInputErrorFromIssues( - INSPECT_ANALYSIS_VIEW_OPERATION, - parsed.error.issues, - rawInput, - { cause: parsed.error }, - ), - ); + const parsed = parseAnalysisInput( + INSPECT_ANALYSIS_VIEW_OPERATION, + rawInput, + inspectAnalysisViewInputSchema, + ); + if (!parsed.ok) return parsed; if (options.evidenceBundlePath !== undefined) - return inspectBundleView(parsed.data, options.evidenceBundlePath, options); - const source = parsed.data.source; + return inspectBundleView(parsed.value, options.evidenceBundlePath, options); + const source = parsed.value.source; if (source.kind === "evidence-file") - return inspectFileView({ ...parsed.data, source }, lookup, options); - return inspectAnalysisViewValidated({ ...parsed.data, source }, lookup); + return inspectFileView({ ...parsed.value, source }, lookup, options); + return inspectAnalysisViewValidated({ ...parsed.value, source }, lookup); }; /** diff --git a/src/application/android/AndroidPackageInspection.ts b/src/application/android/AndroidPackageInspection.ts index a879b79727..07c9312a96 100644 --- a/src/application/android/AndroidPackageInspection.ts +++ b/src/application/android/AndroidPackageInspection.ts @@ -9,7 +9,7 @@ import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { androidPackageInspectionInputSchema } from "../../domain/android/androidAnalysis.js"; import type { Evidence } from "../../domain/evidence.js"; import type { Result } from "../../domain/result.js"; -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; /** Preserve the complete provider request; select retained delivery explicitly. */ export const inspectAndroidPackage = async ( @@ -19,7 +19,7 @@ export const inspectAndroidPackage = async ( options?: ExecutionOptions, ): Promise> => { const operation = "inspect_android_package"; - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( operation, rawInput, androidPackageInspectionInputSchema, diff --git a/src/application/apkSignature/ApkSignatureService.ts b/src/application/apkSignature/ApkSignatureService.ts index bb98c31227..74ec0731a5 100644 --- a/src/application/apkSignature/ApkSignatureService.ts +++ b/src/application/apkSignature/ApkSignatureService.ts @@ -19,10 +19,8 @@ import { } from "../../domain/apkSignature/apkSignatureReport.js"; import { createEvidence, type Evidence } from "../../domain/evidence.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_apk_signature"; @@ -43,7 +41,7 @@ export class ApkSignatureService { recordEvidence: undefined, }, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectApkSignatureInputSchema, diff --git a/src/application/artifacts/DesktopApplicationService.ts b/src/application/artifacts/DesktopApplicationService.ts index 7fd4773043..865bdbaf5b 100644 --- a/src/application/artifacts/DesktopApplicationService.ts +++ b/src/application/artifacts/DesktopApplicationService.ts @@ -1,10 +1,8 @@ import type { ExecutionOptions } from "../AnalysisProvider.js"; import type { DesktopApplicationPort } from "./DesktopApplicationPort.js"; import { ARTIFACT_GRAPH_PROVIDER } from "../../domain/investigationProviders.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import { AnalysisOutputError } from "../../domain/analysisErrorCore.js"; import type { Evidence } from "../../domain/evidence.js"; import { createDesktopApplicationEvidence } from "../../domain/desktopApplicationEvidence.js"; @@ -27,7 +25,7 @@ export class DesktopApplicationService { options?: ExecutionOptions, ): Promise> { const operation = "inspect_desktop_application"; - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( operation, rawInput, inspectDesktopApplicationInputSchema, diff --git a/src/application/binaryDiagnostics/AppleIpsService.ts b/src/application/binaryDiagnostics/AppleIpsService.ts index 3581712eeb..29a15cac4a 100644 --- a/src/application/binaryDiagnostics/AppleIpsService.ts +++ b/src/application/binaryDiagnostics/AppleIpsService.ts @@ -14,10 +14,8 @@ import { type AppleIps, } from "../../domain/native/appleIps.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_apple_ips"; @@ -33,7 +31,7 @@ export class AppleIpsService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectAppleIpsInputSchema, diff --git a/src/application/binaryDiagnostics/BinaryLayoutService.ts b/src/application/binaryDiagnostics/BinaryLayoutService.ts index 2b3e04d509..87dd9723b6 100644 --- a/src/application/binaryDiagnostics/BinaryLayoutService.ts +++ b/src/application/binaryDiagnostics/BinaryLayoutService.ts @@ -8,10 +8,8 @@ import { binaryLayoutSchema, } from "../../domain/native/binaryLayout.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_binary_layout"; const EVIDENCE_ARCHITECTURES = { @@ -56,7 +54,7 @@ export class BinaryLayoutService { input: Parameters[0], ) => ReturnType, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectBinaryLayoutInputSchema, diff --git a/src/application/binaryDiagnostics/ByteEntropyService.ts b/src/application/binaryDiagnostics/ByteEntropyService.ts index c3cd75dc1a..2e5776d587 100644 --- a/src/application/binaryDiagnostics/ByteEntropyService.ts +++ b/src/application/binaryDiagnostics/ByteEntropyService.ts @@ -36,10 +36,8 @@ import { import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js"; import { sameRegularFileState } from "../../filesystem/RegularFile.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; /** Bound retained result rows before constructing the Evidence representation. */ export const MAX_BYTE_ENTROPY_BLOCKS = 32_768; @@ -99,7 +97,7 @@ export class ByteEntropyService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( OPERATION, rawInput, inspectByteEntropyInputSchema, diff --git a/src/application/binaryDiagnostics/DevicetreeService.ts b/src/application/binaryDiagnostics/DevicetreeService.ts index af2f76aff0..44e58443c4 100644 --- a/src/application/binaryDiagnostics/DevicetreeService.ts +++ b/src/application/binaryDiagnostics/DevicetreeService.ts @@ -13,10 +13,8 @@ import { type Devicetree, } from "../../domain/firmware/devicetree.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_devicetree"; @@ -32,7 +30,7 @@ export class DevicetreeService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectDevicetreeInputSchema, diff --git a/src/application/binaryDiagnostics/ElfNotesService.ts b/src/application/binaryDiagnostics/ElfNotesService.ts index b33ac1cf4d..2640172e23 100644 --- a/src/application/binaryDiagnostics/ElfNotesService.ts +++ b/src/application/binaryDiagnostics/ElfNotesService.ts @@ -15,10 +15,8 @@ import { import { BINARY_ARCHITECTURES } from "../../domain/binaryTargetTypes.js"; import { elfArchitecture } from "../../domain/elfFixedHeader.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_elf_notes"; @@ -34,7 +32,7 @@ export class ElfNotesService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectElfNotesInputSchema, diff --git a/src/application/binaryDiagnostics/KernelModuleService.ts b/src/application/binaryDiagnostics/KernelModuleService.ts index 0122071e0c..8f8fbe1dd7 100644 --- a/src/application/binaryDiagnostics/KernelModuleService.ts +++ b/src/application/binaryDiagnostics/KernelModuleService.ts @@ -21,10 +21,8 @@ import { import { BINARY_ARCHITECTURES } from "../../domain/binaryTargetTypes.js"; import { elfArchitecture } from "../../domain/elfFixedHeader.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_kernel_module"; @@ -42,7 +40,7 @@ export class KernelModuleService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectKernelModuleInputSchema, diff --git a/src/application/binaryDiagnostics/NativeExportsService.ts b/src/application/binaryDiagnostics/NativeExportsService.ts index 4ac52072aa..590ccbc3af 100644 --- a/src/application/binaryDiagnostics/NativeExportsService.ts +++ b/src/application/binaryDiagnostics/NativeExportsService.ts @@ -8,10 +8,8 @@ import { nativeExportsSchema, } from "../../domain/native/nativeExports.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_native_exports"; @@ -28,7 +26,7 @@ export class NativeExportsService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectNativeExportsInputSchema, diff --git a/src/application/binaryDiagnostics/PeCertificatesService.ts b/src/application/binaryDiagnostics/PeCertificatesService.ts index 9faf5f53bd..0868dd6fac 100644 --- a/src/application/binaryDiagnostics/PeCertificatesService.ts +++ b/src/application/binaryDiagnostics/PeCertificatesService.ts @@ -14,10 +14,8 @@ import { } from "../../domain/native/peCertificates.js"; import { peSubjectArchitecture } from "../../domain/peMachine.js"; import { err, ok, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_pe_certificates"; @@ -33,7 +31,7 @@ export class PeCertificatesService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectPeCertificatesInputSchema, diff --git a/src/application/binaryDiagnostics/PeImportsService.ts b/src/application/binaryDiagnostics/PeImportsService.ts index 0cb2f55eb1..299bfa4fad 100644 --- a/src/application/binaryDiagnostics/PeImportsService.ts +++ b/src/application/binaryDiagnostics/PeImportsService.ts @@ -14,10 +14,8 @@ import { } from "../../domain/native/peImports.js"; import { peSubjectArchitecture } from "../../domain/peMachine.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_pe_imports"; @@ -33,7 +31,7 @@ export class PeImportsService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectPeImportsInputSchema, diff --git a/src/application/binaryDiagnostics/PeLoadConfigService.ts b/src/application/binaryDiagnostics/PeLoadConfigService.ts index bb4e322b0d..4db23b6f69 100644 --- a/src/application/binaryDiagnostics/PeLoadConfigService.ts +++ b/src/application/binaryDiagnostics/PeLoadConfigService.ts @@ -14,10 +14,8 @@ import { } from "../../domain/native/peLoadConfig.js"; import { peSubjectArchitecture } from "../../domain/peMachine.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_pe_load_config"; @@ -33,7 +31,7 @@ export class PeLoadConfigService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectPeLoadConfigInputSchema, diff --git a/src/application/binaryDiagnostics/PeResourcesService.ts b/src/application/binaryDiagnostics/PeResourcesService.ts index c0b0f6a2f9..78b58216ca 100644 --- a/src/application/binaryDiagnostics/PeResourcesService.ts +++ b/src/application/binaryDiagnostics/PeResourcesService.ts @@ -11,10 +11,8 @@ import { } from "../../domain/native/peResources.js"; import { peSubjectArchitecture } from "../../domain/peMachine.js"; import { err, ok, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; /** Shared caller validation, output parsing and Evidence provenance for CLI/MCP. */ export class PeResourcesService { @@ -29,7 +27,7 @@ export class PeResourcesService { options?: ExecutionOptions, ): Promise> { const operation = "inspect_pe_resources"; - const input = parseProviderOperationInput( + const input = parseAnalysisInput( operation, rawInput, inspectPeResourcesInputSchema, diff --git a/src/application/binaryDiagnostics/RecordedCrashService.ts b/src/application/binaryDiagnostics/RecordedCrashService.ts index e1dcd2d166..7713a336b4 100644 --- a/src/application/binaryDiagnostics/RecordedCrashService.ts +++ b/src/application/binaryDiagnostics/RecordedCrashService.ts @@ -9,10 +9,8 @@ import { recordedCrashSchema, } from "../../domain/native/recordedCrash.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_recorded_crash"; @@ -51,7 +49,7 @@ export class RecordedCrashService { input: Parameters[0], ) => ReturnType, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectRecordedCrashInputSchema, diff --git a/src/application/captures/PacketCaptureService.ts b/src/application/captures/PacketCaptureService.ts index 3c224c0587..832fe95e92 100644 --- a/src/application/captures/PacketCaptureService.ts +++ b/src/application/captures/PacketCaptureService.ts @@ -17,10 +17,8 @@ import { type InspectPacketCaptureInput, } from "../../domain/captures/packetCapture.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; export interface PacketCapturePort { /** Release per-call resources before returning, or retain failed cleanup for close/retry. */ @@ -46,7 +44,7 @@ export class PacketCaptureService { options?: ExecutionOptions, ): Promise> { const operation = "inspect_packet_capture"; - const input = parseProviderOperationInput( + const input = parseAnalysisInput( operation, raw, inspectPacketCaptureInputSchema, @@ -85,7 +83,7 @@ export class PacketCaptureService { options?: ExecutionOptions, ): Promise> { const operation = "inspect_packet_fields"; - const input = parseProviderOperationInput( + const input = parseAnalysisInput( operation, raw, inspectPacketFieldsInputSchema, diff --git a/src/application/electron/ElectronFuseService.ts b/src/application/electron/ElectronFuseService.ts index 4290ebdf4b..dab580db09 100644 --- a/src/application/electron/ElectronFuseService.ts +++ b/src/application/electron/ElectronFuseService.ts @@ -9,10 +9,8 @@ import { import type { Evidence } from "../../domain/evidence.js"; import { createElectronFuseEvidence } from "../../domain/electron/electronFuseEvidence.js"; import { err, ok, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_electron_fuses"; @@ -30,7 +28,7 @@ export class ElectronFuseService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectElectronFusesInputSchema, diff --git a/src/application/erlang/BeamService.ts b/src/application/erlang/BeamService.ts index 46fa29a988..bcf5e904b0 100644 --- a/src/application/erlang/BeamService.ts +++ b/src/application/erlang/BeamService.ts @@ -9,10 +9,8 @@ import { type ErlangBeam, } from "../../domain/erlang/beam.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_erlang_beam"; @@ -28,7 +26,7 @@ export class BeamService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectErlangBeamInputSchema, diff --git a/src/application/evm/EvmInterfaceService.ts b/src/application/evm/EvmInterfaceService.ts index 4706106c47..99fb060598 100644 --- a/src/application/evm/EvmInterfaceService.ts +++ b/src/application/evm/EvmInterfaceService.ts @@ -10,10 +10,8 @@ import { evmInterfaceSchema, } from "../../domain/evm/evmInterface.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_evm_interface"; const diagnosticsSchema = z.object({ @@ -33,7 +31,7 @@ export class EvmInterfaceService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectEvmInterfaceInputSchema, diff --git a/src/application/flutter/DartAotInspection.ts b/src/application/flutter/DartAotInspection.ts index ab3deb6388..1d4b48f236 100644 --- a/src/application/flutter/DartAotInspection.ts +++ b/src/application/flutter/DartAotInspection.ts @@ -9,7 +9,7 @@ import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { dartAotInspectionInputSchema } from "../../domain/flutter/flutterBuildAnalysis.js"; import type { Evidence } from "../../domain/evidence.js"; import type { Result } from "../../domain/result.js"; -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; /** Keep the caller's ABI choice and complete analysis; select only its delivery. */ export const inspectDartAot = async ( @@ -19,7 +19,7 @@ export const inspectDartAot = async ( options?: ExecutionOptions, ): Promise> => { const operation = "inspect_dart_aot"; - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( operation, rawInput, dartAotInspectionInputSchema, diff --git a/src/application/frida/FridaInstrumentationService.ts b/src/application/frida/FridaInstrumentationService.ts index 08084743ec..b615fda1a7 100644 --- a/src/application/frida/FridaInstrumentationService.ts +++ b/src/application/frida/FridaInstrumentationService.ts @@ -16,7 +16,7 @@ import { } from "../../domain/analysisErrorCore.js"; import { err, ok, type Result } from "../../domain/result.js"; import { fridaSessionStatusInputSchema } from "../../domain/fridaSessionStatus.js"; -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import { requireAnalysisViewRetention, summarizeRetainedAnalysis, @@ -252,7 +252,7 @@ export class FridaInstrumentationService { evidenceById: undefined, }, ): Result { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( "frida_session_status", rawInput, fridaSessionStatusInputSchema, diff --git a/src/application/go/GoBinaryService.ts b/src/application/go/GoBinaryService.ts index 78f5cfe562..536c7c59a5 100644 --- a/src/application/go/GoBinaryService.ts +++ b/src/application/go/GoBinaryService.ts @@ -10,10 +10,8 @@ import { inspectGoBinaryInputSchema, } from "../../domain/go/goBinary.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import { requireAnalysisViewRetention, summarizeRetainedAnalysis, @@ -40,7 +38,7 @@ export class GoBinaryService { options?: ExecutionOptions, retention: AnalysisViewRetention = NO_RETENTION, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectGoBinaryInputSchema, diff --git a/src/application/il2cpp/Il2cppMetadataService.ts b/src/application/il2cpp/Il2cppMetadataService.ts index 2e8a04ab75..be18ef7a2f 100644 --- a/src/application/il2cpp/Il2cppMetadataService.ts +++ b/src/application/il2cpp/Il2cppMetadataService.ts @@ -1,4 +1,4 @@ -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import { analysisErrorWithCleanupFailure } from "../../domain/analysisErrorCleanup.js"; import { ProviderCleanupError } from "../../domain/providerCleanupError.js"; import type { @@ -99,7 +99,7 @@ export class Il2cppMetadataService { retention: AnalysisViewRetention, output?: Il2cppSeedOutput, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectIl2cppMetadataInputSchema, diff --git a/src/application/javascript/JavaScriptApplicationWorkflowService.ts b/src/application/javascript/JavaScriptApplicationWorkflowService.ts index 703dbbb4b9..11c6c6d78c 100644 --- a/src/application/javascript/JavaScriptApplicationWorkflowService.ts +++ b/src/application/javascript/JavaScriptApplicationWorkflowService.ts @@ -3,7 +3,7 @@ import { type EvidenceLookup, } from "../EvidenceInputResolver.js"; import { retainedEvidenceReferenceSchema } from "../../domain/evidenceReference.js"; -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import type { ResolvedSourceToBundleReference } from "./SourceToBundleReference.js"; import { z } from "zod"; @@ -122,7 +122,7 @@ export const traceApplicationFeatureEvidence = ( AnalysisError > => { const operation = "trace_application_feature"; - const request = parseProviderOperationInput( + const request = parseAnalysisInput( operation, rawInput, featureTraceFrameSchema, @@ -187,7 +187,7 @@ export const compareApplicationVersionsEvidence = ( AnalysisError > => { const operation = "compare_application_versions"; - const request = parseProviderOperationInput( + const request = parseAnalysisInput( operation, rawInput, versionComparisonFrameSchema, @@ -305,7 +305,7 @@ export const compareJavaScriptExportShapesEvidence = ( AnalysisError > => { const operation = "compare_javascript_export_shapes"; - const request = parseProviderOperationInput( + const request = parseAnalysisInput( operation, rawInput, exportShapeFrameSchema, diff --git a/src/application/javascript/JavaScriptRecoveryService.ts b/src/application/javascript/JavaScriptRecoveryService.ts index c5291f148d..a0bfed4778 100644 --- a/src/application/javascript/JavaScriptRecoveryService.ts +++ b/src/application/javascript/JavaScriptRecoveryService.ts @@ -1,7 +1,7 @@ +import { parseAnalysisInput } from "../AnalysisInput.js"; import { isAbsolute, resolve } from "node:path"; import { - AnalysisCancelledError, AnalysisInputError, AnalysisOutputError, } from "../../domain/analysisErrorCore.js"; @@ -11,7 +11,6 @@ import { javascriptRecoveryInputSchema, javascriptRecoveryResultSchema, } from "../../domain/javascript/javascriptRecovery.js"; -import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, type Result } from "../../domain/result.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; import { completeProviderEvidence } from "../ProviderOperationExecution.js"; @@ -32,21 +31,17 @@ export class JavaScriptRecoveryService { input: unknown, options?: ExecutionOptions, ): Promise> { - if (options?.signal?.aborted === true) - return err( - new AnalysisCancelledError(OPERATION, { cause: options.signal.reason }), - ); - const parsed = javascriptRecoveryInputSchema.safeParse(input); - if (!parsed.success) - return err( - analysisInputErrorFromIssues(OPERATION, parsed.error.issues, input, { - cause: parsed.error, - }), - ); - const invalidPaths = (["path", "output_directory"] as const).filter( - (field) => !isAbsolute(parsed.data[field]), + const parsed = parseAnalysisInput( + OPERATION, + input, + javascriptRecoveryInputSchema, + options?.signal, ); - const invalidAdditional = (parsed.data.additional_paths ?? []).filter( + if (!parsed.ok) return parsed; + const invalidPaths = (["path", "output_directory"] as const).filter( + (field) => !isAbsolute(parsed.value[field]), + ); + const invalidAdditional = (parsed.value.additional_paths ?? []).filter( (additional) => !isAbsolute(additional), ); if (invalidPaths.length > 0 || invalidAdditional.length > 0) @@ -65,11 +60,14 @@ export class JavaScriptRecoveryService { ]), ); const selectedInputs = new Set( - [parsed.data.path, ...(parsed.data.additional_paths ?? [])].map((path) => - resolve(path), + [parsed.value.path, ...(parsed.value.additional_paths ?? [])].map( + (path) => resolve(path), ), ); - if (selectedInputs.size !== 1 + (parsed.data.additional_paths ?? []).length) + if ( + selectedInputs.size !== + 1 + (parsed.value.additional_paths ?? []).length + ) return err( new AnalysisInputError(OPERATION, undefined, [ { @@ -79,7 +77,7 @@ export class JavaScriptRecoveryService { }, ]), ); - const execution = await this.provider.recover(parsed.data, options); + const execution = await this.provider.recover(parsed.value, options); if (!execution.ok) return execution; const result = javascriptRecoveryResultSchema.safeParse( execution.value.result, @@ -92,10 +90,10 @@ export class JavaScriptRecoveryService { ), ); if ( - result.data.source.path !== parsed.data.path || + result.data.source.path !== parsed.value.path || (result.data.additional_sources ?? []).length !== - (parsed.data.additional_paths ?? []).length || - (parsed.data.additional_paths ?? []).some( + (parsed.value.additional_paths ?? []).length || + (parsed.value.additional_paths ?? []).some( (path, index) => result.data.additional_sources?.[index]?.path !== path, ) || execution.value.subject.path !== result.data.source.path || @@ -114,7 +112,7 @@ export class JavaScriptRecoveryService { execution.value.provider, { operation: OPERATION, - parameters: parsed.data, + parameters: parsed.value, result: result.data, rawResult: execution.value.rawResult, locations: execution.value.locations, diff --git a/src/application/managed/ManagedMemberComparisonService.ts b/src/application/managed/ManagedMemberComparisonService.ts index daa5cc799c..dfe9a0da44 100644 --- a/src/application/managed/ManagedMemberComparisonService.ts +++ b/src/application/managed/ManagedMemberComparisonService.ts @@ -1,3 +1,4 @@ +import { parseAnalysisInput } from "../AnalysisInput.js"; import { z } from "zod"; import { @@ -28,7 +29,6 @@ import { MANAGED_STATIC_PROVIDER, MANAGED_WORKFLOW_PROVIDER, } from "../../domain/investigationProviders.js"; -import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; const managedMemberComparisonRequestSchema = z.strictObject({ left: z.unknown(), @@ -46,16 +46,15 @@ export const executeManagedMemberComparison = ( input: unknown, ): Result => { const operation = "compare_managed_members"; - const request = managedMemberComparisonRequestSchema.safeParse(input); - if (!request.success) - return err( - analysisInputErrorFromIssues(operation, request.error.issues, input, { - cause: request.error, - }), - ); - const left = readManagedMemberEvidence(request.data.left, "left"); + const request = parseAnalysisInput( + operation, + input, + managedMemberComparisonRequestSchema, + ); + if (!request.ok) return request; + const left = readManagedMemberEvidence(request.value.left, "left"); if (!left.ok) return left; - const right = readManagedMemberEvidence(request.data.right, "right"); + const right = readManagedMemberEvidence(request.value.right, "right"); if (!right.ok) return right; if (left.value.evidence.evidence_id === right.value.evidence.evidence_id) return err( diff --git a/src/application/managed/ManagedReconstructionService.ts b/src/application/managed/ManagedReconstructionService.ts index 70e6323838..27c2e6092a 100644 --- a/src/application/managed/ManagedReconstructionService.ts +++ b/src/application/managed/ManagedReconstructionService.ts @@ -10,7 +10,7 @@ import { } from "../../domain/managed/managedReconstruction.js"; import { ok, type Result } from "../../domain/result.js"; import { MANAGED_WORKFLOW_PROVIDER } from "../../domain/investigationProviders.js"; -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "import_managed_reconstruction" as const; @@ -18,7 +18,7 @@ const OPERATION = "import_managed_reconstruction" as const; export const resolveManagedReconstructionEvidenceInput = ( rawInput: unknown, ): Result => { - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( OPERATION, rawInput, managedReconstructionImportRequestSchema, diff --git a/src/application/python/PythonPycService.ts b/src/application/python/PythonPycService.ts index 4cc8e2e57f..3d77369b06 100644 --- a/src/application/python/PythonPycService.ts +++ b/src/application/python/PythonPycService.ts @@ -13,10 +13,8 @@ import { pythonPycSchema, } from "../../domain/python/pythonPyc.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_python_pyc"; @@ -32,7 +30,7 @@ export class PythonPycService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectPythonPycInputSchema, diff --git a/src/application/reverse/ReverseEngineeringService.ts b/src/application/reverse/ReverseEngineeringService.ts index 726abb4475..e4117b31a8 100644 --- a/src/application/reverse/ReverseEngineeringService.ts +++ b/src/application/reverse/ReverseEngineeringService.ts @@ -47,7 +47,7 @@ import { } from "../../process/OwnedCommand.js"; import type { ProviderProcessSupervisor } from "../../process/ProviderProcess.js"; import { absoluteCallerPath } from "../../filesystem/callerPath.js"; -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; export type ReverseEngineeringOperation = | "inspect_with_objdump" @@ -215,7 +215,7 @@ export class ReverseEngineeringService { recordEvidence: undefined, }, ): Promise> { - const parsed = parseProviderOperationInput( + const parsed = parseAnalysisInput( "inspect_with_objdump", rawInput, objdumpInputSchema, diff --git a/src/application/rust/RustBinaryService.ts b/src/application/rust/RustBinaryService.ts index 79a02099e7..3784f4ada7 100644 --- a/src/application/rust/RustBinaryService.ts +++ b/src/application/rust/RustBinaryService.ts @@ -11,10 +11,8 @@ import { } from "../../domain/rust/rustBinary.js"; import { inspectRustBinaryRequestSchema } from "../../domain/rust/rustBinaryQuery.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import { requireAnalysisViewRetention, projectRetainedAnalysis, @@ -40,7 +38,7 @@ export class RustBinaryService { evidenceById: undefined, }, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectRustBinaryRequestSchema, diff --git a/src/application/signals/SignalRecordingService.ts b/src/application/signals/SignalRecordingService.ts index 67570ea595..9173f6e859 100644 --- a/src/application/signals/SignalRecordingService.ts +++ b/src/application/signals/SignalRecordingService.ts @@ -17,10 +17,8 @@ import { type DecodeSignalRecordingInput, } from "../../domain/signals/signalRecording.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; export interface SignalRecordingPort { /** Release per-call resources before returning, or retain failed cleanup for close/retry. */ @@ -46,7 +44,7 @@ export class SignalRecordingService { options?: ExecutionOptions, ): Promise> { const operation = "decode_signal_recording"; - const input = parseProviderOperationInput( + const input = parseAnalysisInput( operation, raw, decodeSignalRecordingInputSchema, @@ -85,7 +83,7 @@ export class SignalRecordingService { options?: ExecutionOptions, ): Promise> { const operation = "inspect_signal_decoders"; - const input = parseProviderOperationInput( + const input = parseAnalysisInput( operation, raw, inspectSignalDecodersInputSchema, diff --git a/src/application/sqlite/SqliteDatabaseService.ts b/src/application/sqlite/SqliteDatabaseService.ts index ba206261d7..6f79fbdd63 100644 --- a/src/application/sqlite/SqliteDatabaseService.ts +++ b/src/application/sqlite/SqliteDatabaseService.ts @@ -1,4 +1,4 @@ -import { parseProviderOperationInput } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; import type { SqliteDatabasePort } from "./SqliteDatabasePort.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; @@ -79,7 +79,7 @@ export class SqliteDatabaseService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectSqliteDatabaseInputSchema, diff --git a/src/application/wasm/WasmArtifactService.ts b/src/application/wasm/WasmArtifactService.ts index a95ab38014..2545311805 100644 --- a/src/application/wasm/WasmArtifactService.ts +++ b/src/application/wasm/WasmArtifactService.ts @@ -12,10 +12,8 @@ import { DEFAULT_WASM_COMMAND_TIMEOUT_MS, } from "../../domain/wasm/wasmArtifact.js"; import { err, type Result } from "../../domain/result.js"; -import { - completeProviderEvidence, - parseProviderOperationInput, -} from "../ProviderOperationExecution.js"; +import { completeProviderEvidence } from "../ProviderOperationExecution.js"; +import { parseAnalysisInput } from "../AnalysisInput.js"; const OPERATION = "inspect_wasm_artifact"; @@ -36,7 +34,7 @@ export class WasmArtifactService { rawInput: unknown, options?: ExecutionOptions, ): Promise> { - const input = parseProviderOperationInput( + const input = parseAnalysisInput( OPERATION, rawInput, inspectWasmArtifactInputSchema, diff --git a/tests/boundary/process/javascriptVerifierSelectedPaths.test.ts b/tests/boundary/process/javascriptVerifierSelectedPaths.test.ts new file mode 100644 index 0000000000..19b4e51755 --- /dev/null +++ b/tests/boundary/process/javascriptVerifierSelectedPaths.test.ts @@ -0,0 +1,120 @@ +import { execFile } from "node:child_process"; +import { rm, symlink, writeFile } from "node:fs/promises"; +import { join, relative } from "node:path"; +import { promisify } from "node:util"; + +import { afterEach, expect, it } from "vitest"; + +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; +import { + SOURCE, + applicationFixture, + electronRuntimeEvidence, +} from "../../fixtures/javascriptRuntimeReconciliation.js"; + +const execute = promisify(execFile); +const temporary: string[] = []; + +afterEach(async () => { + await Promise.all( + temporary + .splice(0) + .map((path) => rm(path, { recursive: true, force: true })), + ); +}); + +const selectedFixture = async () => { + const physical = await applicationFixture(); + const root = await createTestTempDirectory("rea-verifier-selection-"); + temporary.push(physical, root); + const alias = join(root, "selected-app"); + await symlink( + physical, + alias, + process.platform === "win32" ? "junction" : "dir", + ); + return { physical, root, selected: relative(process.cwd(), alias) }; +}; + +// Run the real script helper against the same built owner used by the verifiers. +const analysisIdentity = async (path: string) => { + const helper = new URL( + "../../../scripts/lib/javascript-application-analysis.mjs", + import.meta.url, + ).href; + const { stdout } = await execute( + process.execPath, + [ + "--input-type=module", + "-e", + `import { analyzeJavaScriptApplicationWithOwnedScope } from ${JSON.stringify(helper)}; + const evidence = await analyzeJavaScriptApplicationWithOwnedScope({ input_path: process.argv[1] }); + console.log(JSON.stringify({ evidence_id: evidence.evidence_id, selected: evidence.parameters.input_path }));`, + path, + ], + { maxBuffer: 16 * 1_024 * 1_024 }, + ); + return JSON.parse(stdout) as { evidence_id: string; selected: string }; +}; + +it("retains selected layer paths in runtime verifier parent Evidence", async () => { + const { physical, root, selected } = await selectedFixture(); + const evidencePath = join(root, "runtime.json"); + await writeFile( + evidencePath, + JSON.stringify(electronRuntimeEvidence(physical, SOURCE)), + ); + const expected = await analysisIdentity(selected); + const canonical = await analysisIdentity(physical); + expect(expected.selected).toBe(selected); + expect(expected.evidence_id).not.toBe(canonical.evidence_id); + const { stdout } = await execute( + process.execPath, + [ + "scripts/verify/javascript/runtime-observation.mjs", + "--application", + selected, + "--runtime-evidence", + evidencePath, + ], + { maxBuffer: 16 * 1_024 * 1_024 }, + ); + expect(JSON.parse(stdout)).toMatchObject({ + verified: true, + layers: [{ evidence_id: expected.evidence_id }], + }); +}, 20_000); + +it("retains workflow selections while rejecting aliases of the same target", async () => { + const left = await selectedFixture(); + const right = await selectedFixture(); + const expectedLeft = await analysisIdentity(left.selected); + const expectedRight = await analysisIdentity(right.selected); + const { stdout } = await execute( + process.execPath, + [ + "scripts/verify/javascript/application-workflows.mjs", + "--left", + left.selected, + "--right", + right.selected, + ], + { maxBuffer: 16 * 1_024 * 1_024 }, + ); + expect(JSON.parse(stdout)).toMatchObject({ + verified: true, + left: { evidence_id: expectedLeft.evidence_id }, + right: { evidence_id: expectedRight.evidence_id }, + }); + await expect( + execute(process.execPath, [ + "scripts/verify/javascript/application-workflows.mjs", + "--left", + left.selected, + "--right", + left.physical, + ]), + ).rejects.toMatchObject({ + stderr: expect.stringContaining("must resolve to distinct paths"), + }); +}, 20_000);