feat: flow 第 1 部分(骨架、研究开始、第零轮)与第零轮检验
按任务书 12 实现 src/flow:从账本现算下一步的纯函数、主循环、恢复入口、派模型任务和跑正式运行两个共用动作、冻结配置、基线、第零轮三个研究者阶段和入口分配,走到第 1 轮时停下。materials 导出 finalEntries、加 researcherId 和 StudyRecord。 审阅采纳两条:研究编号本机已用过时冻结前报错;docker run 没启动时写完结果就停下,不在本进程里无限起新编号。 eval/ 新增第零轮检验:起研究命令行、回放假模型、检验用 Jev 客户端、六问质量评估、16-real-1 回放、杀进程与两份同时跑。tsc 与 depcruise 也检查 eval/,新增 eval 只经 index.ts 用正式代码的规则。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { finalEntries } from "../../materials/index.ts";
|
||||
import { assignEntries as assign } from "../../rules/index.ts";
|
||||
import type { Json } from "../../vocab/index.ts";
|
||||
import { runModelTask } from "../model-task.ts";
|
||||
import type { Next } from "../next-step.ts";
|
||||
import { halt, USER_STOPPED, type Study, type StudyEnding } from "../study.ts";
|
||||
|
||||
// 第零轮(方法.md 4.2):三个研究者阶段派任务,认领之后由程序分配入口。
|
||||
|
||||
/**
|
||||
* 派一个研究者阶段里要派的人,同时跑,各自一个会话(任务书 12 第 2.6 节)。第零轮的提交种类交上来之后没有要另算的东西。
|
||||
* 有人没交上(没交或出错),等这次派发的会话全部结束后停下,不打断别人(第 2.4 节)。
|
||||
* 某个会话抛出内部错误时,也等别人结束再把它抛出去。
|
||||
*/
|
||||
export async function dispatchStage(s: Study, next: Extract<Next, { do: "dispatch" }>): Promise<StudyEnding | null> {
|
||||
const settled = await Promise.allSettled(
|
||||
next.actors.map((actor) => runModelTask(s, { kind: next.kind, actor, stageId: next.stageId, object: null, known: () => null })),
|
||||
);
|
||||
const failure = settled.find((r) => r.status === "rejected");
|
||||
if (failure !== undefined) throw failure.reason;
|
||||
if (s.stop.aborted) return USER_STOPPED;
|
||||
const results = settled.map((r) => (r as PromiseFulfilledResult<Awaited<ReturnType<typeof runModelTask>>>).value);
|
||||
const missed = results.flatMap(({ call, ending }) =>
|
||||
ending.status === "notSubmitted" || ending.status === "failed" ? [{ call, text: `${ending.status === "failed" ? "出错" : "没交"}:${ending.reason}` }] : [],
|
||||
);
|
||||
if (missed.length === 0) return null;
|
||||
return halt(s.ledger, `阶段 ${next.stageId} 有人没交上,等这次派发的会话全部结束后停下:${missed.map((m) => `调用 ${m.call},${m.text}`).join(";")}`, missed.map((m) => m.call));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分配(方法.md 4.2"分配规则"):每人最后一份认领和最终有效的入口交给 rules 的 assignEntries,写一条入口分配决定。
|
||||
* assignEntries 不在规则登记表里,规则名就写它,版本 1;没有方法参数,也不用随机。
|
||||
*/
|
||||
export function assignEntries(s: Study): void {
|
||||
const roster = s.ledger.records().find((r) => r.kind === "stageOpen" && r.content.stage === "claim" && r.content.round === 0);
|
||||
const claim = roster!.content as { stageId: string; roster: string[] };
|
||||
const researchers = claim.roster.map((id) => {
|
||||
const last = s.ledger.latestSubmission({ stageId: claim.stageId, author: id, kind: "claim", target: null, item: null });
|
||||
return { id, choices: (last!.content.content as { choices: string[] }).choices };
|
||||
});
|
||||
const entries = finalEntries(s.ledger).map((e) => ({ id: e.id, author: e.author }));
|
||||
const input = { researchers, entries };
|
||||
s.ledger.append("decision", { decision: "entries", round: 0, rule: "assignEntries", ruleVersion: 1, params: null, seed: null, input, output: assign(input) as unknown as Json });
|
||||
}
|
||||
Reference in New Issue
Block a user