265 lines
9.6 KiB
TypeScript
265 lines
9.6 KiB
TypeScript
/**
|
|
* Read-only confirmation-gate projection.
|
|
*
|
|
* These blockers are fail-closed honesty, not a scoring change. They never
|
|
* grant a unique birth minute. VedAstro `not_evaluated` means the official
|
|
* minute-sensitive layer has not run; that is not a fail. The sealed public
|
|
* AA holdout stays `not_ready` until a human updates this contract after a
|
|
* valid passing evaluation — this module does not invent cases.
|
|
*/
|
|
|
|
import { RECTIFICATION_POLICY } from "../../rectification-policy.ts";
|
|
import { indistinguishableWidthMinutes } from "./candidate-plateau.ts";
|
|
import sealedHoldout from "../../../../../references/rectification_sealed_holdout.v1.json";
|
|
|
|
export const SEALED_MINUTE_HOLDOUT = {
|
|
sealed_benchmark_id: sealedHoldout.sealed_benchmark_id,
|
|
status: sealedHoldout.status === "ready" ? "ready" : "not_ready",
|
|
valid_public_aa_cases: sealedHoldout.valid_public_aa_cases,
|
|
required_cases: sealedHoldout.required_cases,
|
|
top_1_rate: sealedHoldout.top_1_rate,
|
|
confirmation_coverage_rate: sealedHoldout.confirmation_coverage_rate,
|
|
} as const;
|
|
|
|
type GateCandidate = Readonly<{
|
|
time: string;
|
|
rank: number;
|
|
tiedMinuteCount: number;
|
|
}>;
|
|
|
|
export type ConfirmationGateBlocker = Readonly<{
|
|
id: "vedastro_minute_sensitive" | "adjacent_minutes_indistinguishable" | "public_aa_holdout";
|
|
status: string;
|
|
user_meaning: string;
|
|
indistinguishable_width_minutes?: number;
|
|
max_confirmation_width_minutes?: number;
|
|
required_cases?: number;
|
|
valid_public_aa_cases?: number;
|
|
top_1_rate?: number;
|
|
confirmation_coverage_rate?: number;
|
|
sealed_benchmark_id?: string;
|
|
failure_code?: string;
|
|
}>;
|
|
|
|
export type UniqueMinutePath = "closed_at_representative" | "awaiting_user_consent";
|
|
|
|
export function uniqueMinutePath(confirmationAllowed: boolean): UniqueMinutePath {
|
|
return confirmationAllowed ? "awaiting_user_consent" : "closed_at_representative";
|
|
}
|
|
|
|
export type ConfirmationGate = Readonly<{
|
|
confirmation_allowed: boolean;
|
|
unique_minute_path: UniqueMinutePath;
|
|
blockers: readonly ConfirmationGateBlocker[];
|
|
}>;
|
|
|
|
export type SessionOutcomeKind =
|
|
| "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 SessionOutcome = Readonly<{
|
|
kind: SessionOutcomeKind;
|
|
user_meaning: string;
|
|
}>;
|
|
|
|
export function sessionOutcomeView(kind: SessionOutcomeKind): SessionOutcome {
|
|
if (kind === "awaiting_confirmation") {
|
|
return {
|
|
kind,
|
|
user_meaning: "确认门已允许。只有用户明确同意才能写已确认校正时间。",
|
|
};
|
|
}
|
|
if (kind === "adopt_representative") {
|
|
return {
|
|
kind,
|
|
user_meaning: "可以采用代表性候选作当前排盘;不得把它描述为已确认的唯一出生分钟。",
|
|
};
|
|
}
|
|
if (kind === "discriminate_candidates") {
|
|
return {
|
|
kind,
|
|
user_meaning: "方法资料已齐,但候选还没拉开。下一步按候选盘面差异反问前事,不要采用时间卡。",
|
|
};
|
|
}
|
|
if (kind === "validate_holdout") {
|
|
return {
|
|
kind,
|
|
user_meaning: "领先候选还要用尚未计分的前事做独立核对,不能直接采用。",
|
|
};
|
|
}
|
|
if (kind === "validated_range") {
|
|
return {
|
|
kind,
|
|
user_meaning: "领先候选已通过尚未计分的前事核对。本会话交付可信区间和代表性工作时间,不是唯一分钟确认。",
|
|
};
|
|
}
|
|
if (kind === "exact_minute_confirmed") {
|
|
return {
|
|
kind,
|
|
user_meaning: "确认门已允许。只有用户明确同意才能写已确认校正时间。",
|
|
};
|
|
}
|
|
if (kind === "provisional_range") {
|
|
return {
|
|
kind,
|
|
user_meaning: "当前几个候选基本并列。给出的是可信区间的代表点,不是已经分出的赢家。",
|
|
};
|
|
}
|
|
if (kind === "provisional_range_user_stopped") {
|
|
return {
|
|
kind,
|
|
user_meaning: "用户主动停止收集。当前可信区间和代表性工作时间可以交付,独立核对尚未完成,不是最终校正结果。",
|
|
};
|
|
}
|
|
if (kind === "completed_with_range") {
|
|
return {
|
|
kind,
|
|
user_meaning: "当前证据只能支持可信区间和代表性工作时间,不能确认唯一分钟。",
|
|
};
|
|
}
|
|
return {
|
|
kind: "collect_evidence",
|
|
user_meaning: "还需要能评分的带日期事件,才能给出可采用的代表性时间。",
|
|
};
|
|
}
|
|
|
|
export function sessionOutcomeFromGate(input: {
|
|
selectionAllowed: boolean;
|
|
proposeAllowed?: boolean;
|
|
confirmationAllowed: boolean;
|
|
interviewOpen?: boolean;
|
|
userStopped?: boolean;
|
|
}): SessionOutcome {
|
|
if (input.confirmationAllowed) return sessionOutcomeView("awaiting_confirmation");
|
|
const userStopped = input.userStopped === true;
|
|
const interviewOpen = input.interviewOpen === true && !userStopped;
|
|
if (userStopped && input.selectionAllowed) {
|
|
return sessionOutcomeView("provisional_range_user_stopped");
|
|
}
|
|
if (input.proposeAllowed === true && !interviewOpen) {
|
|
return sessionOutcomeView("adopt_representative");
|
|
}
|
|
return sessionOutcomeView("collect_evidence");
|
|
}
|
|
|
|
function asRecord(value: unknown): Readonly<Record<string, unknown>> | null {
|
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
? value as Readonly<Record<string, unknown>>
|
|
: null;
|
|
}
|
|
|
|
export function readVedastroMinuteSensitiveStatus(
|
|
decisionReceipt: Readonly<Record<string, unknown>> | null | undefined,
|
|
): string {
|
|
const gates = asRecord(decisionReceipt?.gates);
|
|
const exact = asRecord(gates?.exact_confirmation);
|
|
const nested = exact?.external_validation_status;
|
|
if (typeof nested === "string" && nested.trim()) return nested.trim();
|
|
const top = decisionReceipt?.external_validation_status;
|
|
if (typeof top === "string" && top.trim()) return top.trim();
|
|
return "not_evaluated";
|
|
}
|
|
|
|
function readVedastroFailureCode(
|
|
decisionReceipt: Readonly<Record<string, unknown>> | null | undefined,
|
|
): string | null {
|
|
const gates = asRecord(decisionReceipt?.gates);
|
|
const exact = asRecord(gates?.exact_confirmation);
|
|
const validation = asRecord(exact?.vedastro_event_validation);
|
|
const failure = asRecord(validation?.failure);
|
|
return typeof failure?.code === "string" && failure.code.trim() ? failure.code.trim() : null;
|
|
}
|
|
|
|
function vedastroPassed(status: string): boolean {
|
|
return status === "passed";
|
|
}
|
|
|
|
function vedastroUserMeaning(status: string, failureCode: string | null): string {
|
|
if (status === "not_evaluated") {
|
|
return "分钟敏感校验尚未执行;缺少该验证时不能确认唯一分钟。";
|
|
}
|
|
if (status === "passed") {
|
|
return "分钟敏感校验已通过。";
|
|
}
|
|
if (failureCode) return `分钟敏感校验执行失败(${failureCode}),不能确认唯一分钟。`;
|
|
return "分钟敏感校验未能区分相邻分钟,不能确认唯一分钟。";
|
|
}
|
|
|
|
function adjacentPassed(candidates: readonly GateCandidate[], widthMinutes: number): boolean {
|
|
if (candidates.length === 0) return false;
|
|
const top = [...candidates].sort((left, right) => left.rank - right.rank)[0];
|
|
return Boolean(top)
|
|
&& top.tiedMinuteCount === 1
|
|
&& widthMinutes <= RECTIFICATION_POLICY.maxConfirmationWidthMinutes;
|
|
}
|
|
|
|
function holdoutPassed(): boolean {
|
|
const holdout: {
|
|
status: string;
|
|
valid_public_aa_cases: number;
|
|
required_cases: number;
|
|
confirmation_coverage_rate: number;
|
|
} = SEALED_MINUTE_HOLDOUT;
|
|
return holdout.status === "ready"
|
|
&& holdout.valid_public_aa_cases >= holdout.required_cases
|
|
&& holdout.confirmation_coverage_rate > 0;
|
|
}
|
|
|
|
export function buildConfirmationGate(input: {
|
|
engineConfirmationAllowed: boolean;
|
|
candidates: readonly GateCandidate[];
|
|
decisionReceipt?: Readonly<Record<string, unknown>> | null;
|
|
}): ConfirmationGate {
|
|
const width = indistinguishableWidthMinutes(input.candidates);
|
|
const vedastroStatus = readVedastroMinuteSensitiveStatus(input.decisionReceipt);
|
|
const vedastroFailureCode = readVedastroFailureCode(input.decisionReceipt);
|
|
const adjacentOk = adjacentPassed(input.candidates, width);
|
|
const holdoutOk = holdoutPassed();
|
|
const confirmationAllowed = input.engineConfirmationAllowed
|
|
&& vedastroPassed(vedastroStatus)
|
|
&& adjacentOk
|
|
&& holdoutOk;
|
|
|
|
return {
|
|
confirmation_allowed: confirmationAllowed,
|
|
unique_minute_path: uniqueMinutePath(confirmationAllowed),
|
|
blockers: [
|
|
{
|
|
id: "vedastro_minute_sensitive",
|
|
status: vedastroStatus,
|
|
...(vedastroFailureCode ? { failure_code: vedastroFailureCode } : {}),
|
|
user_meaning: vedastroUserMeaning(vedastroStatus, vedastroFailureCode),
|
|
},
|
|
{
|
|
id: "adjacent_minutes_indistinguishable",
|
|
status: adjacentOk ? "passed" : "blocked",
|
|
indistinguishable_width_minutes: width,
|
|
max_confirmation_width_minutes: RECTIFICATION_POLICY.maxConfirmationWidthMinutes,
|
|
user_meaning: adjacentOk
|
|
? "当前宽度未超过确认门上限。"
|
|
: "当前是一段不可分区间。代表分钟只是代表性候选,不是唯一出生分钟。",
|
|
},
|
|
{
|
|
id: "public_aa_holdout",
|
|
status: SEALED_MINUTE_HOLDOUT.status,
|
|
required_cases: SEALED_MINUTE_HOLDOUT.required_cases,
|
|
valid_public_aa_cases: SEALED_MINUTE_HOLDOUT.valid_public_aa_cases,
|
|
top_1_rate: SEALED_MINUTE_HOLDOUT.top_1_rate,
|
|
confirmation_coverage_rate: SEALED_MINUTE_HOLDOUT.confirmation_coverage_rate,
|
|
sealed_benchmark_id: SEALED_MINUTE_HOLDOUT.sealed_benchmark_id,
|
|
user_meaning: holdoutOk
|
|
? "公开密封 holdout 已达发布门槛。"
|
|
: "公开密封 holdout 未达发布门槛,不能确认唯一分钟或声称已完成精确分钟校准。",
|
|
},
|
|
],
|
|
};
|
|
}
|