fix: 用容器生命周期核实恢复并单列三份总体验收
审阅发现spawn后快照早于恢复实际执行,不能证明没有重启;改用杀前至目标结果落账的Docker历史事件,保留缓存完整性限制。逐份stats不再冒称任务书总体通过;独立验收要求三份不同研究、成功杀进程及不重叠区间。两项缺陷附回归,7项定向测试与check通过,独立复审无新问题;真实B尚待启动。
This commit is contained in:
+203
-7
@@ -1,6 +1,6 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import { test } from "node:test";
|
||||
@@ -10,6 +10,11 @@ import {
|
||||
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";
|
||||
@@ -106,10 +111,11 @@ test("forward 导出/统计:历史代码配对、两次核验一次修复、
|
||||
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", "after-resume"])
|
||||
for (const part of ["before", "after-kill", "before-resume"])
|
||||
writeFileSync(
|
||||
join(dir, `container-${part}.json`),
|
||||
JSON.stringify({
|
||||
@@ -122,6 +128,25 @@ test("forward 导出/统计:历史代码配对、两次核验一次修复、
|
||||
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",
|
||||
@@ -180,6 +205,7 @@ test("forward 导出/统计:历史代码配对、两次核验一次修复、
|
||||
{ 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"),
|
||||
);
|
||||
@@ -194,12 +220,10 @@ test("forward 导出/统计:历史代码配对、两次核验一次修复、
|
||||
chargedOnceNoRerun: true,
|
||||
resultAndContinued: true,
|
||||
});
|
||||
// 三份早期快照完全一致,之后同一容器重启仍必须使验收失败。
|
||||
writeFileSync(
|
||||
join(dir, "container-after-resume.json"),
|
||||
JSON.stringify({
|
||||
Id: "replacement",
|
||||
State: { Running: true, StartedAt: "2026-10-04T00:01:00Z" },
|
||||
}),
|
||||
join(dir, "container-events.jsonl"),
|
||||
[event("die"), event("start"), event("restart"), event("die")].join("\n"),
|
||||
);
|
||||
const badOut = join(root, "bad-stats");
|
||||
const bad = spawnSync(
|
||||
@@ -213,6 +237,22 @@ test("forward 导出/统计:历史代码配对、两次核验一次修复、
|
||||
.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) => {
|
||||
@@ -352,3 +392,159 @@ test("forward 参考分数:incomplete 原值保留但有效分数为空", (t)
|
||||
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,
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user