Files
reverse-engineering-anything/src/application/AnalysisProvider.ts
T
Mykola Dementiev d3e9ee5505 fix(ida): report a procedure IDA cannot resolve as invalid input (#1785)
* test(ida): expect a missing procedure to be reported as invalid input

* test(ida): name the invalid_request code and its category for a missing procedure

* fix(ida): report a procedure IDA cannot resolve as invalid input

* test(ida): pin the missing-procedure issue shape and the lookups that stay unreadable

* fix(ida): treat a lookup answer for another query as a producer fault

* test(ida): expect an argument-independent issue path and pin legacy error replies and escaping

* fix(ida): report a missing procedure without naming an argument path

* test(ida): expect provider-derived selectors to stay producer faults in composed workflows

* fix(ida): keep provider-derived procedure selectors out of the caller's input errors

* test(ida): cover session forwarding, parameter hygiene and structured derived-node errors

* docs(ida): describe when procedure resolution blames the caller
2026-10-11 22:52:16 +08:00

242 lines
8.7 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";
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[];
readonly locations: readonly EvidenceLocation[];
readonly subject: EvidenceSubjectTarget | null;
}
/**
* 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 const createAnalysisExecution = (
result: unknown,
provider: ProviderIdentity,
options: {
readonly rawResult?: unknown;
readonly analysisProfile?: AnalysisProfileCommitment;
readonly limitations?: readonly string[];
readonly locations?: readonly EvidenceLocation[];
readonly subject?: EvidenceSubjectTarget;
} = {},
): AnalysisExecution => ({
result: jsonValueSchema.parse(result),
rawResult:
options.rawResult === undefined
? null
: jsonValueSchema.parse(options.rawResult),
provider,
...(options.analysisProfile === undefined
? {}
: { analysisProfile: structuredClone(options.analysisProfile) }),
limitations: [...(options.limitations ?? [])],
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 {
/** 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;
}