Files
reverse-engineering-anything/src/application/binary/AnalysisProviderCandidateDiagnostics.ts
T
Henglie f20f05536e feat(providers): canonicalize selection diagnostics (#9171)
* feat(providers): canonicalize selection diagnostics

Extend candidate statuses with selection source and stable rejections derived from readiness and target support. Automatic selection now keeps every rejected candidate attributable, while explicit and ambiguous outcomes remain fail-closed and deterministic.

* test(providers): surface typed selection errors

Follow the repository test guide: throw the typed Result error before
reading the selected value so a failure reports the provider error.
2026-11-09 17:41:13 +08:00

59 lines
2.0 KiB
TypeScript

import type {
ProviderRejectionOrigin,
ProviderSelectionRejection,
} from "../../domain/providerSelectionError.js";
import type { AnalysisProviderCandidateStatus } from "./AnalysisProviderRegistry.js";
/** One availability or target-support observation that rejected a candidate. */
type RejectionObservation =
| Extract<
AnalysisProviderCandidateStatus["availability"],
{ status: "unavailable" }
>
| Extract<
AnalysisProviderCandidateStatus["targetSupport"],
{ status: "unsupported" }
>;
/** Re-derive the diagnostic view after readiness or target facts change. */
export const withCandidateRejections = (
status: AnalysisProviderCandidateStatus,
): AnalysisProviderCandidateStatus => ({
...status,
rejections: candidateRejections(status),
});
/** Stable reasons this candidate's readiness or target support excluded it. */
const candidateRejections = (
candidate: Pick<
AnalysisProviderCandidateStatus,
"provider" | "availability" | "targetSupport"
>,
): readonly ProviderSelectionRejection[] => {
const rejections: ProviderSelectionRejection[] = [];
if (candidate.availability.status === "unavailable")
rejections.push(
selectedRejection(candidate, "availability", candidate.availability),
);
if (candidate.targetSupport.status === "unsupported")
rejections.push(
selectedRejection(candidate, "target", candidate.targetSupport),
);
return rejections;
};
/** Name the observation a rejection came from so availability cannot read as a refusal. */
const selectedRejection = (
candidate: Pick<AnalysisProviderCandidateStatus, "provider">,
origin: ProviderRejectionOrigin,
rejection: RejectionObservation,
): ProviderSelectionRejection => ({
providerId: candidate.provider.id,
origin,
code: rejection.code,
reason: rejection.reason,
...("remediation" in rejection ? { remediation: rejection.remediation } : {}),
// ProviderSelectionError owns the single defensive diagnostic copy.
diagnostics: rejection.diagnostics,
});