Files
reverse-engineering-anything/src/domain/providerSelectionError.ts
T
Kaoru0822andClaude Opus 5.5 cbc69537cd fix(discovery): carry provider repair instructions into capability discovery (#8532)
* fix(discovery): carry provider repair instructions into capability discovery

Provider readiness reported only a failure description, so capability
discovery copied that description into tool_availability remediation and
dropped repairs the owners already had: the JEB discovery-deadline setting,
the ADB/Apktool execute-permission repair kept in diagnostics, and the IDA
and Kuna configuration recovery.

Unavailable ProviderAvailability now requires the owner's remediation
next to its reason, and readiness-derived unavailable capability
descriptors carry it too. Every producer supplies it (ADB, Apktool, JADX,
JEB, Ghidra, Hopper, IDA, Kuna, pcap, owned-process prerequisites, profile
rejection); the inventory uses it verbatim, falling back to the reason
only for static capability facts that declare no repair. ADB/Apktool
configuration failures own remediation as a field instead of diagnostics,
and execution refusals project it as remediation.action. JADX doctor reads
the availability's remediation instead of re-deriving it.

binary_session complete candidate availability and unavailable capability
groups expose remediation (CLI capabilities/providers too); summary omits
it and lists it in omitted_fields. The superseded IDA
configuration_remediation and windows_native_failure.remediation
diagnostics are removed.

Fixes #8506

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(discovery): keep owner repairs through provider selection and capability maps

Address review findings on the readiness-remediation contract:

- Provider selection rejections carry the availability owner's
  remediation, CLI/MCP error details project it, and an explicit
  selection refused by readiness uses it as remediation.action. IDA's
  unreadable registration and Ghidra's Windows readiness repair no
  longer degrade to generic selection advice now that the diagnostics
  copies are gone.
- Ghidra's Windows readiness and existing-project capability maps carry
  the same repair as the availability.
- pcap readiness keeps the limiter owner's repair (now a typed
  remediationAction) and falls back to Wireshark guidance only when the
  failure has none.
- ADB/Apktool repairs are owner constants that are complete sentences.
  The execution refusal keeps its previous message-plus-repair recovery,
  which the compiled CLI contract asserts.
- Regression tests compare with the owning formatter or constant and
  add public MCP and CLI coverage for an explicit IDA selection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-01 08:57:11 -08:00

131 lines
5.2 KiB
TypeScript

import type { JsonValue } from "./jsonValue.js";
import {
AnalysisError,
type AnalysisCapturedOutput,
type AnalysisErrorOptions,
} from "./analysisErrorBase.js";
/** Stable failure classes for target-bound deep-provider selection. */
export type ProviderSelectionFailureReason =
| "unknown_provider"
| "provider_unavailable"
| "target_unsupported"
| "ambiguous"
| "invalid_options";
/** Which observation produced a rejection; availability says nothing about the target. */
export type ProviderRejectionOrigin = "availability" | "target";
/** One actionable candidate rejection retained in caller diagnostics. */
export interface ProviderSelectionRejection {
readonly providerId: string;
readonly origin: ProviderRejectionOrigin;
readonly code: string;
readonly reason: string;
/** The readiness owner's repair; target-support refusals carry none. */
readonly remediation?: string;
readonly diagnostics: Readonly<Record<string, JsonValue>>;
}
/** Complete typed context for one failed deep-provider selection. */
export interface ProviderSelectionErrorOptions extends Pick<
AnalysisErrorOptions,
"cause" | "diagnostics" | "partialObservation"
> {
readonly capturedOutput?: AnalysisCapturedOutput | undefined;
readonly userMessage?: string;
/** Recovery known to the provider that refused the target, when it has one. */
readonly remediationAction?: string;
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[];
readonly remediationAction: string | undefined;
override readonly userMessage: string;
constructor(options: ProviderSelectionErrorOptions) {
const operation = options.operation ?? "open_binary";
super(providerSelectionDiagnostic(options), {
capturedOutput: options.capturedOutput,
diagnostics: options.diagnostics,
partialObservation: options.partialObservation,
...(options.cause === undefined ? {} : { cause: options.cause }),
});
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.remediationAction =
options.remediationAction ?? requestedProviderRepair(options);
this.userMessage =
options.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}`;
};
/**
* A concrete selection refused by its provider's readiness is repaired by
* that provider's own instruction, not by generic selection advice.
*/
const requestedProviderRepair = (
options: ProviderSelectionErrorOptions,
): string | undefined =>
options.reason === "provider_unavailable"
? options.rejections?.find(
({ providerId, origin }) =>
providerId === options.requestedProviderId &&
origin === "availability",
)?.remediation
: undefined;
/** True when every recorded rejection describes availability, not target support. */
const providerAvailabilityOnly = (
rejections: readonly ProviderSelectionRejection[] | undefined,
): boolean =>
rejections !== undefined &&
rejections.length > 0 &&
rejections.every(({ origin }) => origin === "availability");
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 providerAvailabilityOnly(options.rejections)
? "Provider selection failed local readiness checks. No target-support refusal was reported. Review the local provider diagnostics or run `rea doctor`, then retry."
: "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.";
}
};