446e26224e
- src/materials:每种提交一个材料函数,生成给模型看的材料、第一条消息、对号清单、能读的代码版本;查询只在材料内按编号找;读提示词并算原始字节哈希;信念本按方法.md 6.10 现算。 - 账本里只存成 Json 的冻结协议、位置表、决定、结算的形状和编号规则由 materials 定义并导出。 - vocab 加 Checklist(对号清单)。 - prompts/:参考项目 3 份角色提示词、16 份任务说明,只做必要改动。 - TASK.md 补一句 unversionedNames 不存进代码版本。 - 方法.md 4.3 ① 改为"研究者任务"放信念本;结构.md vocab 上限 1150、补 materials 对外内容。 用户 2026-10-03 开工前的四个决定(选的都是推荐项):"甲:上限改 1150,只放对号清单";"甲:materials 定义并导出";"不放,改方法.md ① 的措辞";"按原提案挑"。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
209 lines
8.0 KiB
TypeScript
209 lines
8.0 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
import { Value } from "typebox/value";
|
|
import {
|
|
canonical,
|
|
hash,
|
|
JudgedResult,
|
|
RunDefinition,
|
|
SUBMISSION_KINDS,
|
|
SUBMISSION_SCHEMAS,
|
|
type Checklist,
|
|
type Submission,
|
|
type SubmissionKind,
|
|
} from "../src/vocab/index.ts";
|
|
|
|
// 预期字符串手写;预期哈希用命令行 `printf '%s' '<预期字符串>' | sha256sum` 算出。
|
|
|
|
test("规范序列化:键的顺序不同,结果相同", () => {
|
|
const expected = '{"a":[true,null,"x"],"b":1,"c":{"y":"中","z":2.5}}';
|
|
const one = { b: 1, a: [true, null, "x"], c: { z: 2.5, y: "中" } };
|
|
const two = { c: { y: "中", z: 2.5 }, a: [true, null, "x"], b: 1 };
|
|
assert.equal(canonical(one), expected);
|
|
assert.equal(canonical(two), expected);
|
|
assert.equal(hash(one), "668eb8c3a097ac33abf5f690ab18c5f753b911ff1c9bbfd6163deb5939b68e5d");
|
|
assert.equal(hash(two), "668eb8c3a097ac33abf5f690ab18c5f753b911ff1c9bbfd6163deb5939b68e5d");
|
|
});
|
|
|
|
test("规范序列化:嵌套数组保持顺序,数组里的对象也排键", () => {
|
|
const value = [{ b: 0, a: [{ d: 1, c: -1 }] }, [1, [2, []]], {}, "s"];
|
|
const expected = '[{"a":[{"c":-1,"d":1}],"b":0},[1,[2,[]]],{},"s"]';
|
|
assert.equal(canonical(value), expected);
|
|
assert.equal(hash(value), "521ca7249ce6c90890b5645dd2f8d46b42f6e000df5ad86aff42415035c7e8cb");
|
|
assert.equal(hash({}), "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a");
|
|
});
|
|
|
|
test("规范序列化:字符串转义(引号、换行、制表、控制字符、反斜杠、中文)与 -0", () => {
|
|
const value = { "中": [-0], z: -0, s: 'a"b\n\t\u0001中\\' };
|
|
const expected = String.raw`{"s":"a\"b\n\t\u0001中\\","z":0,"中":[0]}`;
|
|
assert.equal(canonical(value), expected);
|
|
assert.equal(hash(value), "d4605ee7ceddf1373f60f837ae86ce24de7319705bbcbce4706309fc4415c79a");
|
|
});
|
|
|
|
test("规范序列化:NaN 和 Infinity 报错,嵌套在里面也报错", () => {
|
|
for (const bad of [NaN, Infinity, -Infinity, { a: [1, NaN] }, [{ x: Infinity }]]) {
|
|
assert.throws(() => canonical(bad), /有限数/);
|
|
assert.throws(() => hash(bad), /有限数/);
|
|
}
|
|
});
|
|
|
|
test("规范序列化:带自定义原型的对象和非 JSON 值报错", () => {
|
|
class Point {
|
|
x = 1;
|
|
}
|
|
const bad: unknown[] = [
|
|
new Point(),
|
|
Object.create(null),
|
|
new Date(0),
|
|
new Map(),
|
|
{ a: new Point() },
|
|
{ a: undefined },
|
|
() => 1,
|
|
1n,
|
|
];
|
|
for (const value of bad) assert.throws(() => canonical(value), /纯 JSON/);
|
|
});
|
|
|
|
// 每种提交一份合法样例,同时用来核对字段写的是不是方法.md 第五部分的意思。
|
|
const range = { q1: 2.6, q5: 2.62, q9: 2.63 };
|
|
const factors = [
|
|
{ name: "多起点局部优化", method: "从 8 个随机起点各跑一次 SLSQP,取最好的", keySettings: "起点数 8", continues: null },
|
|
{
|
|
name: "停滞期扰动",
|
|
method: "连续 20 步没有改进时扰动半径",
|
|
keySettings: "幅度 0.004",
|
|
offBehavior: "不扰动",
|
|
continues: "r1-c2.p1",
|
|
},
|
|
];
|
|
const metrics = {
|
|
defined: [
|
|
{
|
|
id: "first-feasible-seconds",
|
|
meaning: "第一次得到合法摆法的时间",
|
|
unit: "秒",
|
|
direction: "minimize" as const,
|
|
howMeasured: "从进程启动计时,到第一个通过校验的摆法写出为止,每次运行记一次",
|
|
missing: "recordMissing" as const,
|
|
},
|
|
],
|
|
reused: ["wall-seconds"],
|
|
};
|
|
const body = "## 为什么可能有效\n……";
|
|
|
|
const samples: { [K in SubmissionKind]: Submission<K> } = {
|
|
entry: { entries: [{ name: "起点分布", body: body }, { name: "收尾修复", body: body }] },
|
|
discussion: {
|
|
body,
|
|
entries: [
|
|
{ name: "a", body },
|
|
{ name: "b", body },
|
|
{ name: "c", body },
|
|
],
|
|
},
|
|
claim: { choices: ["researcher-2-e1", "researcher-1-e2"] },
|
|
proposal: { factors, safeguards: ["final-shrink"], metrics, authorEstimate: range, body },
|
|
firstPrediction: { range, body: "理由" },
|
|
critique: { target: "p2", body: "(推测)扰动可能和起点数混杂" },
|
|
response: {
|
|
replies: [{ critique: "cr-1", reply: "改了 p2 的幅度" }],
|
|
changes: { factors: [factors[0]!] },
|
|
clauses: ["在 solve.py 的 main 里加多起点循环"],
|
|
body: "答复",
|
|
},
|
|
unification: {
|
|
factors,
|
|
safeguards: [],
|
|
metrics: { defined: [], reused: [] },
|
|
body,
|
|
replies: [],
|
|
clauses: ["c1 的内容", "c2 的内容"],
|
|
sources: [{ proposal: "r2-p3", coverage: "体现在 p1" }],
|
|
},
|
|
secondPrediction: { range: { q1: 2.6, q5: 2.6, q9: 2.6 }, body: "理由" },
|
|
implementation: {
|
|
factorLocations: [{ factor: "p1", locations: ["solve.py:10-40"] }],
|
|
safeguardLocations: [{ safeguard: "final-shrink", locations: ["solve.py:88", "util.py"] }],
|
|
unassigned: [{ location: "solve.py:5", note: "补了一个 import" }],
|
|
concerns: "无",
|
|
body: "说明",
|
|
},
|
|
repair: {
|
|
factorLocations: [],
|
|
safeguardLocations: [],
|
|
unassigned: [],
|
|
concerns: "无",
|
|
body: "按核验意见补上 c2",
|
|
},
|
|
verification: {
|
|
clauses: [{ clause: "c1", found: true, where: "solve.py:10-40" }],
|
|
factors: [{ factor: "p1", found: false, where: "看了 solve.py 全文" }],
|
|
safeguards: [{ safeguard: "final-shrink", found: true, where: "solve.py:88" }],
|
|
unassigned: [{ location: "solve.py:5", outOfScope: false, reason: "只是 import" }],
|
|
body: "说明",
|
|
},
|
|
explanation: {
|
|
explanations: [
|
|
{ body: "p1 在出力", cells: [{ factor: "p1", range }, { factor: "p2", range }] },
|
|
{ body: "我没有具体的机制判断", cells: [{ factor: "p1", range: { q1: 2.3, q5: 2.55, q9: 2.64 } }] },
|
|
],
|
|
},
|
|
ablationImplementation: { alsoOff: [], cannotRun: null, concerns: "无" },
|
|
ablationVerification: { factors: [{ factor: "p1", off: true, where: "solve.py:10-40" }] },
|
|
wrapUp: { body: "被支持的:……" },
|
|
};
|
|
|
|
test("每种提交都有阶段、角色和字段定义", () => {
|
|
assert.deepEqual(Object.keys(SUBMISSION_SCHEMAS).sort(), Object.keys(SUBMISSION_KINDS).sort());
|
|
});
|
|
|
|
for (const kind of Object.keys(SUBMISSION_KINDS) as SubmissionKind[]) {
|
|
test(`合法样例通过字段定义检查:${kind}`, () => {
|
|
const schema = SUBMISSION_SCHEMAS[kind];
|
|
const errors = [...Value.Errors(schema, samples[kind])].map(
|
|
(e) => `${e.instancePath} ${e.message}`,
|
|
);
|
|
assert.deepEqual(errors, []);
|
|
});
|
|
}
|
|
|
|
test("关掉消融时,不带 cells 的解释通过字段定义检查", () => {
|
|
const submission = { explanations: [{ body: "机制:p1 在出力" }] };
|
|
assert.equal(Value.Check(SUBMISSION_SCHEMAS.explanation, submission), true);
|
|
});
|
|
|
|
// 复现:TypeBox 的 Record 对带格式的键只生成 patternProperties,不合格式的键原先完全不检查。
|
|
test("环境变量和指标取值里,不合格式的键被拒收", () => {
|
|
const run = {
|
|
command: ["python3", "solve.py"],
|
|
workdir: "/work",
|
|
image: { digest: `sha256:${"a".repeat(64)}`, runtime: "runc" },
|
|
wallSeconds: 120,
|
|
cores: 1,
|
|
inheritEnv: false,
|
|
env: { OMP_NUM_THREADS: "1" },
|
|
};
|
|
assert.equal(Value.Check(RunDefinition, run), true);
|
|
assert.equal(Value.Check(RunDefinition, { ...run, env: { "1BAD-KEY": "x" } }), false);
|
|
assert.equal(Value.Check(RunDefinition, { ...run, env: { "x-y": 5 } }), false);
|
|
const judged = {
|
|
verdict: "valid",
|
|
verdictReason: "合法",
|
|
score: { status: "measured", value: 2.6 },
|
|
metrics: { "wall-seconds": { status: "measured", value: 3 } },
|
|
answerPath: { status: "read", value: "main" },
|
|
safeguardTriggers: { status: "unreadable", reason: "没写" },
|
|
extra: {},
|
|
};
|
|
assert.equal(Value.Check(JudgedResult, judged), true);
|
|
const badMetrics = { "../x": { status: "measured", value: 1 } };
|
|
assert.equal(Value.Check(JudgedResult, { ...judged, metrics: badMetrics }), false);
|
|
});
|
|
|
|
test("对号清单:16 种提交种类每种都有一项,不多不少(类型断言,npm run check 时检查)", () => {
|
|
type Missing = Exclude<SubmissionKind, Checklist["kind"]>;
|
|
type Extra = Exclude<Checklist["kind"], SubmissionKind>;
|
|
const none: [Missing, Extra] extends [never, never] ? true : false = true;
|
|
assert.equal(none, true);
|
|
});
|