feat: flow 第 2 部分,第 r 轮开轮到选批(任务书 14 第二块)
- next-step 按"第 r 轮"列出提案、第一次预测、挑刺、回应与统一、第二次预测的任务 (一人对一个对象),判重、冻结协议、选批三步,走到 implementation 停下(任务书 15) - steps/dispatch.ts:同阶段一人任务先后跑、不同人同时跑;提案、挑刺按 proposalId、critiqueId 分份 - steps/round.ts:判重两两问 Jev、连通成组、并进留池候选;冻结协议在回应阶段关闭后一个事务写全; 选批照 rules 的 BatchInput 拼,种子用 decisionSeed 版本 1(选格预留版本 2) - materials 导出 Board(构造改收记录数组,给纯函数用);讨论原文里已作废的入口编号就地标"(已作废)" - 删 SOURCES.md 里参考项目实测事实那一段(用户 2026-10-03 同意) - 测试:下一步做什么 21 条、判重连组 4 条、冻结与选批 2 条、作废标注 1 条;npm test 437 通过 全新上下文审阅一轮(只看三个程序步骤与恢复):没有会污染账本的问题。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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 { freezeProtocols, selectBatch } from "../src/flow/steps/round.ts";
|
||||
import type { Study } from "../src/flow/study.ts";
|
||||
import { Ledger, type LedgerRecord } from "../src/ledger/index.ts";
|
||||
import { decisionSeed, runBatchRule } from "../src/rules/index.ts";
|
||||
import { checkTaskFacts } from "../src/tasks/index.ts";
|
||||
import { fullConfig } from "./config-helpers.ts";
|
||||
import { tempDir } from "./ledger-helpers.ts";
|
||||
import { judged, P_A1, P_A2, r, Writer } from "./materials-helpers.ts";
|
||||
|
||||
// 冻结协议和选批两步(任务书 14 第 2.4 节):在真实账本里写到回应阶段关闭、第二次预测关闭,跑这一步,看写下的记录。
|
||||
// 预期值按条文手写:协议主体取回应叠原提案、指标改全名、基线和合并的原提案;选批输入照 rules 的 BatchInput 逐项手拼。
|
||||
// 这两步只读账本和冻结的配置,Study 里别的几样(Docker、提示词)用不到。
|
||||
|
||||
const config = fullConfig();
|
||||
const facts = checkTaskFacts(JSON.parse(readFileSync(join(import.meta.dirname, "..", "tasks", "circle_packing", "task.json"), "utf8")));
|
||||
const CRITIQUE = "r1-a1-1.a2.1";
|
||||
|
||||
function studyAfterResponse(t: TestContext): Study {
|
||||
const ledger = Ledger.open(join(tempDir(t), "ledger.db"));
|
||||
t.after(() => ledger.close());
|
||||
const w = new Writer(ledger);
|
||||
ledger.append("code", { object: "base", version: "v-base", locations: null });
|
||||
w.run("base-1", "baseline", 0, null, judged(0.96, "main"));
|
||||
w.open("r1-proposal", "proposal", 1, ["a1", "a2"]);
|
||||
w.sub("r1-proposal", "a1", "proposal", null, "r1-a1-1", P_A1);
|
||||
w.sub("r1-proposal", "a2", "proposal", null, "r1-a2-1", P_A2);
|
||||
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.open("r1-critique", "critique", 1, ["a1", "a2"]);
|
||||
w.sub("r1-critique", "a2", "critique", "r1-a1-1", CRITIQUE, { target: "p2", body: "乙的挑刺" });
|
||||
w.close("r1-critique");
|
||||
w.open("r1-response", "response", 1, ["a1", "a2"]);
|
||||
// a1 第一版只答复,第二版改了正文:冻结取最后一版。
|
||||
w.sub("r1-response", "a1", "response", "r1-c1", null, { replies: [{ critique: CRITIQUE, reply: "保留" }], changes: {}, clauses: ["旧条款"], body: "无" });
|
||||
ledger.append("submission", {
|
||||
stageId: "r1-response",
|
||||
author: "a1",
|
||||
kind: "response",
|
||||
target: "r1-c1",
|
||||
item: null,
|
||||
version: 2,
|
||||
content: { replies: [{ critique: CRITIQUE, reply: "改了正文" }], changes: { body: "甲改过的正文" }, clauses: ["加多起点", "加扰动"], body: "答复正文" },
|
||||
jev: null,
|
||||
});
|
||||
w.sub("r1-response", "a2", "response", "r1-c2", null, { replies: [], changes: {}, clauses: ["加修复"], body: "无" });
|
||||
w.close("r1-response");
|
||||
const frozen = { config: checkedFull(config), steps: stepList(config), facts, prompts: null, image: "", seed: 1 };
|
||||
return { ledger, frozen } as unknown as Study;
|
||||
}
|
||||
|
||||
const protocols = (records: readonly LedgerRecord[]) => records.filter((x) => x.kind === "protocol").map((x) => x.content);
|
||||
|
||||
test("冻结协议:回应阶段关闭后,每个新候选取最后一版回应叠在原提案上,指标改全名,写明基线和合并的原提案;同一个事务", (t) => {
|
||||
const s = studyAfterResponse(t);
|
||||
freezeProtocols(s, 1);
|
||||
assert.deepEqual(protocols(s.ledger.records()), [
|
||||
{
|
||||
candidate: "r1-c1",
|
||||
protocol: {
|
||||
factors: P_A1.factors,
|
||||
safeguards: ["guard"],
|
||||
metrics: { defined: [{ ...P_A1.metrics.defined[0]!, id: "r1-c1.restart-count" }], reused: [] },
|
||||
body: "甲改过的正文",
|
||||
clauses: ["加多起点", "加扰动"],
|
||||
round: 1,
|
||||
baseline: "base",
|
||||
proposals: ["r1-a1-1"],
|
||||
},
|
||||
},
|
||||
{
|
||||
candidate: "r1-c2",
|
||||
protocol: {
|
||||
factors: P_A2.factors,
|
||||
safeguards: [],
|
||||
metrics: { defined: [{ ...P_A2.metrics.defined[0]!, id: "r1-c2.restart-count" }], reused: [] },
|
||||
body: "乙的提案正文",
|
||||
clauses: ["加修复"],
|
||||
round: 1,
|
||||
baseline: "base",
|
||||
proposals: ["r1-a2-1"],
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("选批:输入照 BatchInput 拼(各人最后一版第二次预测、本批基线分数、余额从运行记录算、种子用选批的版本),规则取步骤清单", (t) => {
|
||||
const s = studyAfterResponse(t);
|
||||
freezeProtocols(s, 1);
|
||||
const w = new Writer(s.ledger);
|
||||
w.open("r1-secondPrediction", "secondPrediction", 1, ["a1", "a2"]);
|
||||
w.sub("r1-secondPrediction", "a1", "secondPrediction", "r1-c1", null, { range: r(1, 1.1, 1.2), body: "第一版" });
|
||||
s.ledger.append("submission", {
|
||||
stageId: "r1-secondPrediction",
|
||||
author: "a1",
|
||||
kind: "secondPrediction",
|
||||
target: "r1-c1",
|
||||
item: null,
|
||||
version: 2,
|
||||
content: { range: r(1.05, 1.15, 1.25), body: "改过" },
|
||||
jev: null,
|
||||
});
|
||||
w.sub("r1-secondPrediction", "a2", "secondPrediction", "r1-c1", null, { range: r(1, 1.1, 1.2), body: "理由" });
|
||||
w.sub("r1-secondPrediction", "a1", "secondPrediction", "r1-c2", null, { range: r(0.9, 0.97, 1), body: "理由" });
|
||||
w.sub("r1-secondPrediction", "a2", "secondPrediction", "r1-c2", null, { range: r(0.9, 0.96, 1), body: "理由" });
|
||||
w.close("r1-secondPrediction");
|
||||
selectBatch(s, 1);
|
||||
const decision = s.ledger.records().find((x) => x.kind === "decision" && x.content.decision === "batch")!.content as Record<string, unknown>;
|
||||
const seed = decisionSeed({ studyId: "circle-full-1", round: 1, version: 1 });
|
||||
const input = {
|
||||
candidates: [
|
||||
{ id: "r1-c1", ranges: [{ researcher: "a1", range: r(1.05, 1.15, 1.25) }, { researcher: "a2", range: r(1, 1.1, 1.2) }] },
|
||||
{ id: "r1-c2", ranges: [{ researcher: "a1", range: r(0.9, 0.97, 1) }, { researcher: "a2", range: r(0.9, 0.96, 1) }] },
|
||||
],
|
||||
baseline: 0.96,
|
||||
direction: "maximize",
|
||||
epsilon: facts.epsilon,
|
||||
lambda: 0.1,
|
||||
batchCap: 2,
|
||||
// 13 次里基线运行确认启动、扣了 1 次。
|
||||
balance: 12,
|
||||
seed,
|
||||
} as const;
|
||||
const { rule: _rule, ...output } = runBatchRule("bestExpected", input);
|
||||
assert.deepEqual(decision, { decision: "batch", round: 1, rule: "bestExpected", ruleVersion: 1, params: { epsilon: facts.epsilon, lambda: 0.1, batchCap: 2, balance: 12 }, seed, input, output });
|
||||
});
|
||||
Reference in New Issue
Block a user