import { writeFileSync } from "node:fs"; import { checkConfig } from "../../src/config/index.ts"; import type { LedgerRecord } from "../../src/ledger/index.ts"; import { Board, FORWARD_NOT_RUN, type StudyRecord, } from "../../src/materials/index.ts"; import { placement, runBalance } from "../../src/rules/index.ts"; import { verificationOutcome } from "../../src/submissions/index.ts"; import type { Range } from "../../src/vocab/index.ts"; import { finalSubmissions, ofKind } from "./ledger.ts"; export const FORWARD_STOP = "消融还没写(任务书 17)"; export const jsonFile = (path: string, value: unknown): void => writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`); export const frozenConfig = (records: LedgerRecord[]) => checkConfig((ofKind(records, "config")[0]!.content as StudyRecord).config); /** 区间边界只是报告口径,不参与研究决定;失败或测不到分数保持 null。 */ export function scoreBand(score: number | null, range: Range): string | null { if (score === null) return null; return { belowQ1: "q9", }[placement(range, score)]; } /** 核验要对应当时那版代码,不能拿最终修好后的代码替代历史被审代码。 */ export function candidateHistory(records: LedgerRecord[], candidate: string) { const code = ofKind(records, "code").filter( (r) => r.content.object === candidate, ); const submissions = ofKind(records, "submission").filter( (r) => r.content.target === candidate, ); const attempts = submissions.flatMap((r) => { if (r.content.kind !== "implementation" && r.content.kind !== "repair") return []; // flow 在同一事务里先写 submission,紧接着写 code。 return [{ submission: r, code: code.find((c) => c.seq === r.seq + 1)! }]; }); const verifications = submissions.flatMap((r) => r.content.kind === "verification" ? [ { submission: r, code: code.filter((c) => c.seq < r.seq).at(-1)!, outcome: verificationOutcome(r.content.content), }, ] : [], ); return { attempts, verifications }; } /** 程序验收只读记录重算;基线也算正式运行,没启动的结果不扣次。 */ export function forwardAccounts(records: LedgerRecord[]) { const board = new Board(records); const config = frozenConfig(records); const opens = ofKind(records, "stageOpen"); const closes = ofKind(records, "stageClose"); const stages = opens.map((r) => { const closing = closes.filter( (c) => c.content.stageId === r.content.stageId, ); return { ...r.content, openSeq: r.seq, closeSeqs: closing.map((c) => c.seq), pass: closing.length === 1 && closing[0]!.seq > r.seq, }; }); const orphanCloses = closes.filter( (c) => !opens.some((o) => o.content.stageId === c.content.stageId), ); const started = ofKind(records, "runStarted"); const balance = runBalance(config.runs, board.runStates()); const runs = ofKind(records, "runPlanned").map((p) => ({ plan: p, starts: started.filter((s) => s.content.run === p.content.run), results: ofKind(records, "runResult").filter( (r) => r.content.run === p.content.run, ), })); const batch = board.decision("batch", 1); const finals = finalSubmissions(records); const candidates = (batch?.output.chosen ?? []).map((candidate) => { const history = candidateHistory(records, candidate); const events = ofKind(records, "event").filter( (r) => r.content.event === FORWARD_NOT_RUN && r.content.objects.includes(candidate), ); const candidateRuns = runs.filter( (r) => r.plan.content.purpose === "forward" && r.plan.content.round === 1 && r.plan.content.object === candidate, ); const verificationCount = finals.filter( (r) => r.content.target === candidate && r.content.kind === "verification", ).length; const repairCount = finals.filter( (r) => r.content.target === candidate && r.content.kind === "repair", ).length; const lastPassed = history.verifications.at(-1)?.outcome.passed ?? null; const exhausted = lastPassed === false && repairCount >= config.maxRepairs; return { candidate, verificationCount, repairCount, lastPassed, exhausted, notRunEvents: events, started: candidateRuns.some((r) => r.starts.length > 0), runs: candidateRuns, pass: exhausted ? events.length === 1 && candidateRuns.length === 0 : lastPassed === true && candidateRuns.some( (r) => r.starts.length === 1 && r.results.length === 1, ), }; }); const adoption = ofKind(records, "decision").find( (r) => r.content.decision === "adopt" && r.content.round === 1, ) ?? null; const halt = ofKind(records, "event") .filter((r) => r.content.event === "halted") .at(-1) ?? null; const checks = { stagesPaired: stages.every((s) => s.pass) && orphanCloses.length === 0, chargedOnce: balance.charged === started.length && config.runs - balance.balance === started.length && balance.reserved === 0, forwardSerial: runs .filter((r) => r.plan.content.purpose === "forward") .every( (r, i, forwards) => i === 0 || (forwards[i - 1]!.results.length === 1 && forwards[i - 1]!.results[0]!.seq < r.plan.seq), ), runsFinished: runs.length > 0 && runs.every((r) => r.results.length === 1), candidatesFinished: candidates.length > 0 && candidates.every((c) => c.pass), adoptedAndStopped: adoption !== null && halt?.content.reason === FORWARD_STOP && halt.seq === records.at(-1)!.seq, }; return { initial: config.runs, confirmedStarts: started.length, balance, runs, candidates, stages, orphanCloses, adoption, halt, checks, }; }