feat: 接通实现核验修复到正向运行采纳

任务书16第二块:角色分派与每调用轻量检查,提交及代码原子记账,按次数核验修复且耗尽不跑,串行正式运行及恢复引用指标,采纳含null统一公开。451项免费测试通过,全新上下文独立审阅未发现问题;真实研究验收仍待进行。
This commit is contained in:
2026-10-04 04:25:00 -04:00
parent 039b7bb3bc
commit bf5b3f2358
13 changed files with 659 additions and 77 deletions
+72 -1
View File
@@ -1,9 +1,16 @@
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { cpSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test, type TestContext } from "node:test";
import { lightChecker } from "../src/flow/task-code.ts";
import { recover } from "../src/flow/recover.ts";
import { containerName, freshDir, runPaths } from "../src/flow/paths.ts";
import type { Study } from "../src/flow/study.ts";
import { Board, protocolMetrics, type FrozenProtocol } from "../src/materials/index.ts";
import { loadTask, formalRun as runDefinition, prepareRun } from "../src/tasks/index.ts";
import { runBalance } from "../src/rules/index.ts";
import { runStudy } from "../src/flow/index.ts";
import { Ledger } from "../src/ledger/index.ts";
import { Runner } from "../src/runner/index.ts";
@@ -76,3 +83,67 @@ test("docker run 没启动:写结果\"没启动\"后停下,原因带 Docker
assert.match((ending as { reason: string }).reason, /运行 base-1 没有启动.*unknown or invalid runtime name: inforsi-test-no-such-runtime/);
assert.deepEqual(ledgerKinds(dir), ["config", "code", "runPlanned", "runResult", "event"]);
});
/** 不派模型,只接真实任务镜像、账本和运行器,检查 flow 的两处 Docker 接线。 */
async function runtimeStudy(t: TestContext, suffix: string): Promise<Study> {
const dir = tempDir(t);
const id = studyId(t, suffix);
const task = loadTask(TASK_DIR);
const runner = new Runner(null);
const image = await runner.buildImage(task.dir, `inforsi-${id}`);
const ledger = Ledger.open(join(dir, "ledger.sqlite"));
t.after(() => ledger.close());
return { dir, task, runner, ledger, stop: new AbortController().signal,
frozen: { config: { ...fullConfig(), studyId: id, lightChecks: { maxCount: 1, seconds: 7 } }, facts: task.facts, image, seed: 1 } } as Study;
}
test("flow 轻量检查:每次调用各自限次和限时,真实 Docker 只检查当前目录,不登记正式运行", { timeout: 120_000 }, async (t) => {
const s = await runtimeStudy(t, "light");
const work = join(s.dir, "work");
mkdirSync(work);
writeFileSync(join(work, "initial.py"), "raise RuntimeError('不应执行候选代码')\ndef run_packing():\n pass\n");
for (const call of ["first-call", "second-call"]) {
freshDir(join(s.dir, "calls", call));
const check = lightChecker(s, call, work);
const result = JSON.parse(await check());
assert.deepEqual(result, { used: 1, maxCount: 1, seconds: 7, result: { passed: true } });
assert.match(await check(), /已用完(上限 1 次)/);
assert.equal(await s.runner.status(containerName(s.frozen.config.studyId, `${call}-check-1`)), "absent");
}
assert.deepEqual(s.ledger.records(), []);
});
test("正向恢复:接上原容器不重跑,补启动只扣一次,引用指标沿用原登记缺失作废规则", { timeout: 120_000 }, async (t) => {
const s = await runtimeStudy(t, "forward-recover");
const metric = { id: "old-c.required", meaning: "必测量", unit: "次", direction: "maximize" as const, howMeasured: "原登记定义", missing: "invalidateRun" as const };
const original: FrozenProtocol = { round: 0, baseline: "base", proposals: [], factors: [], safeguards: [], metrics: { defined: [metric], reused: [] }, clauses: [], body: "登记" };
const current: FrozenProtocol = { ...original, round: 1, metrics: { defined: [], reused: [metric.id] } };
s.ledger.append("protocol", { candidate: "old-c", protocol: original });
s.ledger.append("protocol", { candidate: "r1-c1", protocol: current });
const board = new Board(s.ledger.records());
const run = "forward-1";
const paths = runPaths(s.dir, run);
const definition = runDefinition(s.task, s.frozen.image, "1");
s.ledger.append("runPlanned", { run, purpose: "forward", round: 1, object: "r1-c1", definition });
freshDir(paths.base);
prepareRun(s.task, s.task.baselineDir, freshDir(paths.run), protocolMetrics(board, current));
const name = containerName(s.frozen.config.studyId, run);
t.after(() => { const result = docker(["rm", "-f", name]); if (result.status !== 0 && !result.stderr.includes("No such container")) throw new Error(result.stderr); });
assert.deepEqual(await s.runner.start(name, definition, paths.run), { started: true });
// 中断点:容器已启动,账本还没写 runStarted。恢复不调用 formalRun,而是找到原容器等它。
assert.equal(await recover(s), null);
const records = s.ledger.records();
assert.equal(records.filter((r) => r.kind === "runPlanned").length, 1);
assert.equal(records.filter((r) => r.kind === "runStarted").length, 1);
assert.equal(records.filter((r) => r.kind === "runResult").length, 1);
const result = records.find((r) => r.kind === "runResult")!;
if (result.kind !== "runResult") throw new Error("缺运行结果");
assert.equal(result.content.judged!.verdict, "invalid");
assert.match(result.content.judged!.verdictReason, /old-c.required/);
assert.equal(result.content.judged!.metrics[metric.id]!.status, "unmeasured");
assert.deepEqual(runBalance(13, new Board(records).runStates()), { charged: 1, reserved: 0, balance: 12 });
assert.equal(await s.runner.status(name), "absent");
assert.equal(await recover(s), null);
assert.equal(s.ledger.records().length, records.length);
});
+145
View File
@@ -0,0 +1,145 @@
import assert from "node:assert/strict";
import { mkdirSync, readFileSync, symlinkSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { test, type TestContext } from "node:test";
import { adoptBatch } from "../src/flow/steps/forward.ts";
import { prepareSubmission, taskCode } from "../src/flow/task-code.ts";
import type { Study } from "../src/flow/study.ts";
import { Board, buildMaterial, protocolMetrics, type FrozenProtocol } from "../src/materials/index.ts";
import { Ledger } from "../src/ledger/index.ts";
import { loadTask } from "../src/tasks/index.ts";
import { checkoutVersion, createRepository, saveVersion } from "../src/workspace/index.ts";
import type { Submission } from "../src/vocab/index.ts";
import { fullConfig } from "./config-helpers.ts";
import { implementation, tempDir } from "./ledger-helpers.ts";
import { judged, prompts, study, Writer } from "./materials-helpers.ts";
function setup(t: TestContext): { s: Study; w: Writer } {
const dir = tempDir(t);
const ledger = Ledger.open(join(dir, "ledger.sqlite"));
t.after(() => ledger.close());
const task = loadTask(join(import.meta.dirname, "..", "tasks", "circle_packing"));
const s = { dir, ledger, task, prompts, facts: study, frozen: { config: fullConfig(), facts: task.facts } } as Study;
return { s, w: new Writer(ledger) };
}
// 预期值手算:基线 1,ε=1/64,已知最好 2;线=1/64。1+1/64 正好在线上,不采纳。
for (const [label, scores, adopted, gain] of [
["高于线且同分:取自然编号小的", [1.125, 1.125], "r1-c2", 0.125],
["正好等于线:也写 null 决定", [1.015625, 1], null, 0.015625],
["整批没有有效分数:也写 null 决定", [], null, null],
] as const) {
test(`采纳写账:${label},写决定后整批公开`, (t) => {
const { s, w } = setup(t);
s.frozen.facts = { ...s.frozen.facts, epsilon: 0.015625, knownBest: 2 };
w.run("base-1", "baseline", 0, null, judged(1, "main"));
w.decision("batch", 1, null, { chosen: ["r1-c10", "r1-c2", "r1-c3"] });
scores.forEach((score, i) => w.run(["run-first", "run-second"][i]!, "forward", 1, i === 0 ? "r1-c10" : "r1-c2", judged(score, "main")));
w.run("invalid", "forward", 1, "r1-c3", { ...judged(99, "main"), verdict: "invalid", verdictReason: "不合法" });
// 不在本批的运行不参与,即使它分数更高;上轮不能混进本轮。
w.run("other", "forward", 0, "old-candidate", judged(100, "main"));
const before = new Board(s.ledger.records());
assert.ok(before.runs().filter((r) => r.plan.round === 1).every((r) => !before.runPublic(r)));
adoptBatch(s, 1);
const record = s.ledger.records().at(-1)!;
assert.equal(record.kind, "decision");
if (record.kind !== "decision") throw new Error("应写采纳决定");
assert.deepEqual(record.content, {
decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1,
params: { direction: "maximize", epsilon: 0.015625, knownBest: 2 }, seed: null,
input: { candidates: scores.map((score, i) => ({ id: i === 0 ? "r1-c10" : "r1-c2", score })), baseline: 1, direction: "maximize", epsilon: 0.015625, knownBest: 2 },
output: { line: 0.015625, quarter: 0.25, adopted,
best: scores.length === 0 ? null : { id: adopted ?? "r1-c10", score: scores[0], improvement: gain } },
});
const after = new Board(s.ledger.records());
assert.ok(after.runs().filter((r) => r.plan.round === 1).every((r) => after.runPublic(r)));
assert.equal(after.baselineOf(2), adopted ?? "base");
});
}
const metric = { id: "old-c.metric", meaning: "试验值", unit: "次", direction: "maximize" as const, howMeasured: "按原登记计数", missing: "invalidateRun" as const };
const oldProtocol: FrozenProtocol = { round: 0, baseline: "base", proposals: [], factors: [], safeguards: [], metrics: { defined: [metric], reused: [] }, clauses: [], body: "原登记" };
const protocol: FrozenProtocol = { round: 1, baseline: "base", proposals: [], factors: [{ name: "改动", method: "照协议改动", keySettings: "无", continues: null }], safeguards: [], metrics: { defined: [], reused: [metric.id] }, clauses: ["照协议"], body: "冻结正文" };
function codeStudy(t: TestContext) {
const { s, w } = setup(t);
const repo = join(s.dir, "repo.git");
createRepository(repo);
const workDir = join(s.dir, "work");
mkdirSync(workDir);
writeFileSync(join(workDir, "initial.py"), "first = 1\nsecond = 2\n");
writeFileSync(join(workDir, "evaluate.py"), "protected = True\n");
const baseline = saveVersion(repo, workDir, []);
if (!baseline.saved) throw new Error(baseline.reason);
s.ledger.append("code", { object: "base", version: baseline.version, locations: null });
w.protocol("old-c", oldProtocol);
w.protocol("r1-c1", protocol);
w.open("r1-implementation", "implementation", 1, ["experimenter-r1-c1"]);
return { s, w, repo, workDir, baseline: baseline.version };
}
const impl: Submission<"implementation"> = { ...implementation("实现说明"), factorLocations: [{ factor: "p1", locations: ["initial.py:1-2"] }] };
const ref = { actor: "experimenter-r1-c1", object: "r1-c1", kind: "implementation" as const, stageId: "r1-implementation" };
test("提交准备:快照、保护文件、差异块和引用指标同源;修复取上版,但差异永远相对本批基线", (t) => {
const { s, w, repo, workDir, baseline } = codeStudy(t);
assert.deepEqual(taskCode(s, ref), { changes: null, startVersion: baseline });
writeFileSync(join(workDir, "initial.py"), "first = 10\nsecond = 2\n");
const prepared = prepareSubmission(s, ref, impl, 0, workDir);
assert.ok(prepared.code);
const known = prepared.known as { code: { saved: true; protectedFiles: string[]; hunks: { text: string }[]; protocol: unknown } };
assert.deepEqual(known.code.protectedFiles, []);
assert.deepEqual(known.code.protocol, { body: "冻结正文", metrics: [metric] });
assert.match(known.code.hunks[0]!.text, /-first = 1\n\+first = 10/);
s.ledger.transaction(() => {
w.sub(ref.stageId, ref.actor, ref.kind, ref.object, null, impl);
s.ledger.append("code", prepared.code!);
});
w.close(ref.stageId);
w.open("r1-verification-1", "verification", 1, ["verifier-r1-c1"]);
w.sub("r1-verification-1", "verifier-r1-c1", "verification", "r1-c1", null, { clauses: [{ clause: "c1", found: false, where: "第二行未实现" }], factors: [{ factor: "p1", found: false, where: "第二行未实现" }], safeguards: [], unassigned: [], body: "只修第二行" });
w.close("r1-verification-1");
w.open("r1-repair-1", "implementation", 1, [ref.actor]);
const repair = { ...ref, kind: "repair" as const, stageId: "r1-repair-1" };
const previous = taskCode(s, repair);
assert.equal(previous.startVersion, prepared.code.version);
assert.match(previous.changes!.diff, /-first = 1\n\+first = 10/);
const repairMaterial = buildMaterial({ ...repair, ledger: s.ledger, study, prompts, codeRoot: join(s.dir, "readable"), changes: previous.changes, artifacts: null });
assert.match(repairMaterial.firstMessage, /工作目录是上一次提交的代码的副本/);
assert.match(repairMaterial.firstMessage, /第二行未实现/);
writeFileSync(join(workDir, "initial.py"), "first = 10\nsecond = 20\n");
const fixed = prepareSubmission(s, repair, impl, 0, workDir);
const fixedKnown = fixed.known as typeof known;
assert.match(fixedKnown.code.hunks[0]!.text, /-first = 1/);
assert.match(fixedKnown.code.hunks[0]!.text, /\+second = 20/);
s.ledger.transaction(() => {
w.sub(repair.stageId, repair.actor, repair.kind, repair.object, null, impl);
s.ledger.append("code", fixed.code!);
});
w.close(repair.stageId);
w.open("r1-verification-2", "verification", 1, ["verifier-r1-c1"]);
const verification = { ...ref, kind: "verification" as const, stageId: "r1-verification-2" };
const compare = taskCode(s, verification);
assert.equal(compare.startVersion, null);
assert.match(compare.changes!.diff, /-first = 1/);
assert.match(compare.changes!.diff, /\+second = 20/);
assert.deepEqual(protocolMetrics(new Board(s.ledger.records()), protocol), [metric]);
const snapshot = join(s.dir, "snapshot");
mkdirSync(snapshot);
checkoutVersion(repo, prepared.code.version, snapshot);
assert.equal(readFileSync(join(snapshot, "initial.py"), "utf8"), "first = 10\nsecond = 2\n");
});
test("存不成版本的原原因交 submissions;改了保护文件时不能被漏掉", (t) => {
const { s, workDir } = codeStudy(t);
writeFileSync(join(workDir, "evaluate.py"), "protected = False\n");
const prepared = prepareSubmission(s, ref, impl, 0, workDir);
assert.deepEqual((prepared.known as { code: { protectedFiles: string[] } }).code.protectedFiles, ["evaluate.py"]);
symlinkSync("initial.py", join(workDir, "link.py"));
const refused = prepareSubmission(s, ref, impl, 0, workDir);
assert.equal(refused.code, null);
const code = (refused.known as { code: { saved: false; path: string; reason: string } }).code;
assert.equal(code.saved, false);
assert.equal(code.path, "link.py");
assert.match(code.reason, /符号链接/);
});
+153 -4
View File
@@ -4,9 +4,12 @@ import { join } from "node:path";
import { test } from "node:test";
import { stepList, type ResearchConfig } from "../src/config/index.ts";
import type { AppendKind, Content, LedgerRecord } from "../src/ledger/index.ts";
import type { StudyRecord } from "../src/materials/index.ts";
import { Board, forwardNotRunEvent, type StudyRecord } from "../src/materials/index.ts";
import { runBalance } from "../src/rules/index.ts";
import { definition, exited } from "./ledger-helpers.ts";
import { judged } from "./materials-helpers.ts";
import { nextStep } from "../src/flow/next-step.ts";
import type { Json, SubmissionKind } from "../src/vocab/index.ts";
import type { Json, Submission, SubmissionKind } from "../src/vocab/index.ts";
import { fullConfig } from "./config-helpers.ts";
// flow 的"下一步做什么"(任务书 12 第六部分):纯函数,手写账本记录,看它给出的下一步。
@@ -337,8 +340,8 @@ test("第二次预测关闭:选批", () => {
assert.deepEqual(nextStep(roundOne("secondPrediction").records), { do: "selectBatch", round: 1 });
});
test("选批做完:停下,原因\"实现还没写\"", () => {
assert.deepEqual(nextStep(roundOne("selectBatch").records), { do: "halt", reason: "实现还没写(任务书 16)", objects: [] });
test("选批做完:开实现阶段,每个入选候选固定一位实验员", () => {
assert.deepEqual(nextStep(roundOne("selectBatch").records), { do: "openStage", stageId: "r1-implementation", stage: "implementation", round: 1, roster: ["experimenter-r1-c1"] });
});
test("关掉第一次预测:第 1 轮写一条按配置跳过(对象 r1-firstPrediction),接着开挑刺", () => {
@@ -354,3 +357,149 @@ test("关掉第一次预测:第 1 轮写一条按配置跳过(对象 r1-firs
book.add("event", { event: "skippedByConfig", reason, objects: ["r1-firstPrediction"] });
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-critique", stage: "critique", round: 1, roster: R });
});
// ⑨–⑫:预期动作手写,不调模型、Jev 或 Docker。
const X = ["experimenter-r1-c1", "experimenter-r1-c2"];
const V = ["verifier-r1-c1", "verifier-r1-c2"];
function batchTwo(maxRepairs: number): Book {
const book = roundOne("secondPrediction");
const record = book.records.find((r) => r.kind === "config")!;
(record.content as unknown as StudyRecord).config = { ...fullConfig(), maxRepairs } as unknown as Json;
return book.add("decision", { decision: "batch", round: 1, rule: "bestExpected", ruleVersion: 1, params: null, seed: "s", input: null, output: { chosen: C } });
}
function implemented(book: Book): Book {
book.open("r1-implementation", "implementation", 1, X);
for (const [i, candidate] of C.entries()) book.sub("r1-implementation", X[i]!, "implementation", candidate, null);
return book.add("stageClose", { stageId: "r1-implementation" });
}
function verified(book: Book, n: number, candidate: string, passed: boolean): Book {
// 对应真实 15-tight-2 的 submissions created_seq=173(墙约束行和右端错位)、194(修复后通过)。
// 只转录驱动程序所需的逐项 found,不伪造 Jev 的判断或模型运行。
const content: Submission<"verification"> = {
clauses: [{ clause: "c1", found: passed, where: passed ? "initial.py 墙约束行和右端已对齐" : "initial.py:225-228 与 243-246:墙约束行和右端错位" }],
factors: [{ factor: "p1", found: passed, where: "LP 墙约束" }],
safeguards: [], unassigned: [], body: passed ? "通过" : "解的不是协议写的线性规划",
};
return book.add("submission", { stageId: `r1-verification-${n}`, author: `verifier-${candidate}`, kind: "verification", target: candidate, item: null, version: 1, content, jev: null });
}
function firstVerification(book: Book, a: boolean, b: boolean): Book {
implemented(book).open("r1-verification-1", "verification", 1, V);
verified(book, 1, C[0]!, a);
verified(book, 1, C[1]!, b);
return book.add("stageClose", { stageId: "r1-verification-1" });
}
function recordForward(book: Book, candidate: string, n: number, ending: "valid" | "notStarted" | "timeout"): Book {
const run = `r1-forward-${candidate}-${n}`;
book.add("runPlanned", { run, purpose: "forward", round: 1, object: candidate, definition });
if (ending === "notStarted") return book.add("runResult", { run, process: { ending, reason: "Docker 未启动" }, judged: null });
book.add("runStarted", { run });
const process = ending === "timeout" ? { ending: "timeout" as const, wallSeconds: measured(120), cpuSeconds: measured(100), peakMemoryBytes: measured(1024) } : exited;
return book.add("runResult", { run, process, judged: ending === "valid" ? judged(1.25, "main") : null });
}
const forward = (candidate: string, n: number) => ({ do: "forwardRun", round: 1, candidate, run: `r1-forward-${candidate}-${n}` });
test("实现的任务同时可派;已提交后中断不重派,等全体交齐关阶段后才核验", () => {
const book = batchTwo(1).open("r1-implementation", "implementation", 1, X);
assert.deepEqual(nextStep(book.records), { do: "dispatch", stageId: "r1-implementation", tasks: C.map((object, i) => ({ actor: X[i], kind: "implementation", object })) });
book.sub("r1-implementation", X[0]!, "implementation", C[0]!, null);
book.call("r1-implementation", X[0]!, "implementation", C[0]!, 1, { wallSeconds: { status: "unmeasured", reason: "进程中断" }, status: "failed", reason: "进程中断" });
assert.deepEqual(nextStep(book.records), { do: "dispatch", stageId: "r1-implementation", tasks: [{ actor: X[1], kind: "implementation", object: C[1] }] });
book.sub("r1-implementation", X[1]!, "implementation", C[1]!, null);
assert.deepEqual(nextStep(book.records), { do: "closeStage", stageId: "r1-implementation" });
book.add("stageClose", { stageId: "r1-implementation" });
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-verification-1", stage: "verification", round: 1, roster: V });
});
test("15-tight-2 的一次修复路径:核验关后才派原实验员,修复关后只重验修过的候选", () => {
const book = implemented(batchTwo(1)).open("r1-verification-1", "verification", 1, V);
verified(book, 1, C[0]!, false);
verified(book, 1, C[1]!, true);
assert.deepEqual(nextStep(book.records), { do: "closeStage", stageId: "r1-verification-1" });
book.add("stageClose", { stageId: "r1-verification-1" });
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-repair-1", stage: "implementation", round: 1, roster: [X[0]] });
book.open("r1-repair-1", "implementation", 1, [X[0]!]);
assert.deepEqual(nextStep(book.records), { do: "dispatch", stageId: "r1-repair-1", tasks: [{ actor: X[0], kind: "repair", object: C[0] }] });
book.sub("r1-repair-1", X[0]!, "repair", C[0]!, null);
assert.deepEqual(nextStep(book.records), { do: "closeStage", stageId: "r1-repair-1" });
book.add("stageClose", { stageId: "r1-repair-1" });
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-verification-2", stage: "verification", round: 1, roster: [V[0]] });
book.open("r1-verification-2", "verification", 1, [V[0]!]);
assert.deepEqual(nextStep(book.records), { do: "dispatch", stageId: "r1-verification-2", tasks: [{ actor: V[0], kind: "verification", object: C[0] }] });
verified(book, 2, C[0]!, true);
assert.deepEqual(nextStep(book.records), { do: "closeStage", stageId: "r1-verification-2" });
book.add("stageClose", { stageId: "r1-verification-2" });
assert.deepEqual(nextStep(book.records), forward(C[0]!, 1));
});
test("修复次数从关闭的阶段数出:两次修复后仍失败,逐候选写不跑事件,不占预算", () => {
const book = firstVerification(batchTwo(2), false, false);
for (let n = 1; n <= 2; n++) {
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: `r1-repair-${n}`, stage: "implementation", round: 1, roster: X });
book.open(`r1-repair-${n}`, "implementation", 1, X);
for (const [i, candidate] of C.entries()) book.sub(`r1-repair-${n}`, X[i]!, "repair", candidate, null);
book.add("stageClose", { stageId: `r1-repair-${n}` });
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: `r1-verification-${n + 1}`, stage: "verification", round: 1, roster: V });
book.open(`r1-verification-${n + 1}`, "verification", 1, V);
for (const candidate of C) verified(book, n + 1, candidate, false);
book.add("stageClose", { stageId: `r1-verification-${n + 1}` });
}
for (const candidate of C) {
const next = nextStep(book.records);
assert.equal(next.do, "forwardNotRun");
if (next.do !== "forwardNotRun") throw new Error("应写不跑事件");
assert.equal(next.candidate, candidate);
assert.match(next.reason, /第 3 次核验仍不通过,修复次数上限 2 已用完/);
assert.match(next.reason, /解的不是协议写的线性规划/);
book.add("event", forwardNotRunEvent(candidate, next.reason));
}
assert.deepEqual(runBalance(13, new Board(book.records).runStates()), { charged: 1, reserved: 0, balance: 12 });
assert.deepEqual(nextStep(book.records), { do: "adopt", round: 1 });
});
test("maxRepairs=0:第一次核验失败就记不跑,另一候选仍可串行运行", () => {
const book = firstVerification(batchTwo(0), false, true);
const next = nextStep(book.records);
assert.equal(next.do, "forwardNotRun");
if (next.do !== "forwardNotRun") throw new Error("应写不跑事件");
book.add("event", forwardNotRunEvent(next.candidate, next.reason));
assert.deepEqual(nextStep(book.records), forward(C[1]!, 1));
});
test("正向串行:上次未启动起新编号;已登记未结束必须先恢复;超时也扣次且不重跑;采纳前不公开", () => {
const book = firstVerification(batchTwo(1), true, true);
assert.deepEqual(nextStep(book.records), forward(C[0]!, 1));
recordForward(book, C[0]!, 1, "notStarted");
assert.deepEqual(nextStep(book.records), forward(C[0]!, 2));
assert.deepEqual(runBalance(13, new Board(book.records).runStates()), { charged: 1, reserved: 0, balance: 12 });
const run = `r1-forward-${C[0]}-2`;
book.add("runPlanned", { run, purpose: "forward", round: 1, object: C[0]!, definition });
assert.throws(() => nextStep(book.records), /登记了还没有结果:恢复入口应当已经核对过/);
assert.deepEqual(runBalance(13, new Board(book.records).runStates()), { charged: 1, reserved: 1, balance: 11 });
book.add("runStarted", { run });
assert.throws(() => nextStep(book.records), /登记了还没有结果/);
book.add("runResult", { run, process: exited, judged: judged(1.25, "main") });
assert.deepEqual(nextStep(book.records), forward(C[1]!, 1));
recordForward(book, C[1]!, 1, "timeout");
assert.deepEqual(nextStep(book.records), { do: "adopt", round: 1 });
assert.deepEqual(runBalance(13, new Board(book.records).runStates()), { charged: 3, reserved: 0, balance: 10 });
const before = new Board(book.records);
assert.ok(before.runs().filter((r) => r.plan.purpose === "forward").every((r) => !before.runPublic(r)));
book.add("decision", { decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1, params: null, seed: null, input: null, output: { adopted: null } });
const after = new Board(book.records);
assert.ok(after.runs().filter((r) => r.plan.purpose === "forward").every((r) => after.runPublic(r)));
assert.deepEqual(nextStep(book.records), { do: "halt", reason: "消融还没写(任务书 17)", objects: [] });
});
test("采纳只有一个因子的候选也统一在 listCells 停下,不擅自开收尾", () => {
const book = firstVerification(batchTwo(1), true, true);
for (const c of C) recordForward(book, c, 1, "valid");
book.add("decision", { decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1, params: null, seed: null, input: null, output: { adopted: C[0]! } });
assert.deepEqual(nextStep(book.records), { do: "halt", reason: "消融还没写(任务书 17)", objects: [] });
});