import assert from "node:assert/strict"; import { readFileSync } from "node:fs"; import { join } from "node:path"; import { test, type TestContext } from "node:test"; import { stepList } from "../src/config/index.ts"; import { fullConfig as checkedFull } from "../src/flow/frozen.ts"; import { selectCells, settle, unsealChosen } from "../src/flow/steps/ablation.ts"; import type { Study } from "../src/flow/study.ts"; import { Ledger } from "../src/ledger/index.ts"; import { Board } from "../src/materials/index.ts"; import { chooseBet, decisionSeed, runCellRule, settleAblation, type AblationModelInput, type CellRuleInput, type SettleInput } from "../src/rules/index.ts"; import { checkTaskFacts } from "../src/tasks/index.ts"; import { fullConfig } from "./config-helpers.ts"; import { definition, exited, proposal, tempDir } from "./ledger-helpers.ts"; import { judged, r, Writer } from "./materials-helpers.ts"; // 选格、结算与押的因子、轮末解封(任务书 17 第 2.2 节):在真实账本里写到上一步做完,跑这一步,看写下的记录。 // 预期值按条文手拼:选格输入照 rules 的 CellRuleInput 逐项拼,结算的各格结局照方法.md 6.6 的情形拼; // 输出用 rules 的函数当对照(同选批测试的写法,rules 自己有独立预期值的测试)。 const config = fullConfig(); const facts = checkTaskFacts(JSON.parse(readFileSync(join(import.meta.dirname, "..", "tasks", "circle_packing", "task.json"), "utf8"))); function study(t: TestContext): { s: Study; w: Writer } { const ledger = Ledger.open(join(tempDir(t), "ledger.db")); t.after(() => ledger.close()); const frozen = { config: checkedFull(config), steps: stepList(config), facts, prompts: null, image: "", seed: 1 }; return { s: { ledger, frozen } as unknown as Study, w: new Writer(ledger) }; } /** 基线 0.96、被采纳候选 r1-c1(两个因子)完整代码 1.25。 */ function adoptedBase(w: Writer): void { w.l.append("code", { object: "base", version: "v-base", locations: null }); w.run("base-1", "baseline", 0, null, judged(0.96, "main")); w.l.append("code", { object: "r1-c1", version: "v-full", locations: null }); w.run("r1-forward-r1-c1-1", "forward", 1, "r1-c1", judged(1.25, "main")); w.protocol("r1-c1", { factors: [{ name: "因子一" }, { name: "因子二" }], safeguards: [], metrics: { defined: [], reused: [] }, body: "正文", clauses: [], round: 1, baseline: "base", proposals: ["r1-a1-1"] } as never); w.decision("adopt", 1, null, { adopted: "r1-c1" }); } const EXPLANATIONS = [ { id: "r1-a1-x1", author: "a1", cells: [{ factor: "p1", range: r(1.0, 1.1, 1.2) }, { factor: "p2", range: r(1.15, 1.2, 1.24) }] }, { id: "r1-a2-x1", author: "a2", cells: [{ factor: "p1", range: r(0.9, 1.05, 1.2) }, { factor: "p2", range: r(1.1, 1.18, 1.25) }] }, ] as const; /** 解释阶段关闭:a1、a2 各交一条,对两格都给了三个数。 */ function explain(w: Writer): void { w.open("r1-explanation", "explanation", 1, ["a1", "a2"]); for (const e of EXPLANATIONS) { w.sub("r1-explanation", e.author, "explanation", null, null, { explanations: [{ body: `机制(${e.author})`, cells: e.cells.map((c) => ({ factor: c.factor, range: c.range })) }] }); } w.close("r1-explanation"); } /** 成分模型的输入:选格、结算和押的因子共用的那一份。 */ function modelInput(): AblationModelInput { return { baseline: 0.96, full: 1.25, direction: "maximize", knownBest: facts.knownBest, epsilon: facts.epsilon, lambda: 0.1, factors: ["p1", "p2"], explanations: EXPLANATIONS.map((e) => ({ id: e.id, author: e.author, cells: e.cells.map((c) => ({ factor: c.factor, range: c.range })) })) }; } test("选格:输入照 CellRuleInput 拼(基线、完整分数、各条解释、余额从运行记录算、种子用选格的版本 2),规则取步骤清单", (t) => { const { s, w } = study(t); adoptedBase(w); explain(w); selectCells(s, 1); const record = s.ledger.records().find((x) => x.kind === "decision" && x.content.decision === "cells")!; const seed = decisionSeed({ studyId: "circle-full-1", round: 1, version: 2 }); const input: CellRuleInput = { ...modelInput(), // 13 次里基线和正向各确认启动 1 次,余额 11。 balance: 11, batchCap: 2, u: 0.5, decay: 0.5, seed, }; const { rule: used, ...output } = runCellRule("valueMinusCost", input); assert.deepEqual(record.content, { decision: "cells", round: 1, rule: used.name, ruleVersion: used.version, params: output.params, seed, input, output }); }); /** 一次消融实现阶段:subs 是 [格, cannotRun, alsoOff];cannotRun 为 null 表示交了代码。 */ function impl(w: Writer, n: number, subs: Array<[string, string | null, string[]?]>): void { w.open(`r1-ablationImplementation-${n}`, "ablationImplementation", 1, subs.map(([cell]) => `experimenter-${cell}`)); for (const [cell, cannotRun, alsoOff] of subs) { w.sub(`r1-ablationImplementation-${n}`, `experimenter-${cell}`, "ablationImplementation", cell, null, { alsoOff: alsoOff ?? [], cannotRun, concerns: "无" }); } w.close(`r1-ablationImplementation-${n}`); } /** 结算一次:拼出 SettleInput,跑 settle,核对 settlement 和 bet 两条记录。 */ function settled(t: TestContext, prepare: (w: Writer) => void, cells: SettleInput["cells"]): void { const { s, w } = study(t); adoptedBase(w); explain(w); w.decision("cells", 1, null, { chosen: ["p1", "p2"] }); prepare(w); settle(s, 1); const input: SettleInput = { ...modelInput(), cells }; const betInput = { ...modelInput(), cells }; const settlement = s.ledger.records().find((x) => x.kind === "settlement")!; assert.deepEqual(settlement.content, { round: 1, result: { candidate: "r1-c1", input, output: settleAblation(input) } }); const bet = s.ledger.records().find((x) => x.kind === "decision" && x.content.decision === "bet")!; assert.deepEqual(bet.content, { decision: "bet", round: 1, rule: "chooseBet", ruleVersion: 1, params: { direction: "maximize", epsilon: facts.epsilon, knownBest: facts.knownBest, lambda: 0.1 }, seed: null, input: betInput, output: chooseBet(betInput), }); } test("结算:有效分数、没测、跑不起来、超时记证据不足,各按 6.6 的结局拼", (t) => { settled( t, (w) => { impl(w, 1, [["r1-c1-p1", null], ["r1-c1-p2", null]]); w.run("r1-ablation-r1-c1-p1-1", "ablation", 1, "r1-c1-p1", judged(1.1, "main")); // p2 启动了但跑崩: judged 为 null,记证据不足(扣了次数)。 w.l.append("runPlanned", { run: "r1-ablation-r1-c1-p2-1", purpose: "ablation", round: 1, object: "r1-c1-p2", definition }); w.l.append("runStarted", { run: "r1-ablation-r1-c1-p2-1" }); w.l.append("runResult", { run: "r1-ablation-r1-c1-p2-1", process: exited, judged: null }); }, [ { factor: "p1", outcome: { kind: "valid", score: 1.1 } }, { factor: "p2", outcome: { kind: "insufficient" } }, ], ); }); test("结算:没选中的格记没测;交了跑不起来的记跑不起来", (t) => { const { s, w } = study(t); adoptedBase(w); explain(w); w.decision("cells", 1, null, { chosen: ["p1"] }); impl(w, 1, [["r1-c1-p1", "离了 p1 跑不起来"]]); settle(s, 1); const input: SettleInput = { ...modelInput(), cells: [ { factor: "p1", outcome: { kind: "cannotRun" } }, { factor: "p2", outcome: { kind: "untested" } }, ], }; const settlement = s.ledger.records().find((x) => x.kind === "settlement")!; assert.deepEqual(settlement.content, { round: 1, result: { candidate: "r1-c1", input, output: settleAblation(input) } }); }); test("结算:核验不通过、次数用完有事件的记证据不足;设置相同的两格都记一起关,沿用第一格的分数", (t) => { settled( t, (w) => { impl(w, 1, [["r1-c1-p1", null, ["p2"]]]); w.run("r1-ablation-r1-c1-p1-1", "ablation", 1, "r1-c1-p1", judged(1.05, "main")); impl(w, 2, [["r1-c1-p2", null]]); w.l.append("event", { event: "cellNotRun", reason: "第 2 次核验仍不通过,修复次数上限 1 已用完", objects: ["r1-c1-p2"] }); }, [ { factor: "p1", outcome: { kind: "joint", off: ["p1", "p2"], score: 1.05 } }, { factor: "p2", outcome: { kind: "insufficient" } }, ], ); }); test("结算:设置相同的两格都记一起关,沿用第一格的分数", (t) => { settled( t, (w) => { impl(w, 1, [["r1-c1-p1", null, ["p2"]]]); w.run("r1-ablation-r1-c1-p1-1", "ablation", 1, "r1-c1-p1", judged(1.05, "main")); impl(w, 2, [["r1-c1-p2", null, ["p1"]]]); }, [ { factor: "p1", outcome: { kind: "joint", off: ["p1", "p2"], score: 1.05 } }, { factor: "p2", outcome: { kind: "joint", off: ["p2", "p1"], score: 1.05 } }, ], ); }); test("轮末解封:解封本轮选中的候选的全部作者自评(取最后一版的封存),没选中的继续封存", (t) => { const { s, w } = study(t); w.open("r1-proposal", "proposal", 1, ["a1", "a2"]); w.sub("r1-proposal", "a1", "proposal", null, "r1-a1-1", proposal(r(1.0, 1.1, 1.2), "第一版")); const v2 = s.ledger.append("submission", { stageId: "r1-proposal", author: "a1", kind: "proposal", target: null, item: "r1-a1-1", version: 2, content: proposal(r(1.05, 1.15, 1.25), "第二版"), jev: null, } as never); w.sub("r1-proposal", "a2", "proposal", null, "r1-a2-1", proposal(r(0.9, 1.0, 1.1))); w.close("r1-proposal"); w.decision("dedup", 1, null, { fresh: [{ candidate: "r1-c1", proposals: ["r1-a1-1"] }, { candidate: "r1-c2", proposals: ["r1-a2-1"] }], carried: [] }); w.decision("batch", 1, null, { chosen: ["r1-c1"] }); unsealChosen(s, 1); const unseals = s.ledger.records().filter((x) => x.kind === "unseal"); assert.equal(unseals.length, 1); assert.equal(unseals[0]!.content.candidate, "r1-c1"); // 解封的是最后一版(v2)的封存。 const seal = s.ledger.records().find((x) => x.kind === "seal" && x.content.proposal === v2.seq)!; assert.deepEqual(unseals[0]!.content.seals, [seal.seq]); assert.deepEqual(new Board(s.ledger.records()).unsealed(), [{ candidate: "r1-c1", author: "a1", range: r(1.05, 1.15, 1.25) }]); });