/** * V9 rectification Case state machine contracts. * * The server owns the authoritative Case status. The browser and the agent * never derive status from message counts, candidate existence or session * ordering. */ import { RECTIFICATION_CASE_STATUSES, RESUMABLE_CASE_STATUSES, TERMINAL_CASE_STATUSES, } from "@/lib/contracts/rectification-entry"; import type { RectificationCaseStatus } from "@/lib/contracts/rectification-entry"; export { RECTIFICATION_CASE_STATUSES, RESUMABLE_CASE_STATUSES, TERMINAL_CASE_STATUSES, }; export type { RectificationCaseStatus }; const RESUMABLE_SET = new Set(RESUMABLE_CASE_STATUSES); const TERMINAL_SET = new Set(TERMINAL_CASE_STATUSES); export function isRectificationCaseStatus( value: unknown, ): value is RectificationCaseStatus { return ( typeof value === "string" && (RESUMABLE_SET.has(value) || TERMINAL_SET.has(value)) ); } export function isResumableStatus( status: RectificationCaseStatus, ): boolean { return RESUMABLE_SET.has(status); } export function isTerminalStatus(status: RectificationCaseStatus): boolean { return TERMINAL_SET.has(status); } /** * Legal terminal transitions. A resumable case may move to any terminal * status; terminal statuses are immutable. */ export function canTransitToTerminal( from: RectificationCaseStatus, to: RectificationCaseStatus, ): boolean { if (isTerminalStatus(from)) return false; return isTerminalStatus(to); } /** statuses that reject evidence/turn writes. Terminal cases are read-only. */ export function evidenceWritesAllowed( status: RectificationCaseStatus, ): boolean { return isResumableStatus(status); } /** * The one-resumable-case-per-user invariant, mirrored from the database * partial unique index. The database is the enforcement point; this is the * contract the service layer relies on. */ export const MAX_RESUMABLE_CASES_PER_USER = 1; export const RECTIFICATION_SKILL_NAME = "jyotish-birth-time-rectification"; // 原值: "10.0.28" / 新值: "10.0.29" / 原因: 年月阶段改口述并禁止追问本人 // 原值: "10.0.29" / 新值: "10.0.30" / 原因: D2 出卡句与 D4 卡上百分比规则写进 Skill(2026-09-26) // 原值: "10.0.30" / 新值: "10.0.31" / 原因: 开场改大白话、题干带例子和示例回答写进 Skill OpeningPolicy(BUG-1049,2026-09-26) // 原值: "10.0.31" / 新值: "10.0.32" / 原因: 训练门开后只问点选卡、交付正文去吻合率写进 Skill(BUG-1084/1085,2026-09-29) // 原值: "10.0.32" / 新值: "10.0.33" / 原因: 持久opt-in盘型交付,nullable历史保持旧身份/方法(BUG-1115~1117) export const RECTIFICATION_SKILL_VERSION = "10.0.33";