Files
InfoRSI/src/flow/steps/round-zero.ts
T
iomgaa 75d7cffd27 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>
2026-10-03 10:58:52 -04:00

45 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 });
}