* test(mcp): expect trace_feature to report references the provider truncated A provider that reports a capped xrefs read makes the trace truncated with a residual unknown for that target. An explicit complete report and a provider that reports nothing leave the trace as before. * fix(mcp): report references a provider truncated in trace_feature Executions gain an optional flag for a capped xrefs read, set by the IDA adapter from the upstream cap. trace_feature marks the trace truncated with a residual unknown for that target only when the provider reports a truncation; a provider that reports nothing keeps its previous result. The trace_feature workflow profile is revised so earlier results are not replayed. * refactor(ida): report reference truncation whenever the runner observed an xrefs cap * test(mcp): cover session forwarding, per-target state, native reports and failures of the trace coverage * test(mcp): format the session forwarding test * test(ida): expect headless IDA to resolve containing procedures from lookups and segment facts Cover positive and negative containment, segment boundaries including addresses above 2^53, failures of the lookup and of the segment survey, cancellation, input validation and the admitted program as the only document. * test(ida): expect feature tracing and address context to work on headless IDA Run trace_feature, including a capped reference read, and inspect_address_context with and without an explicit document through the IDA session client. * test(mcp): expect trace_feature availability to follow the search operations it calls * fix(mcp): derive trace_feature availability from the search operations it calls * feat(ida): resolve containing procedures and report the admitted program as the document The modern headless profile resolves an address through the function lookup and, when no function contains it, tells a mapped address from an unmapped one using the database-scoped minimal survey. A survey that is missing, truncated or inconsistent fails the read. list_documents reports the single admitted program. The attached profiles do not declare either operation. * docs(ida): describe containing procedures, the admitted document and truncated feature traces * test(ida): verify containing procedures, the admitted document and feature tracing on real IDA * docs(testing): list the headless IDA workflows the real lane verifies * test(ida): make each survey failure case isolate a single defect * test(ida): pin large addresses, segment order, document identity, attached refusals and each trace_feature prerequisite * test(mcp): expect a repeated reference source to be resolved once in trace_feature * fix(mcp): resolve the containing procedure of a repeated reference source once * test(ida): trace the fixture procedure and derive probe addresses from its facts in the real lane * test(process): format processCaptureLifecycle.test.ts after the merge * test(ida): declare the alignment each synthetic universal slice offset satisfies * refactor(ida): move the real lane's feature trace assertions into a helper * test(ida): expect the real lane's feature trace check to accept references to every matched address * fix(ida): check each trace reference against its own matched address in the real lane * test(ida): pin that the new operations reject a document other than the admitted program * refactor(ida): move the real lane's mapped non-function probe into a helper * test(ida): expect the real lane's mapped data probe to skip strings inside functions * fix(ida): try the target's strings in order for the mapped non-function probe * test(ida): declare the real lane helper types for the verifier tests * test(ida): expect the real lane to reject a listed string that resolves as unmapped * fix(ida): fail the real lane when a listed string resolves as unmapped
287 lines
10 KiB
TypeScript
287 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;
|
|
/**
|
|
* Whether the provider capped an `xrefs` read: true when it reported a
|
|
* truncation, false when it explicitly reported none. Absent when it reports
|
|
* nothing, which is not a completeness claim. Never part of Evidence or tool
|
|
* output.
|
|
*/
|
|
readonly referencesTruncated?: boolean;
|
|
readonly locations: readonly EvidenceLocation[];
|
|
readonly subject: EvidenceSubjectTarget | null;
|
|
}
|
|
|
|
interface CreateAnalysisExecutionOptions {
|
|
readonly rawResult?: unknown;
|
|
readonly analysisProfile?: AnalysisProfileCommitment;
|
|
readonly limitations?: readonly string[];
|
|
readonly relationshipCoverage?: RelationshipCoverage;
|
|
readonly referencesTruncated?: boolean;
|
|
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,
|
|
),
|
|
}),
|
|
...(options.referencesTruncated === undefined
|
|
? {}
|
|
: { referencesTruncated: options.referencesTruncated }),
|
|
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;
|
|
}
|