Files
InfoRSI/eval/lib/forward.ts
T
iomgaa b7be524cdc eval: 准备正向采纳的真实运行恢复与质量评估
新增单份串行启动入口、首个正向运行杀进程恢复证据、逐候选审阅导出与账目统计;参考只读副本分数对照。456项免费测试通过,尚未启动三份真实验收。同步结构说明与当前工作状态。
2026-10-04 04:25:00 -04:00

172 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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: "<q1",
q1ToQ5: "[q1,q5)",
q5ToQ9: "[q5,q9]",
aboveQ9: ">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,
};
}