/** * Task #226 — competition driver 的 in-memory 进程状态单例。 * * 这层故意是 in-memory 而不是 DB:driver 是 single-instance background * worker(整 process 内只允许一个 run 同时进行 — 由 admin /start 端点的 * 单飞锁拦截),状态读取频率高(前端 5s 轮询),DB 走一遍没必要。 * * 当 api-server 重启 → 内存被清,状态会从 'idle' 重新开始;但已落地的 * task_ledger / network_version_metrics / submission_feedback_ledger 仍 * 在 DB,driver 下次手动触发时会按 plan_competition_ 续跑。 * * 路由层用 getCompetitionState / setCompetitionState / patchCompetitionState * 三个口子拿和写,没有别的入口 — 严格封装。 */ export type CompetitionStatus = | "idle" | "running" | "completed" | "failed"; export interface CompetitionRunState { /** 当前 run 的 id,idle 时为 null。 */ runId: string | null; status: CompetitionStatus; startedAt: string | null; finishedAt: string | null; /** 已完成的 task 数(含从 ledger 续跑当 done 算上的)。 */ completed: number; /** 失败 task 数。 */ failed: number; /** 本 run 的总 task 数(loadManifest 后才知道,之前为 0)。 */ total: number; /** 已完成 task 的均值 EF1%(仅 done 行), driver 实时算的。 */ meanEf1Percent: number; /** 完成后 result.zip 的绝对路径, idle/running 时 null。 */ resultZipPath: string | null; /** 飞轮 in-vivo 触发后产生的事件计数(promote+rolledback 等)。 */ flywheelEvents: number; /** 错误信息(failed 状态下非空)。 */ lastError: string | null; /** 一行人类可读 note,UI 用来显示当前在做什么。 */ note: string; } const idle: CompetitionRunState = { runId: null, status: "idle", startedAt: null, finishedAt: null, completed: 0, failed: 0, total: 0, meanEf1Percent: 0, resultZipPath: null, flywheelEvents: 0, lastError: null, note: "", }; let current: CompetitionRunState = { ...idle }; export function getCompetitionState(): CompetitionRunState { return { ...current }; } export function setCompetitionState(next: Partial & { status: CompetitionStatus }): CompetitionRunState { current = { ...idle, ...next }; return { ...current }; } export function patchCompetitionState( patch: Partial, ): CompetitionRunState { current = { ...current, ...patch }; return { ...current }; } export function resetCompetitionStateForTest(): void { current = { ...idle }; }