diff --git a/src/apktool/ApktoolProvider.ts b/src/apktool/ApktoolProvider.ts index e4533f11fa..57d30d977b 100644 --- a/src/apktool/ApktoolProvider.ts +++ b/src/apktool/ApktoolProvider.ts @@ -27,9 +27,9 @@ import type { import { providerConfigurationRefusal } from "../domain/providerConfigurationRefusal.js"; import { createAnalysisExecution, - createAnalysisExecutionEvidence, type AnalysisExecution, } from "../application/AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; import { apktoolResultSchemas, DEFAULT_APKTOOL_DECODE_TIMEOUT_MS, diff --git a/src/application/AnalysisDocument.ts b/src/application/AnalysisDocument.ts index 4c1e490f12..6f9200eb29 100644 --- a/src/application/AnalysisDocument.ts +++ b/src/application/AnalysisDocument.ts @@ -1,8 +1,8 @@ -import { - createAnalysisExecutionEvidence, - type AnalysisExecution, - type AnalysisOperationPort, +import type { + AnalysisExecution, + AnalysisOperationPort, } from "./AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; import { AnalysisCancelledError, AnalysisCapabilityUnavailableError, diff --git a/src/application/AnalysisProvider.ts b/src/application/AnalysisProvider.ts index 0001a72d22..e0a311bc44 100644 --- a/src/application/AnalysisProvider.ts +++ b/src/application/AnalysisProvider.ts @@ -1,4 +1,4 @@ -import type { EvidenceCreationOptions } from "../domain/evidence.js"; +import type { AnalysisObservation } from "../domain/analysisObservation.js"; import type { ProviderInspectionScope } from "../providers/ProviderInspectionScope.js"; import type { BinaryTarget } from "../domain/binaryTargetTypes.js"; import type { OfficialToolName } from "../contracts/officialToolContracts.js"; @@ -10,13 +10,11 @@ import type { AnalysisError } from "../domain/analysisErrorBase.js"; import type { AnalysisProfileCommitment } from "../domain/analysisProfile.js"; import type { JsonValue } from "../domain/jsonValue.js"; import type { Result } from "../domain/result.js"; -import { - createEvidence, - type Evidence, - type EvidenceLocation, - type EvidenceSubjectTarget, +import type { + EvidenceLocation, + EvidenceSubjectTarget, } from "../domain/evidence.js"; -import { jsonObjectSchema, jsonValueSchema } from "../domain/jsonValue.js"; +import { jsonValueSchema } from "../domain/jsonValue.js"; import type { ProgressReporter } from "./ProgressReporter.js"; import type { ProviderRejectionCode } from "../contracts/providerSelection.js"; import type { ProcessLineageObservation } from "../process/ProcessOwnership.js"; @@ -51,16 +49,10 @@ export type AnalysisOperation = | "health"; /** Atomic successful provider observation with exact producing provenance. */ -export interface AnalysisExecution { - readonly result: JsonValue; - readonly rawResult: JsonValue | null; +export interface AnalysisExecution extends AnalysisObservation { readonly provider: ProviderIdentity; - readonly analysisProfile?: AnalysisProfileCommitment; /** Result-specific veto of snapshot replay; Evidence remains retainable. */ readonly cachePolicy?: "live"; - readonly limitations: readonly string[]; - /** Provider-owned completeness assessment for caller/callee observations. */ - readonly relationshipCoverage?: RelationshipCoverage; /** * Whether the provider capped an `xrefs` read: true when it reported a * truncation, false when it explicitly reported none. Absent when it reports @@ -68,12 +60,6 @@ export interface AnalysisExecution { * output. */ readonly referencesTruncated?: boolean; - readonly locations: readonly EvidenceLocation[]; - /** - * Missing means the selected operation's default subject should be bound by - * its caller. Explicit null means the observation has no file subject. - */ - readonly subject?: EvidenceSubjectTarget | null; } interface CreateAnalysisExecutionOptions { @@ -138,40 +124,6 @@ export function createAnalysisExecution( }; } -/** - * Compose one completed provider execution into its caller-visible Evidence, - * so success and a late cancellation that retains it carry identical facts. - */ -export const createAnalysisExecutionEvidence = ( - operation: string, - input: unknown, - execution: AnalysisExecution, - confidence: "observed" | "derived", - options?: EvidenceCreationOptions, -): Evidence => - createEvidence( - execution.subject === undefined - ? undefined - : (execution.subject ?? undefined), - execution.provider, - { - operation, - parameters: jsonObjectSchema.parse(input), - result: execution.result, - rawResult: execution.rawResult, - limitations: execution.limitations, - ...(execution.analysisProfile === undefined - ? {} - : { analysisProfile: execution.analysisProfile }), - ...(execution.relationshipCoverage === undefined - ? {} - : { relationshipCoverage: execution.relationshipCoverage }), - locations: execution.locations, - confidence, - }, - options, - ); - /** Provider-neutral application capability for one analysis operation. */ export interface AnalysisOperationPort { execute( diff --git a/src/application/AppBundleResourceRoot.test.ts b/src/application/AppBundleResourceRoot.test.ts index 9b8a53da5b..98fcb76518 100644 --- a/src/application/AppBundleResourceRoot.test.ts +++ b/src/application/AppBundleResourceRoot.test.ts @@ -4,10 +4,8 @@ import { join } from "node:path"; import { expect, it } from "vitest"; -import { - createAnalysisExecution, - createAnalysisExecutionEvidence, -} from "./AnalysisProvider.js"; +import { createAnalysisExecution } from "./AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; import { resolveAppBundleResourceRoot } from "./AppBundleExecutable.js"; import { bindExecutionTarget } from "./binary/BinarySessionExecution.js"; import type { BinaryTarget } from "../domain/binaryTargetTypes.js"; diff --git a/src/application/DirectAnalysis.ts b/src/application/DirectAnalysis.ts index f3161896f7..a03d4c5b5c 100644 --- a/src/application/DirectAnalysis.ts +++ b/src/application/DirectAnalysis.ts @@ -47,7 +47,7 @@ import type { AnalysisExecution, ProviderIdentity, } from "./AnalysisProvider.js"; -import { createAnalysisExecutionEvidence } from "./AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; import { REA_WORKFLOW_PROVIDER, workflowAnalysisProfile, diff --git a/src/application/ProviderOperationExecution.ts b/src/application/ProviderOperationExecution.ts index 38012d0b5b..36ea645b82 100644 --- a/src/application/ProviderOperationExecution.ts +++ b/src/application/ProviderOperationExecution.ts @@ -17,11 +17,11 @@ import { import { jsonValueSchema } from "../domain/jsonValue.js"; import { projectInputIssues } from "../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../domain/result.js"; -import { - createAnalysisExecutionEvidence, - type AnalysisExecution, - type ExecutionOptions, +import type { + AnalysisExecution, + ExecutionOptions, } from "./AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; /** Validated caller-selected request carrying its operation input. */ export interface ProviderOperationRequest { diff --git a/src/application/captures/PacketCaptureService.ts b/src/application/captures/PacketCaptureService.ts index 3b91685ba8..8016021e11 100644 --- a/src/application/captures/PacketCaptureService.ts +++ b/src/application/captures/PacketCaptureService.ts @@ -3,11 +3,11 @@ import { packetFieldsCatalogSchema, type InspectPacketFieldsInput, } from "../../domain/captures/packetFields.js"; -import { - createAnalysisExecutionEvidence, - type AnalysisExecution, - type ExecutionOptions, +import type { + AnalysisExecution, + ExecutionOptions, } from "../AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { AnalysisCancelledError, diff --git a/src/application/firmware/FirmwareAnalysisService.ts b/src/application/firmware/FirmwareAnalysisService.ts index 223b14186a..e8281b0f6b 100644 --- a/src/application/firmware/FirmwareAnalysisService.ts +++ b/src/application/firmware/FirmwareAnalysisService.ts @@ -1,7 +1,5 @@ -import { - createAnalysisExecutionEvidence, - type ExecutionOptions, -} from "../AnalysisProvider.js"; +import type { ExecutionOptions } from "../AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js"; import type { FirmwareAnalysisPort } from "./FirmwareAnalysisPort.js"; import { firmwareRequestSchema, diff --git a/src/application/signals/SignalRecordingService.ts b/src/application/signals/SignalRecordingService.ts index 38fd87e8ac..6bde3fb63a 100644 --- a/src/application/signals/SignalRecordingService.ts +++ b/src/application/signals/SignalRecordingService.ts @@ -3,11 +3,11 @@ import { signalDecodersSchema, type InspectSignalDecodersInput, } from "../../domain/signals/signalDecoders.js"; -import { - createAnalysisExecutionEvidence, - type AnalysisExecution, - type ExecutionOptions, +import type { + AnalysisExecution, + ExecutionOptions, } from "../AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { AnalysisCancelledError, diff --git a/src/application/wasm/WasmArtifactService.ts b/src/application/wasm/WasmArtifactService.ts index 09cd529da1..77c978427d 100644 --- a/src/application/wasm/WasmArtifactService.ts +++ b/src/application/wasm/WasmArtifactService.ts @@ -1,7 +1,5 @@ -import { - createAnalysisExecutionEvidence, - type ExecutionOptions, -} from "../AnalysisProvider.js"; +import type { ExecutionOptions } from "../AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../../domain/analysisObservation.js"; import type { WasmArtifactPort } from "./WasmArtifactPort.js"; import type { AnalysisError } from "../../domain/analysisErrorBase.js"; import { diff --git a/src/domain/analysisObservation.ts b/src/domain/analysisObservation.ts new file mode 100644 index 0000000000..51d8154f15 --- /dev/null +++ b/src/domain/analysisObservation.ts @@ -0,0 +1,59 @@ +import { + createEvidence, + type Evidence, + type EvidenceCreationOptions, + type EvidenceLocation, + type EvidenceProvider, + type EvidenceSubjectTarget, +} from "./evidence.js"; +import type { AnalysisProfileCommitment } from "./analysisProfile.js"; +import { jsonObjectSchema, type JsonValue } from "./jsonValue.js"; +import type { RelationshipCoverage } from "./relationshipCoverage.js"; + +/** Provider observations that can be committed to Evidence. */ +export interface AnalysisObservation { + readonly result: JsonValue; + readonly rawResult: JsonValue | null; + readonly provider: EvidenceProvider; + readonly analysisProfile?: AnalysisProfileCommitment; + readonly limitations: readonly string[]; + /** Provider-owned completeness assessment for caller/callee observations. */ + readonly relationshipCoverage?: RelationshipCoverage; + readonly locations: readonly EvidenceLocation[]; + /** + * Missing means the selected operation's default subject should be bound by + * its caller. Explicit null means the observation has no file subject. + */ + readonly subject?: EvidenceSubjectTarget | null; +} + +/** Commit identical facts on success and when retaining a completed observation after cancellation. */ +export const createAnalysisExecutionEvidence = ( + operation: string, + input: unknown, + execution: AnalysisObservation, + confidence: "observed" | "derived", + options?: EvidenceCreationOptions, +): Evidence => + createEvidence( + execution.subject === undefined + ? undefined + : (execution.subject ?? undefined), + execution.provider, + { + operation, + parameters: jsonObjectSchema.parse(input), + result: execution.result, + rawResult: execution.rawResult, + limitations: execution.limitations, + ...(execution.analysisProfile === undefined + ? {} + : { analysisProfile: execution.analysisProfile }), + ...(execution.relationshipCoverage === undefined + ? {} + : { relationshipCoverage: execution.relationshipCoverage }), + locations: execution.locations, + confidence, + }, + options, + ); diff --git a/src/domain/evidence.ts b/src/domain/evidence.ts index f20ac5ba0c..5c68da4b67 100644 --- a/src/domain/evidence.ts +++ b/src/domain/evidence.ts @@ -227,11 +227,7 @@ export const rebindEvidenceSubjectTargetPath = ( type EvidenceAuthority = z.infer; type ExecutionEnvironment = z.infer; -export interface EvidenceProvider { - readonly id: string; - readonly name: string; - readonly version: string | null; -} +export type EvidenceProvider = Readonly>; export interface EvidenceObservation { readonly predicateType?: string; diff --git a/src/flutter/FlutterBuildProvider.ts b/src/flutter/FlutterBuildProvider.ts index 469660200e..6fc1eadd27 100644 --- a/src/flutter/FlutterBuildProvider.ts +++ b/src/flutter/FlutterBuildProvider.ts @@ -12,9 +12,9 @@ import type { } from "../application/AnalysisProvider.js"; import { createAnalysisExecution, - createAnalysisExecutionEvidence, type AnalysisExecution, } from "../application/AnalysisProvider.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; import { MAX_FLUTTER_LIBRARY_PATH_BYTES, type FlutterRequest, diff --git a/src/wasm/WabtArtifactProvider.ts b/src/wasm/WabtArtifactProvider.ts index 95b30b77dc..b5d7aee30a 100644 --- a/src/wasm/WabtArtifactProvider.ts +++ b/src/wasm/WabtArtifactProvider.ts @@ -30,6 +30,7 @@ import { import { ProviderCleanupError } from "../domain/providerCleanupError.js"; import { analysisErrorWithCleanupFailure } from "../domain/analysisErrorCleanup.js"; import { createEvidence } from "../domain/evidence.js"; +import { createAnalysisExecutionEvidence } from "../domain/analysisObservation.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { wasmArtifactSchema, @@ -478,17 +479,11 @@ export class WabtArtifactProvider implements WasmArtifactPort { result.ok && (options?.signal?.aborted || cleanup.kind === "failed") ) { - partialObservation = createEvidence( - result.value.subject ?? undefined, - result.value.provider, - { - operation: OPERATION, - parameters: input, - result: result.value.result, - confidence: "observed", - locations: result.value.locations, - limitations: result.value.limitations, - }, + partialObservation = createAnalysisExecutionEvidence( + OPERATION, + input, + result.value, + "observed", ); } if (result.ok && options?.signal?.aborted) {