mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 09:21:16 +00:00
* 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.
59 lines
2.0 KiB
TypeScript
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,
|
|
});
|