refactor(evidence): centralize analysis observation facts

This commit is contained in:
morluto committed 2026-10-23 13:17:19 +08:00
1 parent 7f9a03d6ab
commit 6dff37594c
14 files changed
+97 -101

No files matched your search

+1 -1
View File
@@ -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,
+4 -4
View File
@@ -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,
+6 -54
View File
@@ -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(
@@ -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";
+1 -1
View File
@@ -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,
@@ -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 {
@@ -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,
@@ -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,
@@ -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,
+2 -4
View File
@@ -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 {
+59
View File
@@ -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,
);
+1 -5
View File
@@ -227,11 +227,7 @@ export const rebindEvidenceSubjectTargetPath = (
type EvidenceAuthority = z.infer<typeof evidenceAuthoritySchema>;
type ExecutionEnvironment = z.infer<typeof executionEnvironmentSchema>;
export interface EvidenceProvider {
readonly id: string;
readonly name: string;
readonly version: string | null;
}
export type EvidenceProvider = Readonly<z.output<typeof providerSchema>>;
export interface EvidenceObservation {
readonly predicateType?: string;
+1 -1
View File
@@ -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,
+6 -11
View File
@@ -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) {