import type { JsonValue } from "./jsonValue.js"; import type { BrowserObservationFailureReason, BrowserObservationOperation, } from "./browserObservationErrors.js"; export type { BrowserObservationOperation } from "./browserObservationErrors.js"; import type { MissingPermissionScope, PermissionRequest, PermissionScope, } from "./permissionPolicy.js"; import { hopperStartupFailure, type HopperStartupDiagnostic, type HopperStartupFailureDiagnostic, type HopperStartupFailureCode, } from "./hopperStartupFailure.js"; export { hopperStartupFailure, type HopperStartupDiagnostic, type HopperStartupFailureDiagnostic, type HopperStartupFailureCode, }; export { projectAnalysisError } from "./analysisErrorProjection.js"; /** Stable tags exposed by safe analysis-error projections. */ const ANALYSIS_ERROR_TAGS = [ "AnalysisProtocolError", "AnalysisInputError", "AnalysisOutputError", "AnalysisCapabilityUnavailableError", "AnalysisCancelledError", "AnalysisTimeoutError", "ProviderSelectionError", "ProviderAdapterError", "BrowserObservationError", "ArtifactOperationError", "ProcessCaptureError", "EvidenceIntegrityError", "EvidenceLimitError", "EvidenceFileError", "WorkspaceStorageError", "UnknownRegistryError", "HopperTimeoutError", "HopperCancelledError", "HopperProtocolError", "HopperRemoteError", "HopperProcessError", "HopperStartError", "ConfigurationError", "NoBinaryOpenError", "BinaryTargetError", "PermissionRequiredError", "ReplayPlanStaleError", ] as const; /** Stable tag for an expected analysis failure. */ export type AnalysisErrorTag = (typeof ANALYSIS_ERROR_TAGS)[number]; /** Base class for expected analysis, provider, and session failures. */ export abstract class AnalysisError extends Error { abstract readonly _tag: AnalysisErrorTag; readonly userMessage: string | undefined = undefined; readonly userCategory: "permission_required" | "cancelled" | undefined = undefined; readonly cleanupIncomplete: boolean = false; readonly cleanupResources: readonly string[] = []; } /** Base class for failures produced specifically by the Hopper provider. */ export abstract class HopperError extends AnalysisError {} /** Provider-neutral invalid analysis input or output at an application boundary. */ export class AnalysisProtocolError extends AnalysisError { readonly _tag = "AnalysisProtocolError"; } /** Caller input failed provider-neutral application parsing. */ export class AnalysisInputError extends AnalysisError { readonly _tag = "AnalysisInputError"; constructor( readonly operation: string, options?: ErrorOptions, readonly issues: readonly AnalysisInputIssue[] = [], ) { super(`Invalid analysis input for ${operation}`, options); } } /** Secret-safe correction metadata for one rejected caller argument. */ export interface AnalysisInputIssue { readonly path: readonly (string | number)[]; readonly reason: | "unknown_argument" | "missing_argument" | "invalid_type" | "out_of_range" | "invalid_value" | "invalid_format"; /** Schema-authored correction guidance for cross-field or custom checks. */ readonly message?: string; readonly expected?: JsonValue; readonly minimum?: number; readonly maximum?: number; } /** Provider output failed provider-neutral application parsing. */ export class AnalysisOutputError extends AnalysisError { readonly _tag = "AnalysisOutputError"; constructor( readonly operation: string, readonly reason: string, options?: ErrorOptions, ) { super(`Invalid analysis output for ${operation}: ${reason}`, options); } } /** Selected provider cannot execute a declared analysis operation. */ export class AnalysisCapabilityUnavailableError extends AnalysisError { readonly _tag = "AnalysisCapabilityUnavailableError"; constructor( readonly providerId: string, readonly operation: string, readonly reason: string, ) { super(`Provider ${providerId} cannot execute ${operation}: ${reason}`); } } /** Caller cancellation won before provider-neutral work completed. */ export class AnalysisCancelledError extends AnalysisError { readonly _tag = "AnalysisCancelledError"; constructor(readonly operation: string) { super(`Analysis operation was cancelled: ${operation}`); } } /** Provider-neutral operation exceeded its declared execution deadline. */ export class AnalysisTimeoutError extends AnalysisError { readonly _tag = "AnalysisTimeoutError"; constructor( readonly operation: string, readonly timeoutMs: number, ) { super( `Analysis operation timed out after ${String(timeoutMs)}ms: ${operation}`, ); } } /** Stable failure classes for target-bound deep-provider selection. */ export type ProviderSelectionFailureReason = | "unknown_provider" | "provider_unavailable" | "target_unsupported" | "ambiguous" | "invalid_options"; /** One actionable candidate rejection retained in caller diagnostics. */ export interface ProviderSelectionRejection { readonly providerId: string; readonly code: string; readonly reason: string; readonly diagnostics: Readonly>; } /** Complete typed context for one failed deep-provider selection. */ export interface ProviderSelectionErrorOptions { readonly operation?: string; readonly reason: ProviderSelectionFailureReason; readonly requestedProviderId: string; readonly candidateIds: readonly string[]; readonly rejections?: readonly ProviderSelectionRejection[]; } /** No configured provider can satisfy a requested operation or target binding. */ export class ProviderSelectionError extends AnalysisError { readonly _tag = "ProviderSelectionError"; readonly operation: string; readonly reason: ProviderSelectionFailureReason; readonly requestedProviderId: string; readonly candidateIds: readonly string[]; readonly rejections: readonly ProviderSelectionRejection[]; override readonly userMessage: string; constructor(options: ProviderSelectionErrorOptions) { const operation = options.operation ?? "open_binary"; super(providerSelectionDiagnostic(options)); this.operation = operation; this.reason = options.reason; this.requestedProviderId = options.requestedProviderId; this.candidateIds = [...options.candidateIds]; this.rejections = [...(options.rejections ?? [])].map((rejection) => ({ ...rejection, diagnostics: structuredClone(rejection.diagnostics), })); this.userMessage = providerSelectionUserMessage(options); } } const providerSelectionDiagnostic = ( options: ProviderSelectionErrorOptions, ): string => { const candidates = options.candidateIds.length === 0 ? "none" : options.candidateIds.join(", "); return `Analysis provider selection failed (${options.reason}) for ${options.requestedProviderId}; candidates: ${candidates}`; }; const providerSelectionUserMessage = ( options: ProviderSelectionErrorOptions, ): string => { switch (options.reason) { case "ambiguous": return "Multiple analysis providers support this target. Choose one with provider_id, --provider, or REA_ANALYSIS_PROVIDER."; case "unknown_provider": return `The requested analysis provider is unknown. Choose one of: ${options.candidateIds.join(", ") || "no configured providers"}.`; case "provider_unavailable": return "The selected analysis provider is unavailable. Review the reported local diagnostics or run `rea doctor`, then retry."; case "target_unsupported": return "The selected analysis provider does not support this target. Choose another provider or open a supported extracted binary."; case "invalid_options": return "The analysis provider selection or open options are invalid. Correct them and try again."; } }; /** A provider adapter failed outside its more precise typed variants. */ export interface ProviderAdapterErrorOptions extends ErrorOptions { readonly diagnostics?: Readonly>; } /** A provider adapter failed outside its more precise typed variants. */ export class ProviderAdapterError extends AnalysisError { readonly _tag = "ProviderAdapterError"; readonly diagnostics: Readonly> | undefined; constructor( readonly providerId: string, readonly operation: string, options: ProviderAdapterErrorOptions = {}, ) { super(`Provider ${providerId} adapter failed during ${operation}`, options); this.diagnostics = options.diagnostics === undefined ? undefined : structuredClone(options.diagnostics); } } /** A bounded passive browser observation failed at its CDP boundary. */ export class BrowserObservationError extends AnalysisError { readonly _tag = "BrowserObservationError"; constructor( readonly operation: BrowserObservationOperation, readonly reason: BrowserObservationFailureReason, options?: ErrorOptions, ) { super(`Browser observation ${operation} failed: ${reason}`, options); } } /** Artifact inventory or extraction failed a typed safety boundary. */ export class ArtifactOperationError extends AnalysisError { readonly _tag = "ArtifactOperationError"; constructor( readonly operation: | "inventory_artifact" | "inspect_artifact" | "extract_artifact" | "analyze_javascript_application", readonly reason: | "cancelled" | "format" | "integrity" | "limit" | "path" | "policy" | "unavailable" | "io", readonly artifactDetails?: Readonly<{ logicalPath: string; declaredSha256: string | null; calculatedSha256: string | null; unpacked: boolean; }>, ) { super( artifactDetails === undefined ? `Artifact ${operation} failed: ${reason}` : `Artifact ${operation} failed: ${reason} at ${artifactDetails.logicalPath} (declared_sha256=${artifactDetails.declaredSha256 ?? "unavailable"}, calculated_sha256=${artifactDetails.calculatedSha256 ?? "unavailable"}, unpacked=${String(artifactDetails.unpacked)})`, ); } } /** Evidence identity, schema, or bundle manifests failed integrity checks. */ export class EvidenceIntegrityError extends AnalysisError { readonly _tag = "EvidenceIntegrityError"; } /** A session Evidence reference is missing or has the wrong semantic identity. */ export class EvidenceReferenceError extends EvidenceIntegrityError { constructor( readonly evidenceId: string, readonly reason: "missing" | "wrong_operation" | "wrong_predicate", readonly expected: string, readonly actual: string | null, ) { super(`Evidence reference ${reason}: ${evidenceId}`); } } /** A bounded evidence ledger cannot accept more records or serialized bytes. */ export class EvidenceLimitError extends AnalysisError { readonly _tag = "EvidenceLimitError"; constructor( readonly limit: "records" | "bytes", readonly maximum: number, ) { super(`Evidence ledger ${limit} limit exceeded (${String(maximum)})`); } } /** Evidence bundle filesystem access failed within the configured policy. */ export class EvidenceFileError extends AnalysisError { readonly _tag = "EvidenceFileError"; constructor( readonly operation: "read" | "write", readonly reason: | "disabled" | "outside-root" | "not-file" | "too-large" | "exists" | "invalid-json" | "io", options?: ErrorOptions, ) { super(`Evidence bundle ${operation} failed: ${reason}`, options); } } /** A persisted reconstruction coverage workspace could not be read or updated. */ export class WorkspaceStorageError extends AnalysisError { readonly _tag = "WorkspaceStorageError"; constructor( readonly operation: "read" | "update", readonly reason: | "disabled" | "outside-root" | "not-file" | "too-large" | "invalid-json" | "integrity" | "locked" | "revision-conflict" | "name-conflict" | "io", options?: ErrorOptions, ) { super(`Workspace storage ${operation} failed: ${reason}`, options); } } /** Residual-unknown mutation failed a lifecycle, reference, or CAS invariant. */ export class UnknownRegistryError extends AnalysisError { readonly _tag = "UnknownRegistryError"; constructor( readonly reason: | "not-found" | "already-exists" | "revision-conflict" | "invalid-transition" | "integrity" | "limit", options?: ErrorOptions, ) { super(`Residual unknown registry mutation failed: ${reason}`, options); } } /** Hopper did not respond within the configured operation deadline. */ export class HopperTimeoutError extends HopperError { readonly _tag = "HopperTimeoutError"; constructor( readonly timeoutMs: number, readonly operation?: string, readonly requestId?: number, readonly providerState: "busy" | "not_started" = "not_started", ) { super( `Hopper ${operation === undefined ? "startup" : operation} timed out after ${String(timeoutMs)}ms`, ); } } /** The caller cancelled a Hopper operation before it completed. */ export class HopperCancelledError extends HopperError { readonly _tag = "HopperCancelledError"; constructor() { super("Hopper operation was cancelled"); } } /** Hopper returned bytes or JSON that do not satisfy the NDJSON RPC contract. */ export class HopperProtocolError extends HopperError { readonly _tag = "HopperProtocolError"; } export type HopperDiagnosticType = | "remote" | "authorization" | "invalid_request" | "capability_unavailable" | "bridge_exception"; /** Hopper's JSON-RPC endpoint returned an expected remote error response. */ export interface HopperRemoteErrorContext { readonly diagnosticType?: HopperDiagnosticType; readonly operation?: string; readonly requestId?: number; } export class HopperRemoteError extends HopperError { readonly _tag = "HopperRemoteError"; readonly diagnosticType: HopperDiagnosticType; readonly operation: string | undefined; readonly requestId: number | undefined; constructor( readonly code: number, readonly safeMessage: string, context: HopperRemoteErrorContext = {}, ) { super(`Hopper request failed (${String(code)}): ${safeMessage}`); this.diagnosticType = context.diagnosticType ?? "remote"; this.operation = context.operation; this.requestId = context.requestId; } } /** The owned Hopper bridge stopped before its client was closed. */ export class HopperProcessError extends HopperError { readonly _tag = "HopperProcessError"; readonly failureCode: HopperStartupFailureCode | undefined; override readonly userMessage: string | undefined; constructor( readonly exitCode: number | null, readonly diagnostic?: HopperStartupFailureDiagnostic, readonly operation?: string, readonly requestId?: number, ) { super(`Hopper bridge stopped unexpectedly with code ${String(exitCode)}`); const failure = hopperStartupFailure(exitCode); this.failureCode = failure?.code; this.userMessage = failure?.message; } } /** Hopper or the repository bridge could not be started. */ export class HopperStartError extends HopperError { readonly _tag = "HopperStartError"; override readonly userMessage: string | undefined; readonly ownerRunId: string | undefined; constructor( options?: ErrorOptions & { readonly userMessage?: string; readonly ownerRunId?: string; }, ) { super("Hopper application bridge could not be started", options); this.userMessage = options?.userMessage; this.ownerRunId = options?.ownerRunId; } } /** Runtime configuration could not be parsed safely. */ export class ConfigurationError extends AnalysisError { readonly _tag = "ConfigurationError"; } /** No app or binary session exists for an analysis request. */ export class NoBinaryOpenError extends AnalysisError { readonly _tag = "NoBinaryOpenError"; constructor() { super( "No app is open. Ask the user which app to investigate, then call open_binary with its local path.", ); } } /** A supplied target path could not be safely opened as a supported app or binary. */ export class BinaryTargetError extends AnalysisError { readonly _tag = "BinaryTargetError"; constructor( readonly path: string, reason: string, options?: ErrorOptions, ) { super(`Cannot open artifact: ${reason}`, options); } } export interface AnalysisErrorProjection extends Readonly> { readonly code: | "invalid_request" | "unreadable_output" | "capability_unavailable" | "provider_unavailable" | "provider_timeout" | "cancelled" | "artifact_integrity_mismatch" | "artifact_operation_failed" | "evidence_integrity_mismatch" | "truncated" | "permission_required" | "process_capture_failed" | "cleanup_incomplete" | "revision_conflict" | "outside_approved_root" | "configuration_invalid" | "target_unavailable" | "execution_failure" | "plan_stale"; readonly category: | "invalid_input" | "permission_required" | "unsupported_provider" | "integrity_mismatch" | "truncated" | "cancelled" | "timeout" | "unavailable" | "execution_failure"; readonly message: string; readonly retryable: boolean; readonly remediation: Readonly<{ action: string; restart_required: boolean; elicitation_supported?: boolean; }>; readonly details?: Readonly>; } /** One legal recovery path for a denied permission request. */ export type PermissionRemediation = "configure" | "elicit" | "restart"; /** Exact denied authority and its legal recovery path. */ export interface PermissionRequiredContext { readonly requested: PermissionRequest; readonly missing: MissingPermissionScope; readonly ceiling: PermissionScope | null; readonly remediation: PermissionRemediation; } /** Exact denied authority used by both CLI and MCP remediation. */ export class PermissionRequiredError extends AnalysisError { readonly _tag = "PermissionRequiredError" as const; readonly requested: PermissionRequest; readonly missing: MissingPermissionScope; readonly ceiling: PermissionScope | null; readonly remediation: PermissionRemediation; constructor(context: PermissionRequiredContext) { super(`Permission required for ${context.requested.capability}`); this.requested = context.requested; this.missing = context.missing; this.ceiling = context.ceiling; this.remediation = context.remediation; } } /** Approved replay commitment no longer matches the immediately rebuilt plan. */ export class ReplayPlanStaleError extends AnalysisError { readonly _tag = "ReplayPlanStaleError" as const; constructor( readonly approvedDigest: string, readonly actualDigest: string, ) { super("Controlled replay plan changed before execution"); } }