Files
Jyotisha/frontend/src/lib/rectification-agentic/core/rectification-decision.ts
T
Jesse_ChenandCursor 85b14b13ae
Independent Staging Quality Gate / validate (push) Successful in 11m3s
Independent Staging Quality Gate / publish (push) Successful in 8m48s
fix(rectification): close non-converging range offer without an exit
When coverage still blocks adopt and no renderable discriminator remains, narrate the numeric range and persist a spoken collect instead of promising a time card that cannot be clicked. Recompute probe information gain on active candidates and drop zero-split probes explicitly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 18:35:16 +08:00

475 lines
15 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Single authoritative rectification decision.
*
* phase, nextAction, canOfferRange, canAdopt, and canConfirmExactMinute are
* computed here. precision_stage, selection_allowed, and active_focus policy
* are derived from this object and must not be judged independently.
*/
import type { CandidateDiscriminatorProbe } from "./candidate-contrast-packet.ts";
import {
evaluateCandidateSeparation,
type CandidateScoreRow,
type CandidateSeparation,
} from "./candidate-separation.ts";
import { rangeFromTimes } from "./credible-range.ts";
import type { DroppedProbe } from "../v9/probe-question-contract.ts";
import type { RectificationPhase, ResultStatus } from "./types.ts";
export type RectificationNextActionType =
| "ask_fact_collection"
| "ask_candidate_discriminator"
| "ask_holdout_validation"
| "offer_provisional_range"
| "complete_with_range"
| "ready_to_adopt";
export type HoldoutValidationStatus = "not_started" | "passed" | "failed" | "unavailable";
export type DecisionSessionOutcome =
| "collect_evidence"
| "discriminate_candidates"
| "validate_holdout"
| "provisional_range"
| "provisional_range_user_stopped"
| "completed_with_range"
| "validated_range"
| "exact_minute_confirmed"
| "adopt_representative"
| "awaiting_confirmation";
export type CompletionStatus =
| "provisional_range_user_stopped"
| "validated_range"
| "exact_minute_confirmed";
export type DerivedPrecisionStage = "collect_events" | "theme_refine" | "ready_to_adopt";
export type RectificationDecision = Readonly<{
phase: RectificationPhase;
nextAction: RectificationNextActionType;
sessionOutcome: DecisionSessionOutcome;
resultStatus: ResultStatus;
canOfferRange: boolean;
canAdopt: boolean;
canConfirmExactMinute: boolean;
selectionAllowed: boolean;
proposeAllowed: boolean;
precisionStage: DerivedPrecisionStage;
activeFocusPolicy: "keep" | "close";
completionStatus: CompletionStatus | null;
validated: boolean;
credibleRange: readonly [string, string] | null;
representativeTime: string | null;
separation: CandidateSeparation;
probe: CandidateDiscriminatorProbe | null;
holdoutValidation: HoldoutValidationStatus;
droppedProbes: readonly DroppedProbe[];
}>;
export type DecideRectificationInput = Readonly<{
methodCoverageAll: boolean;
confirmationAllowed?: boolean;
userStopped?: boolean;
snapshotCurrent?: boolean;
trainingGateOpen?: boolean;
candidateScores: readonly CandidateScoreRow[];
discriminatorProbe?: CandidateDiscriminatorProbe | null;
holdoutValidation?: HoldoutValidationStatus;
accepted?: boolean;
inferenceCredibleRange?: readonly [string, string] | null;
engineAcceptAllowed?: boolean;
engineProposeAllowed?: boolean;
datedMethodCollectOpen?: boolean;
}>;
export function decideRectification(input: DecideRectificationInput): RectificationDecision {
const separation = evaluateCandidateSeparation(input.candidateScores);
const probe = input.discriminatorProbe ?? null;
const holdout = input.holdoutValidation ?? "unavailable";
const userStopped = input.userStopped === true;
const confirmationAllowed = input.confirmationAllowed === true;
const range = input.inferenceCredibleRange
?? rangeFromTimes(separation.credibleRange)
?? (separation.representativeTime
? [separation.representativeTime, separation.representativeTime] as const
: null);
if (input.accepted) {
return finish(confirmationAllowed ? "awaiting_confirmation" : "adopt_representative", {
input,
separation,
holdout,
range,
probe: null,
canConfirmExactMinute: confirmationAllowed,
});
}
if (confirmationAllowed) {
return finish("awaiting_confirmation", {
input,
separation,
holdout,
range,
probe: null,
canConfirmExactMinute: true,
});
}
const canDiscriminateDespiteCoverage = Boolean(probe)
&& !separation.sufficient
&& input.trainingGateOpen !== false;
const coverageBlocks = (!input.methodCoverageAll && !canDiscriminateDespiteCoverage)
|| input.trainingGateOpen === false;
if (coverageBlocks && !(userStopped && input.candidateScores.length > 0)) {
const trainingOpen = input.trainingGateOpen !== false;
const engineOffers = input.engineAcceptAllowed === true || input.engineProposeAllowed === true;
if (
trainingOpen
&& input.candidateScores.length > 0
&& !probe
&& engineOffers
&& input.datedMethodCollectOpen !== true
) {
return offerRangeWithoutAdopt(separation, holdout, range);
}
return collect(separation, holdout, range, probe);
}
if (input.snapshotCurrent === false) {
if (probe && !userStopped && input.trainingGateOpen !== false) {
return discriminate(separation, holdout, range, probe);
}
if (!(userStopped && input.candidateScores.length > 0)) {
return collect(separation, holdout, range, probe);
}
}
if (!separation.sufficient) {
if (probe && !userStopped) {
return discriminate(separation, holdout, range, probe);
}
return completeWithRange(separation, holdout, range, userStopped ? "user_stopped" : "offer");
}
if (holdout === "not_started" && !userStopped) {
return holdoutValidation(separation, range);
}
if (holdout === "failed") {
if (probe && !userStopped) {
return discriminate(separation, holdout, range, probe);
}
return completeWithRange(separation, holdout, range, "exhausted");
}
if (holdout === "unavailable") {
if (userStopped) {
return completeWithRange(separation, holdout, range, "user_stopped");
}
return finish("adopt_representative", {
input,
separation,
holdout,
range,
probe: null,
canConfirmExactMinute: false,
});
}
if (userStopped && holdout !== "passed") {
return completeWithRange(separation, holdout, range, "user_stopped");
}
return finish("adopt_representative", {
input,
separation,
holdout,
range,
probe: null,
canConfirmExactMinute: false,
});
}
function collect(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
probe: CandidateDiscriminatorProbe | null,
): RectificationDecision {
return {
phase: "event_collection",
nextAction: "ask_fact_collection",
sessionOutcome: "collect_evidence",
resultStatus: "insufficient_evidence",
canOfferRange: false,
canAdopt: false,
canConfirmExactMinute: false,
selectionAllowed: false,
proposeAllowed: false,
precisionStage: "collect_events",
activeFocusPolicy: "keep",
completionStatus: null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe,
holdoutValidation: holdout,
droppedProbes: [],
};
}
function offerRangeWithoutAdopt(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
): RectificationDecision {
return {
phase: "discrimination",
nextAction: "offer_provisional_range",
sessionOutcome: "provisional_range",
resultStatus: "insufficient_evidence",
canOfferRange: true,
canAdopt: false,
canConfirmExactMinute: false,
selectionAllowed: false,
proposeAllowed: false,
precisionStage: "theme_refine",
activeFocusPolicy: "close",
completionStatus: null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe: null,
holdoutValidation: holdout,
droppedProbes: [],
};
}
export function isNonConvergingRangeOffer(decision: Pick<
RectificationDecision,
"canOfferRange" | "canAdopt" | "canConfirmExactMinute" | "nextAction"
>): boolean {
return decision.canOfferRange
&& !decision.canAdopt
&& !decision.canConfirmExactMinute
&& decision.nextAction === "offer_provisional_range";
}
export const REPRESENTATIVE_MINUTE_DISCLAIMER = "代表分钟只是代表性候选,不是已确认的唯一出生分钟。";
export function nonConvergingRangeNarration(input: {
credibleRange?: readonly [string, string] | null;
representativeTime?: string | null;
} = {}): string {
const range = input.credibleRange;
const representative = input.representativeTime?.trim() || null;
const rangeText = range?.[0] && range[1]
? range[0] === range[1] ? range[0] : `${range[0]}${range[1]}`
: null;
if (rangeText && representative) {
return `当前可信区间是 ${rangeText},代表分钟 ${representative}${REPRESENTATIVE_MINUTE_DISCLAIMER}`;
}
if (rangeText) {
return `当前可信区间是 ${rangeText}${REPRESENTATIVE_MINUTE_DISCLAIMER}`;
}
if (representative) {
return `当前代表分钟 ${representative}${REPRESENTATIVE_MINUTE_DISCLAIMER}`;
}
return `当前几个候选还分不开。${REPRESENTATIVE_MINUTE_DISCLAIMER}`;
}
function discriminate(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
probe: CandidateDiscriminatorProbe,
): RectificationDecision {
return {
phase: "discrimination",
nextAction: "ask_candidate_discriminator",
sessionOutcome: "discriminate_candidates",
resultStatus: "discriminating",
canOfferRange: false,
canAdopt: false,
canConfirmExactMinute: false,
selectionAllowed: false,
proposeAllowed: false,
precisionStage: "theme_refine",
activeFocusPolicy: "keep",
completionStatus: null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe,
holdoutValidation: holdout,
droppedProbes: [],
};
}
function holdoutValidation(
separation: CandidateSeparation,
range: readonly [string, string] | null,
): RectificationDecision {
return {
phase: "holdout_validation",
nextAction: "ask_holdout_validation",
sessionOutcome: "validate_holdout",
resultStatus: "discriminating",
canOfferRange: false,
canAdopt: false,
canConfirmExactMinute: false,
selectionAllowed: false,
proposeAllowed: false,
precisionStage: "theme_refine",
activeFocusPolicy: "keep",
completionStatus: null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe: null,
holdoutValidation: "not_started",
droppedProbes: [],
};
}
function completeWithRange(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
kind: "user_stopped" | "offer" | "exhausted",
): RectificationDecision {
const userStopped = kind === "user_stopped";
const terminal = kind !== "offer";
const nextAction = terminal ? "complete_with_range" : "offer_provisional_range";
const sessionOutcome = userStopped
? "provisional_range_user_stopped"
: terminal
? "completed_with_range"
: "provisional_range";
return {
phase: terminal ? "completed" : "discrimination",
nextAction,
sessionOutcome,
resultStatus: "completed_with_range",
canOfferRange: true,
canAdopt: true,
canConfirmExactMinute: false,
selectionAllowed: true,
proposeAllowed: true,
precisionStage: "ready_to_adopt",
activeFocusPolicy: "close",
completionStatus: userStopped ? "provisional_range_user_stopped" : null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe: null,
holdoutValidation: holdout,
droppedProbes: [],
};
}
function finish(
fallbackOutcome: "adopt_representative" | "awaiting_confirmation" | "validated_range" | "exact_minute_confirmed",
input: {
input: DecideRectificationInput;
separation: CandidateSeparation;
holdout: HoldoutValidationStatus;
range: readonly [string, string] | null;
probe: CandidateDiscriminatorProbe | null;
canConfirmExactMinute: boolean;
},
): RectificationDecision {
const completionStatus: CompletionStatus | null = input.canConfirmExactMinute && input.input.accepted
? "exact_minute_confirmed"
: input.holdout === "passed"
? "validated_range"
: input.input.userStopped === true
? "provisional_range_user_stopped"
: null;
const kind = input.canConfirmExactMinute
? (input.input.accepted ? "exact_minute_confirmed" : "awaiting_confirmation")
: input.holdout === "passed"
? "validated_range"
: fallbackOutcome;
return {
phase: "completed",
nextAction: "ready_to_adopt",
sessionOutcome: kind,
resultStatus: kind === "awaiting_confirmation" || kind === "exact_minute_confirmed"
? "converged"
: "completed_with_range",
canOfferRange: true,
canAdopt: true,
canConfirmExactMinute: input.canConfirmExactMinute,
selectionAllowed: true,
proposeAllowed: true,
precisionStage: "ready_to_adopt",
activeFocusPolicy: "close",
completionStatus,
validated: completionStatus === "validated_range" || completionStatus === "exact_minute_confirmed",
credibleRange: input.range,
representativeTime: input.separation.representativeTime,
separation: input.separation,
probe: input.probe,
holdoutValidation: input.holdout,
droppedProbes: [],
};
}
export function sessionKindFromNextAction(
type: RectificationNextActionType,
): DecisionSessionOutcome {
if (type === "ask_fact_collection") return "collect_evidence";
if (type === "ask_candidate_discriminator") return "discriminate_candidates";
if (type === "ask_holdout_validation") return "validate_holdout";
if (type === "offer_provisional_range") return "provisional_range";
if (type === "complete_with_range") return "completed_with_range";
return "adopt_representative";
}
export function offerSessionKinds(): readonly string[] {
return [
"adopt_representative",
"awaiting_confirmation",
"provisional_range",
"provisional_range_user_stopped",
"completed_with_range",
"validated_range",
"exact_minute_confirmed",
];
}
export function publicNextAction(decision: RectificationDecision): Readonly<{
type: RectificationNextActionType;
session_outcome: DecisionSessionOutcome;
completion_status: CompletionStatus | null;
validated: boolean;
can_offer_range: boolean;
can_adopt: boolean;
can_confirm_exact_minute: boolean;
selection_allowed: boolean;
propose_allowed: boolean;
precision_stage: DerivedPrecisionStage;
representative_time: string | null;
credible_range: readonly [string, string] | null;
}> {
return {
type: decision.nextAction,
session_outcome: decision.sessionOutcome,
completion_status: decision.completionStatus,
validated: decision.validated,
can_offer_range: decision.canOfferRange,
can_adopt: decision.canAdopt,
can_confirm_exact_minute: decision.canConfirmExactMinute,
selection_allowed: decision.selectionAllowed,
propose_allowed: decision.proposeAllowed,
precision_stage: decision.precisionStage,
representative_time: decision.representativeTime,
credible_range: decision.credibleRange,
};
}
export function publicDecisionFields(
decision: RectificationDecision,
): ReturnType<typeof publicNextAction> {
return publicNextAction(decision);
}