Files
rea/src/application/AnalysisProvider.ts
T
morluto 3c57e1a8b2 fix(artifacts): authenticate file subjects across provider and view boundaries
Narrow WASM and EVM execution ports to file subjects and preserve supplied subject types at the shared execution factory. Authenticate format with path and digest before recording or projecting Evidence, using the existing WASM identity owner in both consumers.

Validation: focused WASM/EVM service and public MCP workflows passed, including malformed provider subjects and self-contained retained Evidence with contradictory format. Global typecheck/lint and documentation checks passed. Real WABT/Evmole execution and the full suite were not run.
2026-10-13 13:23:03 +08:00

276 lines
10 KiB
TypeScript

import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
import type { OfficialToolName } from "../contracts/officialToolContracts.js";
import type { EnhancedToolName } from "../contracts/enhancedInputs.js";
import type { NativeToolName } from "../contracts/native/nativeToolContracts.js";
import type { ArtifactAnalysisOperation } from "../contracts/artifactToolContracts.js";
import type { ManagedToolName } from "../contracts/managed/managedToolContracts.js";
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 type { EvidenceLocation } from "../domain/evidence.js";
import type { EvidenceSubjectTarget } from "../domain/evidence.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";
import type { ProviderOperationHealth } from "../domain/providerOperationHealth.js";
import {
relationshipCoverageSchema,
type RelationshipCoverage,
} from "../domain/relationshipCoverage.js";
export interface ExecutionOptions {
readonly signal?: AbortSignal;
readonly progress?: ProgressReporter;
/**
* Set when a procedure selector came from provider output, such as a
* revisited callee, rather than from the caller. A provider-derived
* selector is never the caller's input mistake. Absent means the caller
* supplied it.
*/
readonly selectorOrigin?: "provider";
}
/** Closed operations accepted by provider-backed analysis sessions. */
export type AnalysisOperation =
| OfficialToolName
| EnhancedToolName
| NativeToolName
| ArtifactAnalysisOperation
| ManagedToolName
| "health";
/** Atomic successful provider observation with exact producing provenance. */
export interface AnalysisExecution {
readonly result: JsonValue;
readonly rawResult: JsonValue | null;
readonly provider: ProviderIdentity;
readonly analysisProfile?: AnalysisProfileCommitment;
readonly limitations: readonly string[];
/** Provider-owned completeness assessment for caller/callee observations. */
readonly relationshipCoverage?: RelationshipCoverage;
readonly locations: readonly EvidenceLocation[];
readonly subject: EvidenceSubjectTarget | null;
}
interface CreateAnalysisExecutionOptions {
readonly rawResult?: unknown;
readonly analysisProfile?: AnalysisProfileCommitment;
readonly limitations?: readonly string[];
readonly relationshipCoverage?: RelationshipCoverage;
readonly locations?: readonly EvidenceLocation[];
readonly subject?: EvidenceSubjectTarget;
}
/**
* Build a validated atomic provider observation at an adapter boundary.
*
* Pass `rawResult` only for a provider representation distinct from
* `result`. Omitting it records `null` rather than a second complete copy of
* the normalized result in every Evidence record and tool response.
*/
export function createAnalysisExecution<Subject extends EvidenceSubjectTarget>(
result: unknown,
provider: ProviderIdentity,
options: CreateAnalysisExecutionOptions & { readonly subject: Subject },
): Omit<AnalysisExecution, "subject"> & { readonly subject: Subject };
export function createAnalysisExecution(
result: unknown,
provider: ProviderIdentity,
options?: CreateAnalysisExecutionOptions,
): AnalysisExecution;
export function createAnalysisExecution(
result: unknown,
provider: ProviderIdentity,
options: CreateAnalysisExecutionOptions = {},
): AnalysisExecution {
return {
result: jsonValueSchema.parse(result),
rawResult:
options.rawResult === undefined
? null
: jsonValueSchema.parse(options.rawResult),
provider,
...(options.analysisProfile === undefined
? {}
: { analysisProfile: structuredClone(options.analysisProfile) }),
limitations: [...(options.limitations ?? [])],
...(options.relationshipCoverage === undefined
? {}
: {
relationshipCoverage: relationshipCoverageSchema.parse(
options.relationshipCoverage,
),
}),
locations: [...(options.locations ?? [])],
subject: options.subject ?? null,
};
}
/** Provider-neutral application capability for one analysis operation. */
export interface AnalysisOperationPort {
execute(
operation: AnalysisOperation,
parameters: Readonly<Record<string, JsonValue>>,
options?: ExecutionOptions,
): Promise<Result<AnalysisExecution, AnalysisError>>;
}
/** Retained provider-process observation attributed to its producing provider. */
export interface ProviderRuntimeLineageSnapshot {
readonly provider: ProviderIdentity;
readonly observation: ProcessLineageObservation;
}
/** One provider's current serial-request activity, including detached callers. */
export interface ProviderRequestActivitySnapshot {
readonly provider: ProviderIdentity;
readonly active: {
readonly requestId: number;
readonly operation: string;
readonly elapsedMs: number;
readonly callerState: "waiting" | "cancelled";
} | null;
readonly queuedRequests: number;
}
/** Provider session bound to exactly one parsed artifact. */
export interface AnalysisClient extends AnalysisOperationPort {
/** Execute only if this operation can run without loading or starting a provider. */
executeIfReady?(
operation: AnalysisOperation,
parameters: Readonly<Record<string, JsonValue>>,
options?: ExecutionOptions,
): Promise<Result<AnalysisExecution, AnalysisError> | undefined>;
/** Retained token-verified process snapshots for dynamic providers this client started. */
runtimeLineageSnapshots?(): readonly ProviderRuntimeLineageSnapshot[];
/** Current provider work that may outlive a timed-out or cancelled caller. */
requestActivitySnapshots?(): readonly ProviderRequestActivitySnapshot[];
/** Current operational health for the active provider bridge. */
operationHealthSnapshot?(): ProviderOperationHealth | undefined;
/** Close owned resources and retain failed cleanup for a later retry. */
close(
options?: Pick<ExecutionOptions, "progress"> & {
readonly retainDocument?: boolean;
},
): Promise<Result<null, AnalysisError>>;
}
/** Per-client identity allocated before any provider side effect starts. */
export interface AnalysisClientContext {
readonly runId: string;
}
export interface ProviderIdentity {
readonly id: string;
readonly name: string;
readonly version: string | null;
}
/** Typed analysis profile resolved before provider startup; null means unresolved. */
export interface AnalysisProfileResolution {
readonly profile: AnalysisProfileCommitment | null;
}
/** Cancellation context for side-effect-free provider profile resolution. */
export interface AnalysisProfileResolutionOptions {
readonly signal?: AbortSignal;
}
interface CapabilityEffects {
readonly mutatesArtifact: boolean;
readonly launchesProcess: boolean;
readonly mayShowUi: boolean;
readonly mayAccessNetwork: boolean;
readonly mayWriteFilesystem: boolean;
readonly changesPermissions: boolean;
readonly requiresRoot: boolean;
}
type CapabilityAvailability =
| {
readonly available: true;
readonly reason: null;
readonly availabilityCode?: null;
}
| {
readonly available: false;
readonly reason: string;
readonly availabilityCode?: ProviderRejectionCode;
};
export type CapabilityDescriptor = CapabilityAvailability & {
/**
* Snapshot opts immutable, profile-bound reads into replay despite incidental
* private filesystem writes. Live state must always be re-observed. Omitting
* this policy retains conservative effect checks; mutation and UI-state
* exclusions apply to every policy.
*/
readonly cachePolicy?: "snapshot" | "live";
readonly provider: ProviderIdentity;
readonly operation: Exclude<AnalysisOperation, "health">;
readonly effects: CapabilityEffects;
readonly limitations: readonly string[];
};
/** Factory and capability declaration for an analysis implementation. */
export interface AnalysisProvider {
identity(): ProviderIdentity;
capabilities(): readonly CapabilityDescriptor[];
resolveAnalysisProfile?(
target: BinaryTarget,
options?: AnalysisProfileResolutionOptions,
): Promise<Result<AnalysisProfileResolution, AnalysisError>>;
createClient(
target: BinaryTarget,
profile?: AnalysisProfileCommitment,
context?: AnalysisClientContext,
): AnalysisClient;
}
/** Bounded host observation made without starting an analysis process. */
export type ProviderAvailability =
| {
readonly status: "available";
readonly code: null;
readonly reason: null;
readonly diagnostics: Readonly<Record<string, JsonValue>>;
}
| {
readonly status: "unavailable";
readonly code: ProviderRejectionCode;
readonly reason: string;
readonly diagnostics: Readonly<Record<string, JsonValue>>;
};
/** Provider-owned target support observation before profile resolution. */
export type ProviderTargetSupport =
| {
readonly status: "supported";
readonly code: null;
readonly reason: null;
readonly diagnostics: Readonly<Record<string, JsonValue>>;
}
| {
readonly status: "unsupported";
readonly code:
| "target_kind_unsupported"
| "target_format_unsupported"
| "architecture_unsupported"
| "target_role_unsupported"
| "managed_target_unsupported";
readonly reason: string;
readonly diagnostics: Readonly<Record<string, JsonValue>>;
};
/** Deep provider candidate discoverable before one target-bound route is chosen. */
export interface AnalysisProviderCandidate extends AnalysisProvider {
resolveAnalysisProfile(
target: BinaryTarget,
options?: AnalysisProfileResolutionOptions,
): Promise<Result<AnalysisProfileResolution, AnalysisError>>;
inspectAvailability(): ProviderAvailability;
inspectTargetSupport(target: BinaryTarget): ProviderTargetSupport;
}