import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { DatabaseSync } from "node:sqlite"; import { test } from "node:test"; import { candidateHistory, forwardAccounts, FORWARD_STOP, scoreBand, } from "../eval/lib/forward.ts"; import { forwardAcceptance, sameContainerThroughResult, type StudySummary, } from "../eval/lib/forward-evidence.ts"; import { lightCheckUsage } from "../eval/lib/light-checks.ts"; import { referenceScores } from "../eval/lib/reference-scores.ts"; import { Ledger } from "../src/ledger/index.ts"; import { forwardNotRunEvent } from "../src/materials/index.ts"; import { createRepository, saveVersion } from "../src/workspace/index.ts"; import { fullConfig } from "./config-helpers.ts"; import { definition, exited, implementation, notStarted, proposal, tempDir, } from "./ledger-helpers.ts"; import { judged, protocolOf, Writer } from "./materials-helpers.ts"; const verification = (found: boolean) => ({ clauses: [{ clause: "c1", found, where: "solve.py:1" }], factors: [{ factor: "p1", found, where: "solve.py:1" }], safeguards: [], unassigned: [], body: "逐项核对", }); test("forward 导出/统计:历史代码配对、两次核验一次修复、封存自评与真实分数保持可追溯", (t) => { const root = tempDir(t), dir = join(root, "run"), study = join(dir, "study"); mkdirSync(study, { recursive: true }); const ledger = Ledger.open(join(study, "ledger.sqlite")); t.after(() => ledger.close()); const w = new Writer(ledger), config = fullConfig(); ledger.append("config", { config }); const repo = join(study, "repo.git"), work = join(root, "work"); createRepository(repo); mkdirSync(work); const version = (text: string) => { writeFileSync(join(work, "solve.py"), text); const result = saveVersion(repo, work, []); assert.ok(result.saved); return result.version; }; ledger.append("code", { object: "base", version: version("value = 0\n"), locations: null, }); w.run("base-1", "baseline", 0, null, judged(1, "main")); w.open("proposal", "proposal", 1, ["a"]); const p = proposal({ q1: 1, q5: 2, q9: 3 }, "协议正文"); w.sub("proposal", "a", "proposal", null, "p", p); w.close("proposal"); w.protocol("r1-c1", protocolOf("r1-c1", p, 1, "base", ["p"], ["改value"])); w.open("second", "secondPrediction", 1, ["a"]); w.sub("second", "a", "secondPrediction", "r1-c1", null, { range: { q1: 1, q5: 2, q9: 3 }, body: "估计", }); w.close("second"); w.decision("batch", 1, null, { chosen: ["r1-c1"], candidates: [{ id: "r1-c1", merged: { q1: 1, q5: 2, q9: 3 } }], }); const v1 = version("value = 1\n"), v2 = version("value = 2\n"); w.open("impl", "implementation", 1, ["x"]); w.sub("impl", "x", "implementation", "r1-c1", null, implementation("首稿")); ledger.append("code", { object: "r1-c1", version: v1, locations: null }); w.close("impl"); w.open("verify1", "verification", 1, ["v"]); w.sub("verify1", "v", "verification", "r1-c1", null, verification(false)); w.close("verify1"); w.open("repair", "implementation", 1, ["x"]); w.sub("repair", "x", "repair", "r1-c1", null, implementation("只修value")); ledger.append("code", { object: "r1-c1", version: v2, locations: null }); w.close("repair"); w.open("verify2", "verification", 1, ["v"]); w.sub("verify2", "v", "verification", "r1-c1", null, verification(true)); w.close("verify2"); ledger.append("runPlanned", { run: "forward-1", purpose: "forward", round: 1, object: "r1-c1", definition, }); ledger.append("runStarted", { run: "forward-1" }); const atKill = ledger.records(); writeFileSync(join(dir, "ledger-at-kill.json"), JSON.stringify(atKill)); writeFileSync( join(dir, "kill.json"), JSON.stringify({ run: "forward-1", candidate: "r1-c1", container: "inforsi-test-forward-1", beforeKillSeq: atKill.at(-1)!.seq, }), ); for (const part of ["before", "after-kill", "before-resume"]) writeFileSync( join(dir, `container-${part}.json`), JSON.stringify({ Id: "same-container", State: { Running: true, StartedAt: "2026-10-04T00:00:00Z" }, }), ); ledger.append("runResult", { run: "forward-1", process: exited, judged: judged(2.5, "main"), }); writeFileSync( join(dir, "container-events-window.json"), JSON.stringify({ since: "2026-10-04T00:00:01Z", until: "2026-10-04T00:02:00Z", run: "forward-1", resultSeq: ledger.records().at(-1)!.seq, }), ); const event = (action: string) => JSON.stringify({ Type: "container", Action: action, Actor: { ID: "same-container", Attributes: { name: "inforsi-test-forward-1" }, }, }); writeFileSync(join(dir, "container-events.jsonl"), event("die") + "\n"); w.decision("adopt", 1, null, { adopted: "r1-c1" }); ledger.append("event", { event: "halted", objects: [], reason: FORWARD_STOP, }); const records = ledger.records(), accounts = forwardAccounts(records); assert.deepEqual(accounts.balance, { charged: 2, reserved: 0, balance: config.runs - 2, }); assert.ok(Object.values(accounts.checks).every(Boolean)); assert.equal(accounts.candidates[0]!.verificationCount, 2); assert.equal(accounts.candidates[0]!.repairCount, 1); assert.deepEqual( candidateHistory(records, "r1-c1").verifications.map( (v) => v.code.content.version, ), [v1, v2], ); const exported = join(root, "export"); const exportResult = spawnSync( process.execPath, ["eval/forward/export.ts", study, exported], { encoding: "utf8" }, ); assert.equal(exportResult.status, 0, exportResult.stderr); assert.equal( readFileSync(join(exported, "r1-c1/final/solve.py"), "utf8"), "value = 2\n", ); assert.match( readFileSync(join(exported, "r1-c1/attempts.json"), "utf8"), /只修value/, ); assert.match( readFileSync(join(exported, "r1-c1/final-implementation.json"), "utf8"), /concerns/, ); const refs = join(root, "refs"); mkdirSync(refs); for (const name of [ "16-real-1", "15-real-1", "15-tight-2", "14-real-1", "12-real-2", ]) referenceFixture(join(refs, name)); const statsOut = join(root, "stats"); const statsResult = spawnSync( process.execPath, ["eval/forward/stats.ts", statsOut, refs, dir], { encoding: "utf8" }, ); assert.equal(statsResult.status, 0, statsResult.stderr); assert.match(statsResult.stdout, /逐研究检查通过.*不是任务书16总体验收/); const scores = JSON.parse( readFileSync(join(statsOut, "predictions-scores.json"), "utf8"), ); assert.deepEqual(scores[0].authorEstimates[0].range, { q1: 1, q5: 2, q9: 3 }); assert.equal(scores[0].runs[0].measuredMinusQ5, 0.5); assert.equal(scores[0].runs[0].band, "[q5,q9]"); const statistics = JSON.parse( readFileSync(join(statsOut, "stats.json"), "utf8"), ); assert.deepEqual(statistics[0].kill.checks, { sameContainer: true, chargedOnceNoRerun: true, resultAndContinued: true, }); // 三份早期快照完全一致,之后同一容器重启仍必须使验收失败。 writeFileSync( join(dir, "container-events.jsonl"), [event("die"), event("start"), event("restart"), event("die")].join("\n"), ); const badOut = join(root, "bad-stats"); const bad = spawnSync( process.execPath, ["eval/forward/stats.ts", badOut, refs, dir], { encoding: "utf8" }, ); assert.equal(bad.status, 1, bad.stderr); assert.equal( JSON.parse(readFileSync(join(badOut, "stats.json"), "utf8"))[0].kill.checks .sameContainer, false, ); rmSync(join(dir, "kill.json")); const singleOut = join(root, "single-without-kill"); const single = spawnSync( process.execPath, ["eval/forward/stats.ts", singleOut, refs, dir], { encoding: "utf8" }, ); assert.equal(single.status, 0, single.stderr); assert.match( single.stdout, /所给 1 份研究的逐研究检查通过.*不是任务书16总体验收/, ); assert.equal( JSON.parse(readFileSync(join(singleOut, "stats.json"), "utf8"))[0].kill, null, ); }); test("forward 账目:没启动退还、修复用尽不扣次;缺不跑事件或结果不能通过", (t) => { const ledger = Ledger.open(join(tempDir(t), "ledger.sqlite")); t.after(() => ledger.close()); const w = new Writer(ledger), config = { ...fullConfig(), maxRepairs: 0 }; ledger.append("config", { config }); ledger.append("runPlanned", { run: "base-0", purpose: "baseline", round: 0, object: null, definition, }); ledger.append("runResult", { run: "base-0", process: notStarted, judged: null, }); w.run("base-1", "baseline", 0, null, judged(1, "main")); w.decision("batch", 1, null, { chosen: ["c"] }); ledger.append("code", { object: "c", version: "v", locations: null }); w.open("v", "verification", 1, ["v"]); w.sub("v", "v", "verification", "c", null, verification(false)); w.close("v"); assert.equal(forwardAccounts(ledger.records()).candidates[0]!.pass, false); ledger.append("event", forwardNotRunEvent("c", "修复用尽")); w.decision("adopt", 1, null, { adopted: null }); ledger.append("event", { event: "halted", reason: FORWARD_STOP, objects: [], }); const accounts = forwardAccounts(ledger.records()); assert.deepEqual(accounts.balance, { charged: 1, reserved: 0, balance: config.runs - 1, }); assert.ok(Object.values(accounts.checks).every(Boolean)); ledger.append("runPlanned", { run: "unfinished", purpose: "forward", round: 1, object: "c", definition, }); ledger.append("runStarted", { run: "unfinished" }); assert.equal(forwardAccounts(ledger.records()).checks.runsFinished, false); assert.equal(forwardAccounts(ledger.records()).candidates[0]!.pass, false); }); test("forward 分位区间不把无效分数当零,三条边界按既有placement", () => { assert.deepEqual( [null, 0, 1, 2, 3, 4].map((s) => scoreBand(s, { q1: 1, q5: 2, q9: 3 })), [null, "q9"], ); }); test("forward 轻量检查:跨会话按工具编号配对,拒绝超限不算执行,缺结果不伪装零次", (t) => { const dir = tempDir(t); const request = (id: string) => ({ type: "message", message: { role: "assistant", content: [{ type: "toolCall", name: "research_check", id }], }, }); const result = (id: string, text: string) => ({ type: "message", message: { role: "toolResult", toolName: "research_check", toolCallId: id, content: [{ type: "text", text }], }, }); writeFileSync(join(dir, "1.jsonl"), JSON.stringify(request("a")) + "\n"); writeFileSync( join(dir, "2.jsonl"), [ result( "a", JSON.stringify({ used: 1, maxCount: 1, seconds: 5, result: {} }), ), request("b"), result("b", "本次调用的轻量检查已用完(上限 1 次),不能再查。"), ] .map((r) => JSON.stringify(r)) .join("\n"), ); const usage = lightCheckUsage(dir, 1); assert.equal(usage.requested, 2); assert.equal(usage.executed, 1); assert.equal(usage.deniedLimit, 1); assert.equal(usage.pass, true); writeFileSync(join(dir, "3.jsonl"), JSON.stringify(request("c"))); assert.equal(lightCheckUsage(dir, 1).pass, false); }); function referenceFixture(dir: string) { mkdirSync(dir); const db = new DatabaseSync(join(dir, "board.sqlite")); db.exec("CREATE TABLE facts (created_seq INTEGER, kind TEXT, payload TEXT)"); const study = dir.split("/").at(-1)!; const insert = (seq: number, kind: string, payload: unknown) => db .prepare("INSERT INTO facts VALUES (?,?,?)") .run(seq, kind, JSON.stringify(payload)); insert(1, "run-link", { purpose: "forward", roundId: `${study}.r1`, runId: "r", candidateId: "c", }); insert(2, "run-observation", { runId: "r", status: "incomplete", finalMetric: { value: 99 }, reasons: ["无效"], }); insert(3, "adoption", { roundId: `${study}.r1`, adoptedRunId: null, newBaselineId: "B0", }); db.close(); } test("forward 参考分数:incomplete 原值保留但有效分数为空", (t) => { const dir = join(tempDir(t), "reference"); referenceFixture(dir); const rows = referenceScores(join(dir, "board.sqlite")); assert.equal(rows[0]!.rawScore, 99); assert.equal(rows[0]!.validScore, null); assert.equal(rows[0]!.adopted, false); assert.equal(rows[0]!.observationSeq, 2); }); test("forward 生命周期:同名替换、缺完成事件、历史缓存触顶都不能证明没有重启", () => { const snapshots = [ { Id: "old", State: { Running: true, StartedAt: "before" } }, ]; const event = (id: string, action: string) => ({ Type: "container", Action: action, Actor: { ID: id, Attributes: { name: "target" } }, }); assert.equal( sameContainerThroughResult(snapshots, [event("old", "die")], "target") .noRestartOrReplacement, true, ); assert.equal( sameContainerThroughResult( [...snapshots, { Id: "replacement", State: snapshots[0]!.State }], [event("old", "die")], "target", ).snapshotsAgree, false, ); assert.equal( sameContainerThroughResult( snapshots, [event("old", "die"), event("new", "create")], "target", ).noRestartOrReplacement, false, ); assert.equal( sameContainerThroughResult(snapshots, [], "target").completionObserved, false, ); assert.equal( sameContainerThroughResult( snapshots, Array.from({ length: 256 }, () => event("old", "die")), "target", ).historyWithinLimit, false, ); }); test("forward 总体验收:必须三份不同完成研究、成功kill和不重叠的完整启动区间", (t) => { const root = tempDir(t); const launchFile = (dir: string, start: string, end: string) => writeFileSync( join(dir, "launches.jsonl"), [ { event: "start", pid: 1, at: start }, { event: "end", pid: 1, at: end }, ] .map((e) => JSON.stringify(e)) .join("\n"), ); const summaries: StudySummary[] = [0, 1, 2].map((i) => { const dir = join(root, `study-${i}`); mkdirSync(dir); launchFile(dir, `2026-10-04T0${i}:00:00Z`, `2026-10-04T0${i}:30:00Z`); return { dir, study: `study-${i}`, pass: true, accounts: { checks: { adoptedAndStopped: true } }, kill: i === 0 ? { checks: { sameContainer: true, chargedOnceNoRerun: true, resultAndContinued: true, }, } : null, }; }); // 杀后恢复的进程对也必须完整;间隔仍属于该研究,不能让另一研究插入。 writeFileSync( join(summaries[0]!.dir, "launches.jsonl"), [ { event: "start", pid: 1, at: "2026-10-04T00:00:00Z" }, { event: "end", pid: 1, at: "2026-10-04T00:10:00Z" }, { event: "start", pid: 2, at: "2026-10-04T00:15:00Z" }, { event: "end", pid: 2, at: "2026-10-04T00:30:00Z" }, ] .map((e) => JSON.stringify(e)) .join("\n"), ); assert.equal(forwardAcceptance(summaries).pass, true); assert.equal(forwardAcceptance(summaries.slice(0, 1)).pass, false); assert.equal(forwardAcceptance(summaries.slice(0, 2)).pass, false); assert.equal( forwardAcceptance(summaries.map((s) => ({ ...s, kill: null }))).pass, false, ); assert.equal( forwardAcceptance([summaries[0]!, summaries[0]!, summaries[2]!]).pass, false, ); assert.equal( forwardAcceptance( summaries.map((s) => ({ ...s, accounts: { checks: { adoptedAndStopped: false } }, })), ).pass, false, ); assert.equal( forwardAcceptance( summaries.map((s) => s.kill === null ? s : { ...s, kill: { checks: { sameContainer: false } } }, ), ).pass, false, ); const statsDir = join(root, "stats"); mkdirSync(statsDir); writeFileSync(join(statsDir, "stats.json"), JSON.stringify(summaries)); const passed = spawnSync( process.execPath, ["eval/forward/acceptance.ts", statsDir], { encoding: "utf8" }, ); assert.equal(passed.status, 0, passed.stderr); assert.equal( JSON.parse(readFileSync(join(statsDir, "acceptance.json"), "utf8")).pass, true, ); writeFileSync( join(statsDir, "stats.json"), JSON.stringify(summaries.slice(0, 1)), ); const failed = spawnSync( process.execPath, ["eval/forward/acceptance.ts", statsDir], { encoding: "utf8" }, ); assert.equal(failed.status, 1, failed.stderr); assert.match(failed.stdout, /总体程序验收未通过/); // 包含杀后恢复的整个研究区间不能与下一研究重叠。 launchFile(summaries[1]!.dir, "2026-10-04T00:20:00Z", "2026-10-04T01:30:00Z"); assert.equal(forwardAcceptance(summaries).checks.studiesDoNotOverlap, false); writeFileSync( join(summaries[1]!.dir, "launches.jsonl"), JSON.stringify({ event: "start", pid: 1, at: "2026-10-04T01:00:00Z" }), ); assert.equal( forwardAcceptance(summaries).checks.launchIntervalsComplete, false, ); });