Files
InfoRSI/tests/materials-belief.test.ts
T
iomgaa 09c44031a0 feat: 任务书 10,submissions 模块与 materials 修改
- 新增 src/submissions:先程序检查(14 条跨字段规则、不许改的文件、代码存不了、指标编号占用、提案数上限),全过再问 Jev;退回原因列出该写的全部编号;回应/统一拼冻结协议主体只在这一处;核验、消融核验通过没有。
- materials:挑刺单写"现在交什么";退回按阶段、作者、种类、对象找(ledger 退回记录加 target,表结构版本 4);核验材料去掉 Jev 预扫;信念本写合法化;删"本该关着";消融核验不通过可重做,不跑的格从 cellNotRun 事件读原因。
- 用户 2026-10-03 定:冻结协议去掉 replies、replyBody,第二次预测的答复从已公开的回应或统一读;takenMetricIds 加两个保留键;解释一律交 cells;Jev 退回只列判"是"的几问、用英文问题名。
- jev 判重材料带 keySettings;读代码工具删 offset;三处旧说法改正。
- 文档:结构.md 第四、七、八部分,WORK_LOG。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 06:08:34 -04:00

243 lines
14 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 assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { test } from "node:test";
import { Ledger } from "../src/ledger/index.ts";
import { beliefBook, cellNotRunEvent, queryMaterial } from "../src/materials/index.ts";
import { adopt, selectBestExpected, settleAblation, type BatchInput, type SettleInput } from "../src/rules/index.ts";
import { tempDir } from "./ledger-helpers.ts";
import { batch, judged, material, NONE, P_A1, P_A2, protocolOf, r, study, studyUpTo, TABLE_C1, Writer } from "./materials-helpers.ts";
// 信念本的留池、离池,按条文手算的数字,第零轮记录在第 1 轮起公开。
function fresh(t: import("node:test").TestContext): Writer {
const l = Ledger.open(join(tempDir(t), "ledger.db"));
t.after(() => l.close());
const w = new Writer(l);
w.l.append("code", { object: "base", version: "v-base", locations: null });
w.run("run-0", "baseline", 0, null, judged(0.96, "main"));
return w;
}
const adoptInput = (baseline: number, candidates: Array<{ id: string; score: number }>) => ({
baseline,
knownBest: 2.635983,
epsilon: 0.01,
direction: "maximize" as const,
candidates,
});
/** 第 1 轮冻结 r1-c1、r1-c2,选中 r1-c1、跑了没采纳:r1-c2 留到第 2 轮的候选池。 */
function roundOneNoAdoption(t: import("node:test").TestContext, before: (w: Writer) => void): Writer {
const w = fresh(t);
before(w);
w.protocol("r1-c1", protocolOf(P_A1, 1, "base", ["r1-a1-1"], ["c"]));
w.protocol("r1-c2", protocolOf(P_A2, 1, "base", ["r1-a2-1"], ["c"]));
const b1 = batch(1, 0.96, { "r1-c1": { a1: r(1.0, 1.1, 1.2) }, "r1-c2": { a1: r(0.95, 0.97, 0.99) } });
assert.deepEqual(b1.output.chosen, ["r1-c1"]);
w.decision("batch", 1, b1.input, b1.output);
w.l.append("code", { object: "r1-c1", version: "v-c1", locations: TABLE_C1 });
w.run("run-1", "forward", 1, "r1-c1", judged(0.965, "main"));
const a1 = adoptInput(0.96, [{ id: "r1-c1", score: 0.965 }]);
w.decision("adopt", 1, a1, adopt(a1));
return w;
}
test("留池:第 1 轮没选中、第 2 轮入选跑了没采纳的候选,第 3 轮不再算留池(复现审阅意见 1)", (t) => {
const w = roundOneNoAdoption(t, () => undefined);
w.open("s2-prop", "proposal", 2, ["a1"]);
// 第 2 轮提案阶段(还没有本轮判重合并):r1-c2 没选中、基线没变,留在候选池。
assert.ok(beliefBook(w.l, study, 2).includes("r1-c2:没选中,留在本轮候选池;之前的合并范围不附"));
const b2 = batch(2, 0.96, { "r1-c2": { a1: r(0.95, 0.97, 0.99) } });
w.decision("batch", 2, b2.input, b2.output);
w.l.append("code", { object: "r1-c2", version: "v-c2", locations: { factorLocations: [{ factor: "p1", locations: ["x.py"] }], safeguardLocations: [], unassigned: [] } });
w.run("run-2", "forward", 2, "r1-c2", judged(0.962, "main"));
const a2 = adoptInput(0.96, [{ id: "r1-c2", score: 0.962 }]);
w.decision("adopt", 2, a2, adopt(a2));
w.open("s3-prop", "proposal", 3, ["a1"]);
const book = beliefBook(w.l, study, 3);
assert.ok(!book.includes("留在本轮候选池"), "r1-c2 第 2 轮已经入选,不再留池");
assert.match(book, /- 第 1 轮选批[^\n]*\n(?:[^\n]*\n)*? - r1-c2:合并范围 /);
assert.ok(book.includes("离开候选池的旧候选**:没有"));
});
test("导出时(round 为 null)不藏任何候选的合并范围(复现复审意见 1)", (t) => {
const w = roundOneNoAdoption(t, () => undefined);
w.open("s2-prop", "proposal", 2, ["a1"]);
const book = beliefBook(w.l, study, null);
assert.ok(!book.includes("留在本轮候选池"));
assert.match(book, / - r1-c2:合并范围 /);
});
test("第二次预测:留池候选按 carried 判,不取它上一轮的第一次预测(复现复审意见 2)", (t) => {
const w = roundOneNoAdoption(t, (w) => {
w.open("s1-prop", "proposal", 1, ["a1"]);
w.sub("s1-prop", "a1", "proposal", null, "r1-a1-1", P_A1);
w.sub("s1-prop", "a1", "proposal", null, "r1-a1-2", P_A2);
w.close("s1-prop");
w.decision("dedup", 1, null, { fresh: [{ candidate: "r1-c1", proposals: ["r1-a1-1"] }, { candidate: "r1-c2", proposals: ["r1-a1-2"] }], carried: [] });
w.open("s1-fp", "firstPrediction", 1, ["a1"]);
w.sub("s1-fp", "a1", "firstPrediction", "r1-c2", null, { range: r(4.441, 4.442, 4.443), body: "上一轮的第一次" });
w.close("s1-fp");
w.open("s1-crit", "critique", 1, ["a1"]);
w.sub("s1-crit", "a1", "critique", "r1-a1-2", "r1-a1-2.a1.1", { target: "p1", body: "第 1 轮对乙方案的挑刺" });
w.close("s1-crit");
w.open("s1-resp", "response", 1, ["a1"]);
w.sub("s1-resp", "a1", "response", "r1-c2", null, { replies: [{ critique: "r1-a1-2.a1.1", reply: "第 1 轮的答复" }], changes: {}, clauses: ["c"], body: "第 1 轮的答复正文" });
w.close("s1-resp");
});
w.open("s2-prop", "proposal", 2, ["a1"]);
w.close("s2-prop");
w.decision("dedup", 2, null, { fresh: [], carried: [{ candidate: "r1-c2", proposals: [] }] });
w.open("s2-sp", "secondPrediction", 2, ["a1"]);
const m = material(w.l, "secondPrediction", "a1", "s2-sp", "r1-c2", NONE);
assert.ok(m.firstMessage.includes("(这是留池候选,本轮没有第一次预测)"));
assert.ok(!m.firstMessage.includes("4.442"));
// 留池候选沿用原来的冻结协议和回应(4.3 ①):答复从它第 1 轮的回应读。
for (const x of ["第 1 轮对乙方案的挑刺", "作者的答复:第 1 轮的答复", "第 1 轮的答复正文"]) assert.ok(m.firstMessage.includes(x), x);
});
test("离池:基线变了、从没入选的旧候选列在离开候选池里,不再写留池", (t) => {
const book = beliefBook(studyUpTo(t, "proposal2Open"), study, 2);
assert.ok(book.includes("离开候选池的旧候选**:r1-c2"));
assert.ok(!book.includes("留在本轮候选池"));
});
test("信念本的数按条文手算:少多少分、撑几成、世界比特、惊讶、战绩次数、标签", (t) => {
// 正向的三个候选、各人范围、本批基线 b 和 c3 的实测取自 tests/fixtures/rules/info.json 的 bits_forward_r1_real,
// 世界比特、惊讶的预期值是那里不看实现算出的独立值。
const fx = JSON.parse(readFileSync(join(import.meta.dirname, "fixtures", "rules", "info.json"), "utf8")).scenarios[0];
const b: number = fx.input.b;
const full: number = fx.input.actual["r1-v1-c3"];
const w = fresh(t);
const input: BatchInput = {
candidates: Object.entries(fx.input.candidates as Record<string, Array<{ researcher: string; range: { q1: number; q5: number; q9: number } }>>).map(
([id, ranges]) => ({ id, ranges }),
),
baseline: b,
direction: "maximize",
epsilon: 0.01,
lambda: 0.1,
batchCap: 1,
balance: 9,
seed: "seed",
};
// 这里只要 c3 入选并跑过:选批记录的其余内容照规则算出,chosen 写成 c3。
w.decision("batch", 1, input, { ...selectBestExpected(input), chosen: ["r1-v1-c3"] });
w.protocol("r1-v1-c3", protocolOf(P_A1, 1, "base", ["r1-a1-1"], ["c"]));
w.l.append("code", { object: "r1-v1-c3", version: "v-c3", locations: TABLE_C1 });
w.run("run-1", "forward", 1, "r1-v1-c3", judged(full, "main"));
const a = adoptInput(b, [{ id: "r1-v1-c3", score: full }]);
w.decision("adopt", 1, a, adopt(a));
w.run("run-2", "ablation", 1, "r1-v1-c3-p1", judged(1.3, "main"));
w.run("run-3", "ablation", 1, "r1-v1-c3-p2", judged(1.45, "main"));
const settle: SettleInput = {
baseline: b,
full,
direction: "maximize",
epsilon: 0.01,
knownBest: 2.635983,
lambda: 0.1,
factors: ["p1", "p2"],
cells: [
{ factor: "p1", outcome: { kind: "valid", score: 1.3 } },
{ factor: "p2", outcome: { kind: "valid", score: 1.45 } },
],
explanations: [
{ id: "r1-researcher-1-x1", author: "researcher-1", cells: [{ factor: "p1", range: r(1.25, 1.35, 1.45) }, { factor: "p2", range: r(1.5, 1.55, 1.6) }] },
{ id: "r1-researcher-2-x1", author: "researcher-2", cells: [{ factor: "p1", range: r(1.1, 1.2, 1.28) }, { factor: "p2", range: r(1.4, 1.44, 1.5) }] },
],
};
w.l.append("settlement", { round: 1, result: { candidate: "r1-v1-c3", input: settle, output: settleAblation(settle) } as never });
w.open("s2-prop", "proposal", 2, ["researcher-1"]);
const book = beliefBook(w.l, study, 2);
// 世界比特 13.018692667257758、惊讶 18.609640474436972(fixture),写 4 位有效数字。
assert.ok(book.includes("世界比特 13.02,惊讶 18.61 比特"), "正向 c3 的世界比特与惊讶");
// G = 1.552176048093269 − 0.9597642169962064 = 0.5924118311;关 p1:少 0.252176,撑 0.4257 → 43%;关 p2:少 0.102176,撑 0.1725 → 17%。
assert.ok(book.includes("关掉后 1.3,少 0.252176 分,撑 43%"));
assert.ok(book.includes("关掉后 1.45,少 0.102176 分,撑 17%"));
// Σ撑 = 0.598,相对交互量 −0.402 < −0.2:替代(方法.md 6.6)。
assert.ok(book.includes("替代:几个因子在做同一件事"));
// 正向:三人对 c3 的一成都在 2.1 以上,实测 1.552 低于一成:中位数偏高、穿过下沿,没包住。
for (const who of ["researcher-1", "researcher-2", "researcher-3"]) {
assert.ok(book.includes(`- ${who},正向 1 次:中位数偏高 1、偏低 0;穿过上沿 0、下沿 1;包住 0`), who);
}
// 消融:researcher-1 p1 1.3 落在 1.25–1.35(包住,中位数偏高)、p2 1.45 < 1.5(穿下沿,偏高);
// researcher-2 p1 1.3 > 1.28(穿上沿,偏低)、p2 1.45 在 1.44–1.5(包住,偏低)。
assert.ok(book.includes("- researcher-1,消融 2 次:中位数偏高 2、偏低 0;穿过上沿 0、下沿 1;包住 1"));
assert.ok(book.includes("- researcher-2,消融 2 次:中位数偏高 0、偏低 2;穿过上沿 1、下沿 0;包住 1"));
});
test("第零轮的入口、讨论、志愿、分配在第 1 轮起的研究记录里查得到(方法.md 4.2)", (t) => {
const m = material(studyUpTo(t, "proposalOpen"), "proposal", "a2", "s1-prop", null, NONE);
assert.ok(queryMaterial(m, "e-a1-1")!.includes("已被整份替换,作废"));
assert.ok(queryMaterial(m, "e-a2-1")!.includes("乙一起点"));
assert.ok(queryMaterial(m, "s0-d1.a1")!.includes("甲的讨论"));
assert.ok(queryMaterial(m, "s0-claim.a2")!.includes("e-a2-1"));
assert.ok(queryMaterial(m, "entry-assignment")!.includes("e-a1-3"));
});
test("第 2 轮解释的研究记录里查得到第 1 轮和第零轮的已公开记录", (t) => {
const m = material(studyUpTo(t, "explOpen2"), "explanation", "a1", "s2-expl", null, { changes: null, artifacts: {} });
const list = m.sections.find((s) => s.heading === "研究记录")!.text;
const ids = ["run-1", "run-2", "run-3", "r1-c1", "r1-a1-1", "r1-a2-1", "r1-a1-1.a2.1", "r1-a1-x1", "r1-a2-x1", "s1-wrap.a1", "s0-d1.a1", "s0-claim.a2", "entry-assignment"];
for (const id of ids) {
assert.ok(list.includes(`- ${id}:`), `研究记录清单里应有 ${id}`);
assert.notEqual(queryMaterial(m, id), null, id);
}
});
test("解释的现在交什么写明每人最多 M 条", (t) => {
const m = material(studyUpTo(t, "explOpen1"), "explanation", "a1", "s1-expl", null, { changes: null, artifacts: {} });
assert.ok(m.firstMessage.slice(m.firstMessage.lastIndexOf("# 现在交什么")).includes("最多 2 条"));
});
/** 第 1 轮冻结 r1-c1(两个因子)、选中、跑出 run1Judged、采纳。 */
function roundOneAdopted(t: import("node:test").TestContext, run1Judged: ReturnType<typeof judged>): Writer {
const w = fresh(t);
w.protocol("r1-c1", protocolOf(P_A1, 1, "base", ["r1-a1-1"], ["c"]));
const b1 = batch(1, 0.96, { "r1-c1": { a1: r(1.0, 1.1, 1.2) } });
w.decision("batch", 1, b1.input, b1.output);
w.l.append("code", { object: "r1-c1", version: "v-c1", locations: TABLE_C1 });
w.run("run-1", "forward", 1, "r1-c1", run1Judged);
const a1 = adoptInput(0.96, [{ id: "r1-c1", score: 1.2 }]);
w.decision("adopt", 1, a1, adopt(a1));
return w;
}
test("选批历史的每次运行后面原样写出任务的附加记录(合法化扣掉的分,方法.md 6.10 第 3 条)", (t) => {
const extra = { legalization: { applied: true, deduction: { status: "measured", value: 0.0012 } } };
const w = roundOneAdopted(t, { ...judged(1.2, "main"), extra });
const book = beliefBook(w.l, study, 1);
assert.ok(book.includes('任务的附加记录(含合法化,原样):{"legalization":{"applied":true,"deduction":{"status":"measured","value":0.0012}}}'), book);
});
test("消融格因核验不通过、重做次数用完而不跑:没有运行,证据不足的原因从事件读,信念本照样写得出", (t) => {
const w = roundOneAdopted(t, judged(1.2, "main"));
// p1 跑了,答案出自被关掉的 p1 本身:只照实写答案出处,不另加"本该关着"。p2 不跑。
w.l.append("code", { object: "r1-c1-p1", version: "v-c1-p1", locations: null });
w.run("run-2", "ablation", 1, "r1-c1-p1", judged(1.08, "p1"));
w.l.append("event", cellNotRunEvent("r1-c1-p2", "消融核验不通过,修复次数用完"));
const settle: SettleInput = {
baseline: 0.96,
full: 1.2,
direction: "maximize",
epsilon: 0.01,
knownBest: 2.635983,
lambda: 0.1,
factors: ["p1", "p2"],
cells: [
{ factor: "p1", outcome: { kind: "valid", score: 1.08 } },
{ factor: "p2", outcome: { kind: "insufficient" } },
],
explanations: [{ id: "r1-a1-x1", author: "a1", cells: [{ factor: "p1", range: r(1.0, 1.1, 1.15) }, { factor: "p2", range: r(1.1, 1.15, 1.19) }] }],
};
w.l.append("settlement", { round: 1, result: { candidate: "r1-c1", input: settle, output: settleAblation(settle) } as never });
const book = beliefBook(w.l, study, 2);
assert.ok(book.includes("- r1-c1-p2 停滞扰动(停滞扰动的设定):候选 r1-c1,基线 base;证据不足:消融核验不通过,修复次数用完;测前合并范围"), book);
// G = 1.2 − 0.96 = 0.24;关 p1 少 0.12,撑 0.5 → 50%。
assert.ok(book.includes("关掉后 1.08,少 0.12 分,撑 50%"), book);
assert.ok(book.includes("答案出自 p1"));
assert.ok(!book.includes("本该关着"));
});