fix(rectification): centralize delivery authority
This commit is contained in:
@@ -56,8 +56,12 @@ export function decideNextAction(input: DecideNextActionInput): RectificationNex
|
||||
holdoutValidation: input.holdoutValidation,
|
||||
accepted: input.accepted,
|
||||
inferenceCredibleRange: input.inferenceCredibleRange,
|
||||
engineAcceptAllowed: input.selectionAllowed,
|
||||
engineProposeAllowed: input.proposeAllowed,
|
||||
engineCeiling: {
|
||||
acceptanceAllowed: input.selectionAllowed === true,
|
||||
selectionAllowed: input.selectionAllowed === true,
|
||||
proposeAllowed: input.proposeAllowed === true,
|
||||
confirmationAllowed: input.confirmationAllowed === true,
|
||||
},
|
||||
inferenceRounds: input.inferenceRounds,
|
||||
effectiveAnswerCount: input.effectiveAnswerCount,
|
||||
plateauRounds: input.plateauRounds,
|
||||
|
||||
@@ -38,6 +38,61 @@ export type EvidenceStopReason =
|
||||
| "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. */
|
||||
@@ -98,8 +153,7 @@ export type DecideRectificationInput = Readonly<{
|
||||
holdoutValidation?: HoldoutValidationStatus;
|
||||
accepted?: boolean;
|
||||
inferenceCredibleRange?: readonly [string, string] | null;
|
||||
engineAcceptAllowed?: boolean;
|
||||
engineProposeAllowed?: boolean;
|
||||
engineCeiling: EngineCapabilityCeiling;
|
||||
datedMethodCollectOpen?: boolean;
|
||||
/** Server-persisted inference counters; never infer these from chat text. */
|
||||
inferenceRounds?: number;
|
||||
@@ -111,28 +165,57 @@ export type DecideRectificationInput = Readonly<{
|
||||
userUncertaintyHigh?: boolean;
|
||||
}>;
|
||||
|
||||
function evidenceStopReason(
|
||||
function classifyStop(
|
||||
input: DecideRectificationInput,
|
||||
separation: CandidateSeparation,
|
||||
): EvidenceStopReason | null {
|
||||
if (separation.ranked.length === 0) return null;
|
||||
): StopClass | null {
|
||||
if (
|
||||
input.datedEventCount !== undefined
|
||||
&& input.datedEventCount < MIN_STANDALONE_DATED_EVENTS
|
||||
) {
|
||||
return "insufficient_dated_events";
|
||||
return { kind: "keep_collecting", reason: "insufficient_dated_events" };
|
||||
}
|
||||
if (
|
||||
input.datedDomainCount !== undefined
|
||||
&& input.datedDomainCount < MIN_STANDALONE_DATED_DOMAINS
|
||||
) {
|
||||
return "insufficient_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" };
|
||||
}
|
||||
if (separation.tiedForFirst) return "tied_first";
|
||||
if (input.userUncertaintyHigh === true) return "user_uncertainty_too_high";
|
||||
return null;
|
||||
}
|
||||
|
||||
function deliveryCapability(input: {
|
||||
stopClass: StopClass | null;
|
||||
separation: CandidateSeparation;
|
||||
holdout: HoldoutValidationStatus;
|
||||
engineCeiling: EngineCapabilityCeiling;
|
||||
confirmationAllowed: boolean;
|
||||
coverageBlocks: boolean;
|
||||
}): DeliveryCapability {
|
||||
const locallySelectable = input.separation.ranked.length > 0
|
||||
&& !input.coverageBlocks
|
||||
&& input.stopClass?.kind !== "keep_collecting"
|
||||
&& input.stopClass?.kind !== "exhausted"
|
||||
&& input.separation.sufficient
|
||||
&& input.holdout === "passed";
|
||||
return {
|
||||
canAdopt: locallySelectable && input.engineCeiling.acceptanceAllowed,
|
||||
selectionAllowed: locallySelectable && input.engineCeiling.selectionAllowed,
|
||||
proposeAllowed: locallySelectable && input.engineCeiling.proposeAllowed,
|
||||
canConfirmExactMinute: locallySelectable
|
||||
&& input.confirmationAllowed
|
||||
&& input.engineCeiling.confirmationAllowed,
|
||||
};
|
||||
}
|
||||
|
||||
export function decideRectification(input: DecideRectificationInput): RectificationDecision {
|
||||
const separation = evaluateCandidateSeparation(input.candidateScores);
|
||||
const probe = input.discriminatorProbe ?? null;
|
||||
@@ -144,9 +227,63 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
?? (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,
|
||||
coverageBlocks,
|
||||
});
|
||||
|
||||
const stopReason = evidenceStopReason(input, separation);
|
||||
|
||||
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);
|
||||
}
|
||||
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,
|
||||
@@ -154,7 +291,7 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
holdout,
|
||||
range,
|
||||
probe: null,
|
||||
canConfirmExactMinute: confirmationAllowed,
|
||||
capability,
|
||||
});
|
||||
}
|
||||
if (confirmationAllowed) {
|
||||
@@ -164,78 +301,20 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
holdout,
|
||||
range,
|
||||
probe: null,
|
||||
canConfirmExactMinute: true,
|
||||
capability,
|
||||
});
|
||||
}
|
||||
if (userStopped && input.candidateScores.length > 0) {
|
||||
return completeWithRange(separation, holdout, range, "user_stopped");
|
||||
}
|
||||
if (input.snapshotCurrent === false) {
|
||||
if (probe && !userStopped && input.trainingGateOpen !== false) {
|
||||
return discriminateOrExhaust(
|
||||
input,
|
||||
separation,
|
||||
holdout,
|
||||
range,
|
||||
probe,
|
||||
);
|
||||
}
|
||||
if (!(userStopped && input.candidateScores.length > 0)) {
|
||||
return collect(separation, holdout, range, probe);
|
||||
}
|
||||
}
|
||||
if (stopReason) {
|
||||
return completeWithRange(separation, holdout, range, "exhausted", stopReason);
|
||||
}
|
||||
const canDiscriminateDespiteCoverage = Boolean(probe)
|
||||
&& !separation.sufficient
|
||||
&& input.trainingGateOpen !== false;
|
||||
const coverageBlocks = (!input.methodCoverageAll && !canDiscriminateDespiteCoverage)
|
||||
|| input.trainingGateOpen === false;
|
||||
if (coverageBlocks) {
|
||||
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 (!separation.sufficient) {
|
||||
if (probe && !userStopped) {
|
||||
return discriminateOrExhaust(input, separation, holdout, range, probe);
|
||||
}
|
||||
return completeWithRange(separation, holdout, range, userStopped ? "user_stopped" : "offer");
|
||||
}
|
||||
if (holdout === "not_started" && !userStopped) {
|
||||
return holdoutValidation(separation, range);
|
||||
if (holdout === "not_started") {
|
||||
return holdoutValidation(separation, range, capability);
|
||||
}
|
||||
if (holdout === "failed") {
|
||||
if (probe && !userStopped) {
|
||||
return discriminateOrExhaust(input, separation, holdout, range, probe);
|
||||
if (probe) {
|
||||
return discriminateOrExhaust(input, separation, holdout, range, probe, capability);
|
||||
}
|
||||
return completeWithRange(separation, holdout, range, "exhausted");
|
||||
return completeWithRange(separation, holdout, range, "exhausted", capability);
|
||||
}
|
||||
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 offerRangeWithoutAdopt(separation, holdout, range, capability);
|
||||
}
|
||||
return finish("adopt_representative", {
|
||||
input,
|
||||
@@ -243,7 +322,7 @@ export function decideRectification(input: DecideRectificationInput): Rectificat
|
||||
holdout,
|
||||
range,
|
||||
probe: null,
|
||||
canConfirmExactMinute: false,
|
||||
capability,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -259,11 +338,13 @@ function discriminateOrExhaust(
|
||||
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");
|
||||
return completeWithRange(separation, holdout, range, "exhausted", capability, stopReason);
|
||||
}
|
||||
return discriminate(separation, holdout, range, probe);
|
||||
return discriminate(separation, holdout, range, probe, capability, stopReason);
|
||||
}
|
||||
|
||||
function collect(
|
||||
@@ -271,6 +352,8 @@ function collect(
|
||||
holdout: HoldoutValidationStatus,
|
||||
range: readonly [string, string] | null,
|
||||
probe: CandidateDiscriminatorProbe | null,
|
||||
capability: DeliveryCapability,
|
||||
stopReason: EvidenceStopReason | null = null,
|
||||
): RectificationDecision {
|
||||
return {
|
||||
phase: "event_collection",
|
||||
@@ -278,10 +361,7 @@ function collect(
|
||||
sessionOutcome: "collect_evidence",
|
||||
resultStatus: "insufficient_evidence",
|
||||
canOfferRange: false,
|
||||
canAdopt: false,
|
||||
canConfirmExactMinute: false,
|
||||
selectionAllowed: false,
|
||||
proposeAllowed: false,
|
||||
...capability,
|
||||
precisionStage: "collect_events",
|
||||
activeFocusPolicy: "keep",
|
||||
completionStatus: null,
|
||||
@@ -292,6 +372,7 @@ function collect(
|
||||
probe,
|
||||
holdoutValidation: holdout,
|
||||
droppedProbes: [],
|
||||
stopReason,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -299,6 +380,7 @@ function offerRangeWithoutAdopt(
|
||||
separation: CandidateSeparation,
|
||||
holdout: HoldoutValidationStatus,
|
||||
range: readonly [string, string] | null,
|
||||
capability: DeliveryCapability,
|
||||
): RectificationDecision {
|
||||
return {
|
||||
phase: "discrimination",
|
||||
@@ -306,10 +388,7 @@ function offerRangeWithoutAdopt(
|
||||
sessionOutcome: "provisional_range",
|
||||
resultStatus: "insufficient_evidence",
|
||||
canOfferRange: true,
|
||||
canAdopt: false,
|
||||
canConfirmExactMinute: false,
|
||||
selectionAllowed: false,
|
||||
proposeAllowed: false,
|
||||
...capability,
|
||||
precisionStage: "theme_refine",
|
||||
activeFocusPolicy: "close",
|
||||
completionStatus: null,
|
||||
@@ -361,6 +440,8 @@ function discriminate(
|
||||
holdout: HoldoutValidationStatus,
|
||||
range: readonly [string, string] | null,
|
||||
probe: CandidateDiscriminatorProbe,
|
||||
capability: DeliveryCapability,
|
||||
stopReason: EvidenceStopReason | null = null,
|
||||
): RectificationDecision {
|
||||
return {
|
||||
phase: "discrimination",
|
||||
@@ -368,10 +449,7 @@ function discriminate(
|
||||
sessionOutcome: "discriminate_candidates",
|
||||
resultStatus: "discriminating",
|
||||
canOfferRange: false,
|
||||
canAdopt: false,
|
||||
canConfirmExactMinute: false,
|
||||
selectionAllowed: false,
|
||||
proposeAllowed: false,
|
||||
...capability,
|
||||
precisionStage: "theme_refine",
|
||||
activeFocusPolicy: "keep",
|
||||
completionStatus: null,
|
||||
@@ -382,12 +460,14 @@ function discriminate(
|
||||
probe,
|
||||
holdoutValidation: holdout,
|
||||
droppedProbes: [],
|
||||
...(stopReason ? { stopReason } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function holdoutValidation(
|
||||
separation: CandidateSeparation,
|
||||
range: readonly [string, string] | null,
|
||||
capability: DeliveryCapability,
|
||||
): RectificationDecision {
|
||||
return {
|
||||
phase: "holdout_validation",
|
||||
@@ -395,10 +475,7 @@ function holdoutValidation(
|
||||
sessionOutcome: "validate_holdout",
|
||||
resultStatus: "discriminating",
|
||||
canOfferRange: false,
|
||||
canAdopt: false,
|
||||
canConfirmExactMinute: false,
|
||||
selectionAllowed: false,
|
||||
proposeAllowed: false,
|
||||
...capability,
|
||||
precisionStage: "theme_refine",
|
||||
activeFocusPolicy: "keep",
|
||||
completionStatus: null,
|
||||
@@ -417,6 +494,7 @@ function completeWithRange(
|
||||
holdout: HoldoutValidationStatus,
|
||||
range: readonly [string, string] | null,
|
||||
kind: "user_stopped" | "offer" | "exhausted",
|
||||
capability: DeliveryCapability,
|
||||
stopReason: EvidenceStopReason | null = null,
|
||||
): RectificationDecision {
|
||||
const userStopped = kind === "user_stopped";
|
||||
@@ -433,10 +511,7 @@ function completeWithRange(
|
||||
sessionOutcome,
|
||||
resultStatus: "completed_with_range",
|
||||
canOfferRange: true,
|
||||
canAdopt: true,
|
||||
canConfirmExactMinute: false,
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
...capability,
|
||||
precisionStage: "ready_to_adopt",
|
||||
activeFocusPolicy: "close",
|
||||
completionStatus: userStopped ? "provisional_range_user_stopped" : null,
|
||||
@@ -460,17 +535,17 @@ function finish(
|
||||
holdout: HoldoutValidationStatus;
|
||||
range: readonly [string, string] | null;
|
||||
probe: CandidateDiscriminatorProbe | null;
|
||||
canConfirmExactMinute: boolean;
|
||||
capability: DeliveryCapability;
|
||||
},
|
||||
): RectificationDecision {
|
||||
const completionStatus: CompletionStatus | null = input.canConfirmExactMinute && input.input.accepted
|
||||
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.canConfirmExactMinute
|
||||
const kind = input.capability.canConfirmExactMinute
|
||||
? (input.input.accepted ? "exact_minute_confirmed" : "awaiting_confirmation")
|
||||
: input.holdout === "passed"
|
||||
? "validated_range"
|
||||
@@ -483,10 +558,7 @@ function finish(
|
||||
? "converged"
|
||||
: "completed_with_range",
|
||||
canOfferRange: true,
|
||||
canAdopt: true,
|
||||
canConfirmExactMinute: input.canConfirmExactMinute,
|
||||
selectionAllowed: true,
|
||||
proposeAllowed: true,
|
||||
...input.capability,
|
||||
precisionStage: "ready_to_adopt",
|
||||
activeFocusPolicy: "close",
|
||||
completionStatus,
|
||||
|
||||
@@ -679,7 +679,7 @@ ${nextInterview.hostNarration}`
|
||||
nextInterviewPersisted = true;
|
||||
}
|
||||
}
|
||||
const adoptionNarration = nextAction.type === "offer_provisional_range" && nextAction.can_adopt
|
||||
const adoptionNarration = nextAction.can_offer_range && nextAction.can_adopt
|
||||
? `${nonConvergingRangeNarration({
|
||||
credibleRange: nextAction.credible_range,
|
||||
representativeTime: nextAction.representative_time,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "../core/candidate-contrast-packet.ts";
|
||||
import {
|
||||
decideRectification,
|
||||
engineCapabilityCeilingFromReceipt,
|
||||
publicDecisionFields,
|
||||
type HoldoutValidationStatus,
|
||||
type RectificationDecision,
|
||||
@@ -522,9 +523,7 @@ export function decideFromDossier(
|
||||
holdoutValidation: holdoutStatusFromInference(inference, oosBlindPrompts),
|
||||
accepted: Boolean(dossier.case.acceptedTime),
|
||||
inferenceCredibleRange: inference?.credible_range ?? null,
|
||||
engineAcceptAllowed: latest?.selectionAllowed === true
|
||||
|| latest?.decisionReceipt?.accept_allowed === true,
|
||||
engineProposeAllowed: latest?.decisionReceipt?.propose_allowed === true,
|
||||
engineCeiling: engineCapabilityCeilingFromReceipt(latest?.decisionReceipt ?? null),
|
||||
datedMethodCollectOpen: datedMethodCollectOpen(collecting.methods),
|
||||
...decisionBudget,
|
||||
...evidenceStops,
|
||||
@@ -553,6 +552,7 @@ export function decideAfterInferenceChange(input: {
|
||||
trainingGateOpen: trainingScoreableGate(input.dossier.evidence).open,
|
||||
candidateScores: [],
|
||||
userStopped: input.userStopped,
|
||||
engineCeiling: engineCapabilityCeilingFromReceipt(input.dossier.latestResult?.decisionReceipt ?? null),
|
||||
...decisionBudgetFromInference(null),
|
||||
...evidenceStops,
|
||||
});
|
||||
@@ -593,9 +593,7 @@ export function decideAfterInferenceChange(input: {
|
||||
inferenceCredibleRange: input.state.credible_range,
|
||||
userStopped: input.userStopped,
|
||||
accepted: Boolean(input.dossier.case.acceptedTime),
|
||||
engineAcceptAllowed: input.dossier.latestResult?.selectionAllowed === true
|
||||
|| input.dossier.latestResult?.decisionReceipt?.accept_allowed === true,
|
||||
engineProposeAllowed: input.dossier.latestResult?.decisionReceipt?.propose_allowed === true,
|
||||
engineCeiling: engineCapabilityCeilingFromReceipt(input.dossier.latestResult?.decisionReceipt ?? null),
|
||||
datedMethodCollectOpen: datedMethodCollectOpen(collecting.methods),
|
||||
...decisionBudgetFromInference(input.state),
|
||||
...evidenceStops,
|
||||
@@ -616,9 +614,22 @@ export function overlayPublicDecision<T extends object>(
|
||||
const fields = publicDecisionFields(decision);
|
||||
const projection = authoritativeCandidateProjection(snapshot as DecisionDossier["latestResult"]);
|
||||
const inconsistent = !projection.consistent;
|
||||
const ceiling = engineCapabilityCeilingFromReceipt(
|
||||
(snapshot as DecisionDossier["latestResult"])?.decisionReceipt ?? null,
|
||||
);
|
||||
const canAdopt = !inconsistent && fields.can_adopt && ceiling.acceptanceAllowed;
|
||||
const selectionAllowed = !inconsistent && fields.selection_allowed && ceiling.selectionAllowed;
|
||||
const proposeAllowed = !inconsistent && fields.propose_allowed && ceiling.proposeAllowed;
|
||||
const canConfirmExactMinute = !inconsistent
|
||||
&& fields.can_confirm_exact_minute
|
||||
&& ceiling.confirmationAllowed;
|
||||
return {
|
||||
...snapshot,
|
||||
...fields,
|
||||
can_adopt: canAdopt,
|
||||
selection_allowed: selectionAllowed,
|
||||
propose_allowed: proposeAllowed,
|
||||
can_confirm_exact_minute: canConfirmExactMinute,
|
||||
...(projection.fromInference
|
||||
? {
|
||||
candidates: projection.candidates,
|
||||
@@ -628,8 +639,7 @@ export function overlayPublicDecision<T extends object>(
|
||||
credible_range: projection.credibleRange,
|
||||
}
|
||||
: {}),
|
||||
...(inconsistent ? { can_adopt: false, selection_allowed: false } : {}),
|
||||
selectionAllowed: inconsistent ? false : fields.selection_allowed,
|
||||
selectionAllowed,
|
||||
validated: fields.validated,
|
||||
completionStatus: fields.completion_status,
|
||||
};
|
||||
|
||||
@@ -70,7 +70,9 @@ export type V9EngineScoreResult = Readonly<{
|
||||
candidates: readonly V9EngineCandidate[];
|
||||
overallConfidence: "low" | "medium" | "high";
|
||||
marginPercent: number | null;
|
||||
acceptanceAllowed: boolean;
|
||||
selectionAllowed: boolean;
|
||||
proposeAllowed: boolean;
|
||||
confirmationAllowed: boolean;
|
||||
representativeCandidateId: string | null;
|
||||
representativeTime: string | null;
|
||||
@@ -237,7 +239,9 @@ type ParsedReceipt = Readonly<{
|
||||
raw: V9DecisionReceipt;
|
||||
eventContractVersion: string;
|
||||
policyVersion: string;
|
||||
acceptanceAllowed: boolean;
|
||||
selectionAllowed: boolean;
|
||||
proposeAllowed: boolean;
|
||||
confirmationAllowed: boolean;
|
||||
representativeCandidateId: string | null;
|
||||
representativeTime: string | null;
|
||||
@@ -267,6 +271,7 @@ function readDecisionReceipt(value: unknown, candidates: readonly V9EngineCandid
|
||||
|| typeof row.display_allowed !== "boolean"
|
||||
|| typeof row.selection_allowed !== "boolean"
|
||||
|| typeof row.acceptance_allowed !== "boolean"
|
||||
|| typeof row.propose_allowed !== "boolean"
|
||||
|| typeof row.confirmation_allowed !== "boolean"
|
||||
|| typeof row.accept_allowed !== "boolean"
|
||||
|| typeof row.confirm_allowed !== "boolean"
|
||||
@@ -288,7 +293,6 @@ function readDecisionReceipt(value: unknown, candidates: readonly V9EngineCandid
|
||||
|| (representativeCandidateId !== null && (!representative || representative.time !== representativeTime))
|
||||
|| (row.confirmation_allowed === true && row.selection_allowed !== true)
|
||||
|| (row.selection_allowed === true && row.display_allowed !== true)
|
||||
|| (row.propose_allowed !== undefined && typeof row.propose_allowed !== "boolean")
|
||||
|| (row.propose_allowed === true && row.selection_allowed !== true)
|
||||
|| !questionContractVersionIsCompatible(row.question_contract_version ?? row.question_contract)
|
||||
) {
|
||||
@@ -298,7 +302,9 @@ function readDecisionReceipt(value: unknown, candidates: readonly V9EngineCandid
|
||||
raw: row,
|
||||
eventContractVersion: EVENT_CONTRACT_VERSION,
|
||||
policyVersion: row.policy_version,
|
||||
acceptanceAllowed: row.acceptance_allowed,
|
||||
selectionAllowed: row.selection_allowed,
|
||||
proposeAllowed: row.propose_allowed,
|
||||
confirmationAllowed: row.confirmation_allowed,
|
||||
representativeCandidateId,
|
||||
representativeTime,
|
||||
@@ -527,7 +533,9 @@ export async function runV9CandidateScore(input: {
|
||||
candidates,
|
||||
overallConfidence: receipt.overallConfidence,
|
||||
marginPercent: receipt.marginPercent,
|
||||
acceptanceAllowed: receipt.acceptanceAllowed,
|
||||
selectionAllowed: receipt.selectionAllowed,
|
||||
proposeAllowed: receipt.proposeAllowed,
|
||||
confirmationAllowed: receipt.confirmationAllowed,
|
||||
representativeCandidateId: receipt.representativeCandidateId,
|
||||
representativeTime: receipt.representativeTime,
|
||||
|
||||
@@ -1139,8 +1139,14 @@ export function decideConversationalSession(input: {
|
||||
: discriminatorFromFollowup(input.nextFollowup),
|
||||
holdoutValidation: input.holdoutValidation,
|
||||
accepted: input.accepted,
|
||||
engineAcceptAllowed: input.selectionAllowed,
|
||||
engineProposeAllowed: input.proposeAllowed,
|
||||
// This helper is not used by the production V9 delivery path; keep its
|
||||
// delivery ceiling explicit without coupling it to question selection.
|
||||
engineCeiling: {
|
||||
acceptanceAllowed: input.selectionAllowed === true,
|
||||
selectionAllowed: input.selectionAllowed === true,
|
||||
proposeAllowed: input.proposeAllowed === true,
|
||||
confirmationAllowed: input.confirmationAllowed === true,
|
||||
},
|
||||
datedMethodCollectOpen: input.methods ? datedMethodCollectOpen(input.methods) : undefined,
|
||||
inferenceRounds: input.inferenceRounds,
|
||||
effectiveAnswerCount: input.effectiveAnswerCount,
|
||||
|
||||
Reference in New Issue
Block a user