Files
Jyotisha/frontend/src/lib/rectification-agentic/core/rectification-decision.ts
T
Jesse_ChenandCursor 35e5781e66
Independent Staging Quality Gate / validate (push) Successful in 9m50s
Independent Staging Quality Gate / publish (push) Successful in 1m53s
fix(rectification): gate collect-phase adopt cards and keep post-adopt verify answerable
Public can_adopt follows session_outcome; reverse_verify reuses the persisted question id; offer cards settle on the owning message with a status-bar handoff.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-03 09:29:34 +08:00

653 lines
22 KiB
TypeScript

/**
* 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 { EFFECTIVE_ANSWER_SAFETY_CAP } from "../../birth-time-dynamic-stop-policy.ts";
import { RECTIFICATION_POLICY } from "../../rectification-policy.ts";
import {
DEFAULT_MAX_DISCRIMINATION_ROUNDS,
type RectificationPhase,
type ResultStatus,
} from "./types.ts";
import {
REPRESENTATIVE_MINUTE_DISCLAIMER,
nonConvergingRangeNarration,
} from "../user-copy.ts";
export { REPRESENTATIVE_MINUTE_DISCLAIMER, nonConvergingRangeNarration };
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 EvidenceStopReason =
| "insufficient_dated_events"
| "insufficient_domains"
| "tied_first"
| "user_uncertainty_too_high";
export type StopClass =
| Readonly<{ kind: "keep_collecting"; reason: "insufficient_dated_events" | "insufficient_domains" }>
| Readonly<{ kind: "exhausted"; reason: "tied_first" | "user_uncertainty_too_high" }>
| Readonly<{ kind: "user_stopped" }>;
export type EngineCapabilityCeiling = Readonly<{
acceptanceAllowed: boolean;
selectionAllowed: boolean;
proposeAllowed: boolean;
confirmationAllowed: boolean;
}>;
type DeliveryCapability = Readonly<{
canAdopt: boolean;
selectionAllowed: boolean;
proposeAllowed: boolean;
canConfirmExactMinute: boolean;
}>;
const CLOSED_ENGINE_CAPABILITY_CEILING: EngineCapabilityCeiling = {
acceptanceAllowed: false,
selectionAllowed: false,
proposeAllowed: false,
confirmationAllowed: false,
};
export function engineCapabilityCeilingFromReceipt(value: unknown): EngineCapabilityCeiling {
if (!value || typeof value !== "object" || Array.isArray(value)) {
return CLOSED_ENGINE_CAPABILITY_CEILING;
}
const row = value as Readonly<Record<string, unknown>>;
const acceptanceAllowed = row.acceptance_allowed;
const selectionAllowed = row.selection_allowed;
const proposeAllowed = row.propose_allowed;
const confirmationAllowed = row.confirmation_allowed;
if (
typeof acceptanceAllowed !== "boolean"
|| typeof selectionAllowed !== "boolean"
|| typeof proposeAllowed !== "boolean"
|| typeof confirmationAllowed !== "boolean"
|| acceptanceAllowed !== selectionAllowed
|| (row.accept_allowed !== undefined
&& (typeof row.accept_allowed !== "boolean" || row.accept_allowed !== selectionAllowed))
|| (row.confirm_allowed !== undefined
&& (typeof row.confirm_allowed !== "boolean" || row.confirm_allowed !== confirmationAllowed))
|| (row.display_allowed !== undefined
&& (typeof row.display_allowed !== "boolean" || (selectionAllowed && !row.display_allowed)))
|| (proposeAllowed && !selectionAllowed)
|| (confirmationAllowed && !selectionAllowed)
) {
return CLOSED_ENGINE_CAPABILITY_CEILING;
}
return { acceptanceAllowed, selectionAllowed, proposeAllowed, confirmationAllowed };
}
export const RECTIFICATION_TERMINATION_COPY = "当前最优结果是候选时间段,而不是已经确认的唯一出生分钟。临时代表时间仅用于下一轮验证与比较。";
/** Standalone range-delivery floor; the exact-minute confirmation gate remains 4/3. */
export const MIN_STANDALONE_DATED_EVENTS = 3;
export const MIN_STANDALONE_DATED_DOMAINS = 2;
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";
/** Public `can_adopt` is true only in these outcomes. Internal `decision.canAdopt` may still be true during collect. */
export const ADOPT_OUTCOMES: ReadonlySet<DecisionSessionOutcome> = new Set([
"adopt_representative",
"provisional_range_user_stopped",
"awaiting_confirmation",
"validated_range",
]);
export function sessionOutcomeAllowsAdopt(
outcome: DecisionSessionOutcome | string | null | undefined,
): outcome is DecisionSessionOutcome {
return typeof outcome === "string" && ADOPT_OUTCOMES.has(outcome as DecisionSessionOutcome);
}
export function publicCanAdopt(decision: Pick<RectificationDecision, "canAdopt" | "sessionOutcome">): boolean {
return decision.canAdopt && sessionOutcomeAllowsAdopt(decision.sessionOutcome);
}
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[];
stopReason?: EvidenceStopReason | null;
terminationCopy?: string | null;
}>;
export type DecideRectificationInput = Readonly<{
methodCoverageAll: boolean;
confirmationAllowed?: boolean;
userStopped?: boolean;
snapshotCurrent?: boolean;
trainingGateOpen?: boolean;
candidateScores: readonly CandidateScoreRow[];
discriminatorProbe?: CandidateDiscriminatorProbe | null;
nakshatraBoundaryProbe?: CandidateDiscriminatorProbe | null;
holdoutValidation?: HoldoutValidationStatus;
accepted?: boolean;
inferenceCredibleRange?: readonly [string, string] | null;
engineCeiling: EngineCapabilityCeiling;
datedMethodCollectOpen?: boolean;
/** Server-persisted inference counters; never infer these from chat text. */
inferenceRounds?: number;
effectiveAnswerCount?: number;
plateauRounds?: number;
/** Minimum standalone delivery floor; distinct from the 4/3 confirmation gate. */
datedEventCount?: number;
datedDomainCount?: number;
userUncertaintyHigh?: boolean;
}>;
function classifyStop(
input: DecideRectificationInput,
separation: CandidateSeparation,
): StopClass | null {
if (
input.datedEventCount !== undefined
&& input.datedEventCount < MIN_STANDALONE_DATED_EVENTS
) {
return { kind: "keep_collecting", reason: "insufficient_dated_events" };
}
if (
input.datedDomainCount !== undefined
&& input.datedDomainCount < MIN_STANDALONE_DATED_DOMAINS
) {
return { kind: "keep_collecting", reason: "insufficient_domains" };
}
if (separation.ranked.length === 0 && !input.discriminatorProbe) {
return { kind: "keep_collecting", reason: "insufficient_dated_events" };
}
if (input.userStopped === true) return { kind: "user_stopped" };
if (separation.tiedForFirst) return { kind: "exhausted", reason: "tied_first" };
if (input.userUncertaintyHigh === true) {
return { kind: "exhausted", reason: "user_uncertainty_too_high" };
}
return null;
}
function deliveryCapability(input: {
stopClass: StopClass | null;
separation: CandidateSeparation;
holdout: HoldoutValidationStatus;
engineCeiling: EngineCapabilityCeiling;
confirmationAllowed: boolean;
trainingGateOpen: boolean;
}): DeliveryCapability {
const locallySelectable = input.separation.ranked.length > 0
&& input.trainingGateOpen
&& input.stopClass?.kind !== "keep_collecting";
return {
canAdopt: locallySelectable && input.engineCeiling.acceptanceAllowed,
selectionAllowed: locallySelectable && input.engineCeiling.selectionAllowed,
proposeAllowed: locallySelectable && input.engineCeiling.proposeAllowed,
canConfirmExactMinute: locallySelectable
&& input.separation.sufficient
&& input.holdout === "passed"
&& input.confirmationAllowed
&& input.engineCeiling.confirmationAllowed,
};
}
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);
const stopClass = classifyStop(input, separation);
const stopReason = stopClass && "reason" in stopClass ? stopClass.reason : null;
const canDiscriminateDespiteCoverage = Boolean(probe)
&& !separation.sufficient
&& input.trainingGateOpen !== false;
const coverageBlocks = (!input.methodCoverageAll && !canDiscriminateDespiteCoverage)
|| input.trainingGateOpen === false;
const capability = deliveryCapability({
stopClass,
separation,
holdout,
engineCeiling: input.engineCeiling,
confirmationAllowed,
trainingGateOpen: input.trainingGateOpen !== false,
});
if (input.snapshotCurrent === false) {
if (probe && !userStopped && input.trainingGateOpen !== false) {
return discriminateOrExhaust(input, separation, holdout, range, probe, capability, stopReason);
}
return collect(separation, holdout, range, probe, capability, stopReason);
}
if (userStopped && separation.ranked.length > 0) {
return completeWithRange(separation, holdout, range, "user_stopped", capability);
}
if (coverageBlocks) {
const engineOffers = input.engineCeiling.acceptanceAllowed
|| input.engineCeiling.proposeAllowed;
if (
stopClass?.kind !== "keep_collecting"
&& input.trainingGateOpen !== false
&& separation.ranked.length > 0
&& !probe
&& engineOffers
&& input.datedMethodCollectOpen !== true
) {
return offerRangeWithoutAdopt(separation, holdout, range, capability);
}
return collect(separation, holdout, range, probe, capability, stopReason);
}
if (stopClass?.kind === "keep_collecting") {
return collect(separation, holdout, range, probe, capability, stopClass.reason);
}
if (stopClass?.kind === "exhausted" && stopClass.reason === "user_uncertainty_too_high") {
return completeWithRange(separation, holdout, range, "exhausted", capability, stopClass.reason);
}
if (!separation.sufficient) {
if (probe) {
return discriminateOrExhaust(input, separation, holdout, range, probe, capability, stopReason);
}
if (holdout === "not_started" && !capability.canAdopt) {
return holdoutValidation(separation, range, capability);
}
if (input.datedMethodCollectOpen === true) {
return collect(separation, holdout, range, null, capability, stopReason);
}
if (input.nakshatraBoundaryProbe) {
return discriminateOrExhaust(
input,
separation,
holdout,
range,
input.nakshatraBoundaryProbe,
capability,
stopReason,
);
}
return stopClass?.kind === "exhausted"
? completeWithRange(separation, holdout, range, "exhausted", capability, stopClass.reason)
: completeWithRange(separation, holdout, range, "offer", capability);
}
if (stopClass?.kind === "exhausted") {
return completeWithRange(separation, holdout, range, "exhausted", capability, stopClass.reason);
}
if (input.accepted) {
return finish(confirmationAllowed ? "awaiting_confirmation" : "adopt_representative", {
input,
separation,
holdout,
range,
probe: null,
capability,
});
}
if (confirmationAllowed) {
return finish("awaiting_confirmation", {
input,
separation,
holdout,
range,
probe: null,
capability,
});
}
if (holdout === "not_started" && !capability.canAdopt) {
return holdoutValidation(separation, range, capability);
}
if (holdout === "failed") {
if (probe) {
return discriminateOrExhaust(input, separation, holdout, range, probe, capability);
}
return completeWithRange(separation, holdout, range, "exhausted", capability);
}
if (holdout === "unavailable") {
return offerRangeWithoutAdopt(separation, holdout, range, capability);
}
return finish("adopt_representative", {
input,
separation,
holdout,
range,
probe: null,
capability,
});
}
function budgetExhausted(input: DecideRectificationInput): boolean {
return (input.inferenceRounds ?? 0) >= DEFAULT_MAX_DISCRIMINATION_ROUNDS
|| (input.effectiveAnswerCount ?? 0) >= EFFECTIVE_ANSWER_SAFETY_CAP
|| (input.plateauRounds ?? 0) >= RECTIFICATION_POLICY.maxPlateauRounds;
}
function discriminateOrExhaust(
input: DecideRectificationInput,
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
probe: CandidateDiscriminatorProbe,
capability: DeliveryCapability,
stopReason: EvidenceStopReason | null = null,
): RectificationDecision {
if (budgetExhausted(input)) {
return completeWithRange(separation, holdout, range, "exhausted", capability, stopReason);
}
return discriminate(separation, holdout, range, probe, capability, stopReason);
}
function collect(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
probe: CandidateDiscriminatorProbe | null,
capability: DeliveryCapability,
stopReason: EvidenceStopReason | null = null,
): RectificationDecision {
return {
phase: "event_collection",
nextAction: "ask_fact_collection",
sessionOutcome: "collect_evidence",
resultStatus: "insufficient_evidence",
canOfferRange: false,
...capability,
precisionStage: "collect_events",
activeFocusPolicy: "keep",
completionStatus: null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe,
holdoutValidation: holdout,
droppedProbes: [],
stopReason,
};
}
function offerRangeWithoutAdopt(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
capability: DeliveryCapability,
): RectificationDecision {
return {
phase: "discrimination",
nextAction: "offer_provisional_range",
sessionOutcome: capability.canAdopt ? "adopt_representative" : "provisional_range",
resultStatus: "insufficient_evidence",
canOfferRange: true,
...capability,
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";
}
function discriminate(
separation: CandidateSeparation,
holdout: HoldoutValidationStatus,
range: readonly [string, string] | null,
probe: CandidateDiscriminatorProbe,
capability: DeliveryCapability,
stopReason: EvidenceStopReason | null = null,
): RectificationDecision {
return {
phase: "discrimination",
nextAction: "ask_candidate_discriminator",
sessionOutcome: "discriminate_candidates",
resultStatus: "discriminating",
canOfferRange: false,
...capability,
precisionStage: "theme_refine",
activeFocusPolicy: "keep",
completionStatus: null,
validated: false,
credibleRange: range,
representativeTime: separation.representativeTime,
separation,
probe,
holdoutValidation: holdout,
droppedProbes: [],
...(stopReason ? { stopReason } : {}),
};
}
function holdoutValidation(
separation: CandidateSeparation,
range: readonly [string, string] | null,
capability: DeliveryCapability,
): RectificationDecision {
return {
phase: "holdout_validation",
nextAction: "ask_holdout_validation",
sessionOutcome: "validate_holdout",
resultStatus: "discriminating",
canOfferRange: false,
...capability,
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",
capability: DeliveryCapability,
stopReason: EvidenceStopReason | null = null,
): RectificationDecision {
const userStopped = kind === "user_stopped";
const terminal = kind !== "offer";
const nextAction = terminal ? "complete_with_range" : "offer_provisional_range";
const sessionOutcome = capability.canAdopt
? "adopt_representative"
: userStopped
? "provisional_range_user_stopped"
: terminal
? "completed_with_range"
: "provisional_range";
return {
phase: terminal ? "completed" : "discrimination",
nextAction,
sessionOutcome,
resultStatus: "completed_with_range",
canOfferRange: true,
...capability,
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: [],
stopReason,
terminationCopy: terminal ? RECTIFICATION_TERMINATION_COPY : null,
};
}
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;
capability: DeliveryCapability;
},
): RectificationDecision {
const completionStatus: CompletionStatus | null = input.capability.canConfirmExactMinute && input.input.accepted
? "exact_minute_confirmed"
: input.holdout === "passed"
? "validated_range"
: input.input.userStopped === true
? "provisional_range_user_stopped"
: null;
const kind = input.capability.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,
...input.capability,
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: publicCanAdopt(decision),
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);
}