feat: 任务书 10,submissions 模块与 materials 修改

- 新增 src/submissions:先程序检查(14 条跨字段规则、不许改的文件、代码存不了、指标编号占用、提案数上限),全过再问 Jev;退回原因列出该写的全部编号;回应/统一拼冻结协议主体只在这一处;核验、消融核验通过没有。
- materials:挑刺单写"现在交什么";退回按阶段、作者、种类、对象找(ledger 退回记录加 target,表结构版本 4);核验材料去掉 Jev 预扫;信念本写合法化;删"本该关着";消融核验不通过可重做,不跑的格从 cellNotRun 事件读原因。
- 用户 2026-10-03 定:冻结协议去掉 replies、replyBody,第二次预测的答复从已公开的回应或统一读;takenMetricIds 加两个保留键;解释一律交 cells;Jev 退回只列判"是"的几问、用英文问题名。
- jev 判重材料带 keySettings;读代码工具删 offset;三处旧说法改正。
- 文档:结构.md 第四、七、八部分,WORK_LOG。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-03 06:08:34 -04:00
parent a0ee4ef7a2
commit 09c44031a0
34 changed files with 1221 additions and 169 deletions
+10 -15
View File
@@ -134,7 +134,7 @@ const READ_MAX_BYTES = 50 * 1024;
* 读一个文件或列一个目录。路径必须是绝对路径,按真实路径(解析 .. 和符号链接之后)判断在不在可读目录里:
* 模型正常探索时就会去 ls 上一层,看到别人还没公开的东西。不合格就抛错,Pi 把错误回给模型。
*/
export function readSource(roots: readonly string[], path: string, offset: number): string {
export function readSource(roots: readonly string[], path: string): string {
const allowed = roots.map((r) => realpathSync(r));
const listing = allowed.map((r) => ` ${r}`).join("\n");
if (!isAbsolute(path)) throw new Error(`read_source 只接受绝对路径,收到的是 "${path}"。可读的目录:\n${listing}`);
@@ -160,26 +160,23 @@ export function readSource(roots: readonly string[], path: string, offset: numbe
lines = text.split("\n");
what = `文件 ${path},共 ${lines.length} 行、${Buffer.byteLength(text)} 字节`;
}
if (offset > lines.length) throw new Error(`offset=${offset} 超出范围:${what},offset 取 1 到 ${lines.length}。`);
const shown: string[] = [];
let bytes = 0;
for (let i = offset - 1; i < lines.length && shown.length < READ_MAX_LINES; i++) {
const line = lines[i] as string;
for (const line of lines) {
if (shown.length === READ_MAX_LINES) break;
bytes += Buffer.byteLength(line) + 1;
if (bytes > READ_MAX_BYTES) {
// 单独一行就超过上限:只给这一行的开头,并明说。
// 第一行就超过上限:只给这一行的开头,并明说。
if (shown.length === 0) {
const head = Buffer.from(line).subarray(0, READ_MAX_BYTES).toString("utf8");
return `${head}\n\n[截断:第 ${offset} 行超过 ${READ_MAX_BYTES} 字节,只显示了它的开头。${what}。]`;
return `${head}\n\n[截断:第 1 行超过 ${READ_MAX_BYTES} 字节,只显示了它的开头。${what}。]`;
}
break;
}
shown.push(line);
}
const last = offset + shown.length - 1;
if (offset === 1 && last === lines.length) return shown.join("\n");
const next = last < lines.length ? `要读后面,用 offset=${last + 1} 再调一次。` : "已读到结尾。";
return `${shown.join("\n")}\n\n[截断:本次显示第 ${offset}–${last} 行(单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节)。${what}。${next}]`;
if (shown.length === lines.length) return shown.join("\n");
return `${shown.join("\n")}\n\n[截断:只显示了前 ${shown.length} 行(单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节)。${what}。]`;
}
/**
@@ -198,8 +195,6 @@ function text(value: string) {
return { content: [{ type: "text" as const, text: value }], details: {} };
}
const OFFSET = Type.Optional(Type.Integer({ minimum: 1, description: "从第几行开始读,默认 1" }));
/** 这个任务的自定义工具(实验员还有换过环境的 bash)。Pi 自带的其余几个由 Pi 按白名单自己装。 */
export function buildTools(task: SessionTask, state: SubmitState, host: Host): ToolDefinition[] {
const tools: ToolDefinition[] = [];
@@ -312,9 +307,9 @@ export function buildTools(task: SessionTask, state: SubmitState, host: Host): T
label: "读代码与资料",
description:
`读取代码或资料文件,或列出一个目录。path 用绝对路径,必须在这些目录里:${roots.join("、")}。` +
`单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节,超出时会告诉你用 offset 从第几行接着读。`,
parameters: Type.Object({ path: Type.String({ description: "绝对路径" }), offset: OFFSET }, { additionalProperties: false }),
execute: async (_id, params) => text(readSource(roots, params.path, params.offset ?? 1)),
`单次最多 ${READ_MAX_LINES} 行或 ${READ_MAX_BYTES} 字节,超出的部分不显示,结尾会写明截断。`,
parameters: Type.Object({ path: Type.String({ description: "绝对路径" }) }, { additionalProperties: false }),
execute: async (_id, params) => text(readSource(roots, params.path)),
}));
}
+6 -3
View File
@@ -7,8 +7,11 @@ import { questionsOf, taskOf, type Question } from "./questions.ts";
type Factor = Submission<"proposal">["factors"][number];
/** 判重看的因子:名称和做法。关掉时换成什么、延续哪个因子不放:它们不改变"这条提案改什么"。 */
export type DedupFactor = Pick<Factor, "name" | "method">;
/**
* 判重看的因子:名称、做法和关键设定。问题写着"设定不同就不算同一个改法",所以关键设定要给 Jev 看。
* 关掉时换成什么、延续哪个因子不放:它们不改变"这条提案改什么"。
*/
export type DedupFactor = Pick<Factor, "name" | "method" | "keySettings">;
/** 一份冻结协议的条款(docs/方法.md 4.3 ⑥"冻结协议包括"),字段与统一提交相同。 */
export type FrozenProtocol = Pick<Submission<"unification">, "factors" | "safeguards" | "metrics" | "body" | "clauses">;
@@ -77,7 +80,7 @@ export async function checkHunk(model: string, hunk: CodeHunk, protocol: Protoco
}
function dedupFactor(f: DedupFactor): { [key: string]: Json } {
return { name: f.name, method: f.method };
return { name: f.name, method: f.method, keySettings: f.keySettings };
}
function check(judgments: JevJudgment[]): JevCheck {
+3
View File
@@ -131,6 +131,8 @@ export const KINDS = {
submission: Type.Unsafe<StoredSubmission>({}),
/**
* 3 退回:被退回的那份内容原样存(它可能正是因为不合格式才被退回),连同退回理由。
* target 是这次任务的对象,与提交的 target 同义(候选、原提案、消融格,没有对象的写 null):
* 同一人在同一阶段可能对几个对象各做一次同种任务,重新发起时按它找本人这个对象的上一次退回。
* jev 是这次退回用到的 Jev 判断(见 JevUsed),判"否"的几问也一起存;没用到 Jev 的写 null。
*/
rejection: Type.Object(
@@ -138,6 +140,7 @@ export const KINDS = {
stageId: Id,
author: Id,
kind: Type.Enum(SUBMISSION_KIND_NAMES, { type: "string" }),
target: OptionalId,
submitted: AnyJson,
reason: Text,
jev: JevUsed,
+1 -1
View File
@@ -3,7 +3,7 @@ import type { DatabaseSync } from "node:sqlite";
// 账本的表结构(docs/结构.md 第四部分"全部只追加"):只有一张表,每一行是序号、种类、内容、内容的哈希。
// 表结构一改就把版本号加 1;旧文件版本号对不上时打开报错,不迁移,也不逐字核对建表语句。
export const SCHEMA_VERSION = 3;
export const SCHEMA_VERSION = 4;
/** 新文件要执行的建表语句,由 Ledger 在一个事务里执行,最后写入版本号。 */
export const CREATE_SCHEMA = [
+19 -9
View File
@@ -100,8 +100,8 @@ export function repair(d: Draft): ChecklistOf<"repair"> {
}
/**
* ⑨ 核验:没归到任何因子或保障的改动排第一(程序按位置表现算);冻结协议、位置表、指标定义、改动全文、
* Jev 对这份实现的预扫结果。因子与保障同一行的地方只作提醒。
* ⑨ 核验:没归到任何因子或保障的改动排第一(程序按位置表现算);冻结协议、位置表、指标定义、改动全文。
* 因子与保障同一行的地方只作提醒。能走到核验的实现已经过了 Jev,Jev 的判断不给核验员看(Jev 是判别条件,不是提示)。
*/
export function verification(d: Draft): ChecklistOf<"verification"> {
const candidate = d.req.object!;
@@ -139,10 +139,6 @@ export function verification(d: Draft): ChecklistOf<"verification"> {
"### 改动全文(候选相对起点代码,每段前后各 3 行上下文)",
"",
fenced(d.req.changes!.diff, "diff"),
"",
"### 预扫结果(收下这份实现时 Jev 逐段判过;概率和阈值原样列出)",
"",
...(impl.jev === null ? ["(没有)"] : impl.jev.map((j) => `- ${j.question}:概率 ${j.probability},阈值 ${j.threshold}`)),
]);
metricSection(d, p);
return { kind: "verification", unassigned: found.unassigned.map((u) => u.location), ...items };
@@ -170,17 +166,31 @@ function ablation(d: Draft, role: string, codeNote: string[]): { cell: string; k
return { cell, key, factors: items.factors, lines };
}
/** ⑯ 消融实现:从完整代码出发只关指定的因子。 */
/**
* ⑯ 消融实现:从完整代码出发只关指定的因子。核验不通过重做时(这一格已有公开的消融核验)另放退回意见:
* 最近一次核验里关错了的因子和核验员写的依据,写法同修复的退回意见。
*/
export function ablationImplementation(d: Draft): ChecklistOf<"ablationImplementation"> {
const a = ablation(d, "实验员", ["你的工作目录是完整代码的副本,改动都落在工作目录里。"]);
d.section("本任务要处理的东西", a.lines);
const verified = d.board.published("ablationVerification").filter((s) => s.target === a.cell).at(-1);
const opinion: string[] = [];
if (verified !== undefined) {
const off = new Set([a.key, ...lastAblation(d, a.cell).alsoOff]);
const wrong = verified.content.factors.filter((x) => x.off !== off.has(x.factor));
const text = (x: (typeof wrong)[number]) => `- 因子 ${x.factor} 应当${off.has(x.factor) ? "关着" : "照留"},核验员看到的是${x.off ? "关着" : "照留"}:${x.where}`;
opinion.push("### 退回意见", "", "上一次交的这一格没通过消融核验,下面是关错了的因子:", ...wrong.map(text), "");
}
d.section("本任务要处理的东西", [...opinion, ...a.lines]);
return { kind: "ablationImplementation", cell: a.cell, off: a.key, factors: a.factors };
}
/** 这一格最近一次公开的消融实现提交。 */
const lastAblation = (d: Draft, cell: string) => d.board.published("ablationImplementation").filter((s) => s.target === cell).at(-1)!.content;
/** ⑯ 消融核验:只查是不是只关了指定的因子(加实验员报的一起关的因子)。不算没有归属的改动。 */
export function ablationVerification(d: Draft): ChecklistOf<"ablationVerification"> {
const a = ablation(d, "核验员", []);
const also = d.board.published("ablationImplementation").filter((s) => s.target === a.cell).at(-1)!.content.alsoOff;
const also = lastAblation(d, a.cell).alsoOff;
d.section("这一格实际要关的因子", [
[a.key, ...also].join("、") + (also.length === 0 ? "(只关指定的因子)" : "(指定的因子加实验员报的一起关的因子)"),
]);
+8 -2
View File
@@ -16,7 +16,7 @@ import { measured, num, pct, PLACEMENT_TEXT, rangeText, resourcesText, score, SY
import type { StudyFacts } from "./inputs.ts";
import { answerPathLines } from "./locations.ts";
import { Board, validScore } from "./read.ts";
import { cellId, factorId, factorKey, ORIGINAL_BASELINE, type Settlement } from "./shapes.ts";
import { CELL_NOT_RUN, cellId, factorId, factorKey, ORIGINAL_BASELINE, type Settlement } from "./shapes.ts";
// 信念本(方法.md 6.10):程序从已公开的记录现算,不存。数字全由 rules 的函数算,这里只挑出来写成字。
// 只读这几样:已关阶段的提交、按 Board.runPublic 公开的运行、决定、结算、解封;封存、第一次预测、退回一律不读。
@@ -96,6 +96,8 @@ export function beliefBookOf(board: Board, study: StudyFacts, round: number | nu
const others = Object.keys(judged.metrics).filter((k) => k !== study.finalMetric).sort(compareIds);
if (others.length > 0) lines.push(` 其余指标:${others.map((k) => `${k} ${measured(judged.metrics[k]!, num)}`).join(";")}`);
lines.push(...answerPathLines(judged, board.code(c.id).locations!, null).map((x) => ` ${x}`));
// 合法化扣掉的分(方法.md 6.10 第 3 条)记在任务自己的附加记录里,字段由任务定,原样写出。
lines.push(` 任务的附加记录(含合法化,原样):${JSON.stringify(judged.extra)}`);
}
});
}
@@ -186,7 +188,11 @@ function cellText(board: Board, s: { round: number } & Settlement, key: string,
} else if (outcome.kind === "joint") {
parts.push(`证据不足(测的是 ${outcome.off.join(" + ")} 一起关${outcome.score === null ? "" : `,分数 ${score(outcome.score)}`})`);
} else {
parts.push(`证据不足:${board.runOf(cell)!.result!.judged!.verdictReason}`);
// 证据不足有两种来历(方法.md 6.6):启动了但没有有效分数,原因取判分;核验不通过、重做次数用完而不跑,
// 这一格没有运行,原因取 flow 写的事件。
const judged = board.runOf(cell)?.result!.judged;
const why = judged === undefined ? board.eventReason(CELL_NOT_RUN, cell) : judged === null ? "没有判分" : judged.verdictReason;
parts.push(`证据不足:${why}`);
}
parts.push(`测前合并范围 ${rangeText(out.merged)}`);
if (out.worldBits !== null) parts.push(`世界比特 ${num(out.worldBits)}`);
+2
View File
@@ -8,7 +8,9 @@ export { queryMaterial, readPrompts } from "./prompts.ts";
export {
callId,
candidateId,
CELL_NOT_RUN,
cellId,
cellNotRunEvent,
clauseKey,
critiqueId,
DECISION_KINDS,
+2 -3
View File
@@ -105,8 +105,8 @@ export function unassignedChanges(
/**
* 答案路径和保障动手次数,对照位置表写成文字(方法.md 第五部分"答案路径")。合法的答案路径是 main、
* 位置表里的因子编号、保障短名。答案出自保障时单独标一行:消融格用方法原句,正向运行换一句;
* 消融格里答案出自被关掉的因子时另写一句。offFactor 是消融格关掉的因子编号,正向运行为 null。
* 位置表里的因子编号、保障短名。答案出自保障时单独标一行:消融格用方法原句,正向运行换一句。
* offFactor 是消融格关掉的因子编号,正向运行为 null。
*/
export function answerPathLines(judged: JudgedResult, table: LocationTable, offFactor: string | null): string[] {
const factors = table.factorLocations.map((f) => f.factor);
@@ -125,7 +125,6 @@ export function answerPathLines(judged: JudgedResult, table: LocationTable, offF
: `本格答案出自保障 ${a.value},量到的可能是这条保障的效果,而不是关掉这个因子的效果。`,
);
}
if (a.value === offFactor) lines.push(`答案出自被关掉的因子 ${a.value}:这一格的代码里它本该关着。`);
}
const t = judged.safeguardTriggers;
if (t.status === "unreadable") lines.push(`保障动手次数读不到:${t.reason}`);
+7 -5
View File
@@ -48,11 +48,13 @@ export function closing(kind: SubmissionKind, object: string | null, proposalLim
const done =
kind === "proposal"
? `每调用一次 research_submit 交一条提案;本轮你最多交 ${proposalLimit} 条、至少 1 条。交完想交的几条就结束回复。`
: kind === "explanation"
? `调用一次 research_submit,explanations 里最多 ${proposalLimit} 条;看回执发现交错了,用 replaces 写那份的提交编号整份替换。`
: role === "researcher"
? "调用一次 research_submit 交上就完成了;看回执发现交错了,用 replaces 写那份的提交编号整份替换。"
: "调用一次 research_submit 交上,这个任务就结束了。";
: kind === "critique"
? "每调用一次 research_submit 交一份挑刺;可以交一份或几份,每份只针对这条提案的整体或其中一个因子。交完想交的几份就结束回复。"
: kind === "explanation"
? `调用一次 research_submit,explanations 里最多 ${proposalLimit} 条;看回执发现交错了,用 replaces 写那份的提交编号整份替换。`
: role === "researcher"
? "调用一次 research_submit 交上就完成了;看回执发现交错了,用 replaces 写那份的提交编号整份替换。"
: "调用一次 research_submit 交上,这个任务就结束了。";
return [
...(object === null ? [] : [`- 本次对象:${object}`]),
"- 调用 `research_submit`,payload 写这些字段:",
+16 -14
View File
@@ -2,7 +2,6 @@ import type { Content, Ledger, LedgerRecord, StoredSubmission } from "../ledger/
import { compareIds, type RunState } from "../rules/index.ts";
import type { JudgedResult, Range, Stage, SubmissionKind } from "../vocab/index.ts";
import {
callPrefix,
cellId,
ORIGINAL_BASELINE,
type DecisionKind,
@@ -82,6 +81,11 @@ export class Board {
return this.protocols().find((x) => x.candidate === candidate)!.protocol;
}
/** 冻结出这个候选的回应或统一(已公开的最后一版):答复不进冻结协议,从这里读。 */
answerOf(candidate: string): Sub<"response" | "unification"> {
return [...this.published("response"), ...this.published("unification")].filter((s) => s.target === candidate).sort((a, b) => a.seq - b.seq).at(-1)!;
}
/**
* 第 round 轮的留池候选(方法.md 4.3 ①):之前各轮冻结的、从没进过任何一批、基线还是本轮基线的候选。
* 只看冻结协议和选批决定,本轮判重合并之前(提案阶段)也能算。
@@ -185,21 +189,19 @@ export class Board {
}
/**
* 重新发起时,本人上一次做同一个任务被退回的原因(任务书 09 第五部分):同一任务(callId 前缀相同)
* 的各次调用里,开始与结束之间本人在这个阶段被退回的最后一条。没有为 null。别人的退回不读。
* 重新发起时,本人上一次做同一个任务被退回的原因(任务书 09 第五部分):本人在这个阶段、这种提交、
* 这个对象上被退回的最后一条。没有为 null。别人的退回、本人别的对象上的退回不读。
*/
lastRejection(stageId: string, actor: string, kind: SubmissionKind, object: string | null): string | null {
const prefix = callPrefix(stageId, actor, kind, object);
let inCall = false;
let reason: string | null = null;
for (const r of this.#records) {
if (r.kind === "callStarted" && r.content.call.startsWith(prefix)) inCall = true;
if (r.kind === "callEnded" && r.content.call.startsWith(prefix)) inCall = false;
if (inCall && r.kind === "rejection" && r.content.author === actor && r.content.stageId === stageId && r.content.kind === kind) {
reason = r.content.reason;
}
}
return reason;
const all = this.#of("rejection").map((r) => r.content);
return all.filter((x) => x.stageId === stageId && x.author === actor && x.kind === kind && x.target === object).at(-1)?.reason ?? null;
}
/** 一个对象最后一条某种事件的原因(事件种类见 shapes.ts)。没有这条事件报错:调用它的地方按方法这条事件一定写了。 */
eventReason(event: string, object: string): string {
const r = this.#of("event").filter((e) => e.content.event === event && e.content.objects.includes(object)).at(-1);
if (r === undefined) throw new Error(`账本里没有对象 ${object} 的 ${event} 事件`);
return r.content.reason;
}
// -------------------------------------------------------------------------
+9 -7
View File
@@ -1,5 +1,5 @@
import { compareIds } from "../rules/index.ts";
import type { ChecklistOf } from "../vocab/index.ts";
import { ANSWER_PATH_KEY, SAFEGUARD_TRIGGERS_KEY, type ChecklistOf } from "../vocab/index.ts";
import {
beliefSection,
codeSection,
@@ -40,14 +40,14 @@ function frame(d: Draft, own: Own, middle: () => void): void {
if (d.round >= 1) metricsSection(d);
}
/** 提案、回应、统一共用的因子规则(方法.md 第五部分"延续""指标"、4.3 ②)。 */
/** 提案、回应、统一共用的因子规则(方法.md 第五部分"延续""指标"、4.3 ②)。答案路径、保障动手次数的两个键也算占用(用户 2026-10-03 定)。 */
function factorRules(d: Draft) {
const registered = registeredMetrics(d.board).map((m) => m.id);
return {
factorLimit: d.req.study.factorLimit,
continuable: d.board.protocols().flatMap((p) => p.protocol.factors.map((_, i) => factorId(p.candidate, factorKey(i)))),
reusableMetrics: registered,
takenMetricIds: [...d.req.study.metrics.map((m) => m.id), ...registered],
takenMetricIds: [...d.req.study.metrics.map((m) => m.id), ANSWER_PATH_KEY, SAFEGUARD_TRIGGERS_KEY, ...registered],
};
}
@@ -191,6 +191,7 @@ export function unification(d: Draft): ChecklistOf<"unification"> {
/**
* ⑦ 第二次预测:冻结协议、挑刺与回应按挑刺编号配对、本人第一次给这个候选的三个数(4.3 ⑦)。
* 答复不在冻结协议里,从这个候选已公开的回应或统一读(回应的 body 是答复正文,统一的 body 已在协议里);留池候选读它冻结那一轮的(用户 2026-10-03 定)。
* 第一次预测只取本人本轮的;别人的第一次和作者自评不读。留池候选本轮不做第一次预测(4.3 ④)。
*/
export function secondPrediction(d: Draft): ChecklistOf<"secondPrediction"> {
@@ -206,14 +207,15 @@ export function secondPrediction(d: Draft): ChecklistOf<"secondPrediction"> {
},
];
frame(d, own, () => {
const p = d.board.protocol(candidate);
const critiques = d.board.published("critique");
const pairs = p.replies.flatMap((r) => {
const a = d.board.answerOf(candidate);
const pairs = a.content.replies.flatMap((r) => {
const c = critiques.find((x) => x.item === r.critique)!;
return [`### 挑刺 ${r.critique}(${c.author} 挑的)`, "", c.content.body, "", `作者的答复:${r.reply}`, ""];
});
d.section("本任务要处理的东西", [mergeLine(d, candidate), "", `### 候选 ${candidate} 的冻结协议`, "", json(p)]);
d.section("挑刺与回应", pairs.length === 0 ? ["(没有针对这个候选的挑刺)"] : [PRESUMPTION, "", ...pairs]);
const extra = a.kind === "response" ? ["### 答复正文", "", a.content.body] : [];
d.section("本任务要处理的东西", [mergeLine(d, candidate), "", `### 候选 ${candidate} 的冻结协议`, "", json(d.board.protocol(candidate))]);
d.section("挑刺与回应", [...(pairs.length === 0 ? ["(没有针对这个候选的挑刺)"] : [PRESUMPTION, "", ...pairs]), ...extra]);
});
return { kind: "secondPrediction", candidate };
}
+13 -17
View File
@@ -10,6 +10,7 @@ import type {
adopt,
settleAblation,
} from "../rules/index.ts";
import type { Content } from "../ledger/index.ts";
import type { Submission } from "../vocab/index.ts";
// 账本里只存成 Json 的几样内容长什么样、编号怎么起(用户 2026-10-03 决定 2):materials 读它们,flow 照着写。
@@ -45,31 +46,19 @@ export function splitCell(cell: string): { candidate: string; key: string } {
}
/** 解释:第 round 轮作者交的第 i 条(从 1 数)。结算里判确认、否定用它。 */
export const explanationId = (round: number, author: string, i: number): string => `r${round}-${author}-x${i}`;
/**
* 模型调用:同一个任务(阶段、谁、哪种提交、对象)第 attempt 次发起。
* 重新发起时 materials 按这个前缀找回上一次在哪段记录之间被退回(结构.md 第四部分第 10 种记录)。
*/
/** 模型调用:同一个任务(阶段、谁、哪种提交、对象)第 attempt 次发起。 */
export const callId = (stageId: string, actor: string, kind: string, object: string | null, attempt: number): string =>
`${callPrefix(stageId, actor, kind, object)}${attempt}`;
export const callPrefix = (stageId: string, actor: string, kind: string, object: string | null): string =>
`${stageId}.${actor}.${kind}.${object ?? "none"}.`;
`${stageId}.${actor}.${kind}.${object ?? "none"}.${attempt}`;
// ---------------------------------------------------------------------------
// 冻结协议(账本记录 protocol.protocol,方法.md 4.3 ⑥)
// ---------------------------------------------------------------------------
/**
* 一个候选的冻结协议:原提案合上回应改过的部分(或统一后的整份),加上答复。
* baseline 是它出发的基线对象编号;round 是冻结的轮次;proposals 是合并进来的全部原提案。
* 留池候选沿用这一份,不重写(方法.md 4.3 ①)。
* 一个候选的冻结协议(方法.md 4.3 ⑥"冻结协议包括"),拼法在 submissions;答复留在回应或统一的提交里(用户 2026-10-03 定)。
* baseline 是出发的基线对象编号,round 是冻结的轮次,proposals 是合并进来的全部原提案。留池候选沿用这一份(4.3 ①)。
*/
export type FrozenProtocol = Pick<Submission<"unification">, "factors" | "safeguards" | "metrics" | "body" | "clauses" | "replies"> & {
round: number;
baseline: string;
proposals: string[];
/** 回应或统一的答复正文。 */
replyBody: string;
};
export type FrozenProtocol = Pick<Submission<"unification">, "factors" | "safeguards" | "metrics" | "body" | "clauses"> & { round: number; baseline: string; proposals: string[] };
// ---------------------------------------------------------------------------
// 冻结代码的位置表(账本记录 code.locations)
@@ -114,3 +103,10 @@ export type Settlement = {
input: SettleInput;
output: ReturnType<typeof settleAblation>;
};
/**
* 事件(账本记录 event):一格消融核验不通过、重做次数用完,这一格不跑、记"证据不足"(方法.md 4.3 ⑯、6.6)。
* 这一格没有运行,信念本从这条事件读原因。objects 只有这一格的编号。
*/
export const CELL_NOT_RUN = "cellNotRun";
export const cellNotRunEvent = (cell: string, reason: string): Content<"event"> => ({ event: CELL_NOT_RUN, reason, objects: [cell] });
+2 -1
View File
@@ -9,7 +9,8 @@ import { adoptionLine } from "./forward.ts";
/**
* 一格的结局(6.6"一格的几种结局"):
* - valid:有效分数(含合法化后的分数),只有它进标签的求和、判确认与否定、记比特和惊讶;
* - insufficient:证据不足(启动了,但跑崩、超时、没有产物、修不了或判为违规);
* - insufficient:证据不足:启动了(扣了次数),但跑崩、超时、没有产物、修不了或判为违规;
* 或者消融核验不通过、重做次数用完,这一格不启动、不扣次数;
* - untested:没测;
* - cannotRun:跑不起来(不启动、不扣次);
* - joint:一起关,off 是实际关掉的全部因子;score 是那次运行的有效分数,没有时为 null。
+193
View File
@@ -0,0 +1,193 @@
import type { ChecklistOf, Range, Submission } from "../vocab/index.ts";
import type { AblationCode, ForwardCode, Incoming, ProtocolBody } from "./types.ts";
// 程序检查:vocab 字段定义管不到的跨字段规则(WORK_LOG"留给 submissions 的跨字段规则"14 条,
// 加任务书 10 第四部分的不许改的文件、代码存不了、指标编号重名、提案条数)。
// 每条规则交回零到几行问题,一份提交的问题一次列全。编号写错时列出对号清单里该写的全部编号:
// 只说"不对",模型拿不到可对照的写法(参考项目真实运行 15-tight-2 第 3 轮,提交 378fe68)。
/** 一份提交的全部程序检查。回应、统一查拼好的协议(protocol),其余种类 protocol 为 null。 */
export function programProblems(inc: Incoming, protocol: ProtocolBody | null): string[] {
switch (inc.kind) {
case "entry":
case "discussion":
case "wrapUp":
return [];
case "claim":
return claimProblems(inc.content, inc.checklist);
case "proposal":
return [
...(inc.known.accepted + 1 > inc.checklist.maxProposals
? [`本阶段你已经收下 ${inc.known.accepted} 条提案,每人最多 ${inc.checklist.maxProposals} 条:要改哪一条,就在 replaces 里写它的提交编号整份替换,不要另交`]
: []),
...factorProblems(inc.content, inc.checklist),
...increasing("authorEstimate", inc.content.authorEstimate),
];
case "firstPrediction":
case "secondPrediction":
return increasing("range", inc.content.range);
case "critique":
return within("target", [inc.content.target], ["whole", ...inc.checklist.factors]);
case "response":
return [...covers("replies 的挑刺编号", inc.content.replies.map((r) => r.critique), inc.checklist.critiques), ...factorProblems(protocol!, inc.checklist)];
case "unification":
return [
...covers("replies 的挑刺编号", inc.content.replies.map((r) => r.critique), inc.checklist.critiques),
...covers("sources 的原提案编号", inc.content.sources.map((s) => s.proposal), inc.checklist.sources),
...factorProblems(protocol!, inc.checklist),
];
case "implementation":
case "repair":
return [...locationProblems(inc.content, inc.checklist), ...codeProblems(inc.known.code)];
case "verification":
return verificationProblems(inc.content, inc.checklist);
case "explanation":
return explanationProblems(inc.content, inc.checklist);
case "ablationImplementation":
return ablationProblems(inc.content, inc.checklist, inc.known.code);
case "ablationVerification":
return covers("factors 的因子", inc.content.factors.map((f) => f.factor), inc.checklist.factors);
}
}
// ---------------------------------------------------------------------------
// 各种提交
// ---------------------------------------------------------------------------
/** 规则 7:志愿至少 min(N, 入口总数) 个、不重复、编号都在入口清单里(方法.md 4.2)。 */
function claimProblems(c: Submission<"claim">, k: ChecklistOf<"claim">): string[] {
return [
...(c.choices.length < k.minChoices ? [`choices 至少要排 ${k.minChoices} 个入口,你排了 ${c.choices.length} 个`] : []),
...within("choices", c.choices, k.entries),
...repeated(c.choices).map((x) => `choices 里 ${x} 写了不止一次`),
];
}
/**
* 提案、回应、统一共用(方法.md 第五部分"延续""指标"、4.3 ②⑥):
* 规则 2 因子数上限;3 两个因子不延续同一个、延续的编号看得到;4 保障短名;10 指标的引用与新登记。
*/
function factorProblems(p: Pick<ProtocolBody, "factors" | "safeguards" | "metrics">, k: ChecklistOf<"proposal" | "response" | "unification">): string[] {
const continued = p.factors.flatMap((f) => (f.continues === null ? [] : [f.continues]));
const defined = p.metrics.defined.map((m) => m.id);
return [
...(p.factors.length > k.factorLimit
? [`factors 有 ${p.factors.length} 个因子,超过上限 ${k.factorLimit} 个:多出来的留到下一轮,不要几个捆成一个`]
: []),
...repeated(continued).map((x) => `有两个以上因子的 continues 都写了 ${x}:同一份提案里两个因子不能延续同一个因子`),
...within("continues", unique(continued), k.continuable).map((x) => `${x};新因子写 null`),
...unique(p.safeguards)
.filter((s) => s === "main" || /^p[1-9][0-9]*$/.test(s))
.map((s) => `保障短名 ${s} 不能用:不能叫 main,也不能写成 p1、p2 这种因子编号的样子`),
...repeated(p.safeguards).map((s) => `safeguards 里 ${s} 写了不止一次`),
...within("metrics.reused", p.metrics.reused, k.reusableMetrics),
...repeated(defined).map((x) => `metrics.defined 里编号 ${x} 写了不止一次`),
...unique(defined)
.filter((x) => k.takenMetricIds.includes(x))
.map((x) => `metrics.defined 的编号 ${x} 已被占用(已占用的是 ${k.takenMetricIds.join("、")}):换一个编号;要用别人登记过的就写进 metrics.reused`),
];
}
/** 规则 11:位置表覆盖每个因子和每条保障;代码位置起始行不大于结束行(方法.md 4.3 ⑨)。 */
function locationProblems(c: Submission<"implementation">, k: ChecklistOf<"implementation" | "repair">): string[] {
const all = [...c.factorLocations.flatMap((f) => f.locations), ...c.safeguardLocations.flatMap((s) => s.locations), ...c.unassigned.map((u) => u.location)];
return [
...covers("factorLocations 的因子", c.factorLocations.map((f) => f.factor), k.factors),
...covers("safeguardLocations 的保障", c.safeguardLocations.map((s) => s.safeguard), k.safeguards),
...unique(all)
.filter((loc) => {
const m = /:([0-9]+)-([0-9]+)$/.exec(loc);
return m !== null && Number(m[1]) > Number(m[2]);
})
.map((loc) => `代码位置 ${loc} 的起始行大于结束行`),
];
}
/** 规则 12:核验的条款、因子、保障逐项全覆盖;没有归属的改动与材料列出的逐条对应(方法.md 4.3 ⑨)。 */
function verificationProblems(c: Submission<"verification">, k: ChecklistOf<"verification">): string[] {
return [
...covers("clauses 的实现条款", c.clauses.map((x) => x.clause), k.clauses),
...covers("factors 的因子", c.factors.map((x) => x.factor), k.factors),
...covers("safeguards 的保障", c.safeguards.map((x) => x.safeguard), k.safeguards),
...covers("unassigned 的改动位置", c.unassigned.map((x) => x.location), k.unassigned),
];
}
/**
* 规则 5、6、1:解释最多 M 条;每条都交 cells、恰好覆盖材料列出的格,三个数严格递增(方法.md 4.3 ⑭)。
* 用户 2026-10-03 定:只按完整流程全开实现,一律要求交 cells;关掉消融的情形以后做开关时再补。
*/
function explanationProblems(c: Submission<"explanation">, k: ChecklistOf<"explanation">): string[] {
return [
...(c.explanations.length > k.maxExplanations ? [`交了 ${c.explanations.length} 条解释,最多 ${k.maxExplanations} 条`] : []),
...c.explanations.flatMap((e, i) =>
e.cells === undefined
? [`第 ${i + 1} 条解释没交 cells:每条解释都要对 ${k.cells.join("、")} 每一格给三个数`]
: [
...covers(`第 ${i + 1} 条解释 cells 的格`, e.cells.map((x) => x.factor), k.cells),
...e.cells.flatMap((x) => increasing(`第 ${i + 1} 条解释 ${x.factor} 格的 range`, x.range)),
],
),
];
}
/**
* 规则 13:写了 cannotRun 时 alsoOff 为空;alsoOff 不含本格要关的因子、都在因子清单里、不重复(方法.md 4.3 ⑯)。
* 跑得起来的另查代码:运行的规矩和正向运行相同,改了不许改的文件同样退回。
*/
function ablationProblems(c: Submission<"ablationImplementation">, k: ChecklistOf<"ablationImplementation">, code: AblationCode | null): string[] {
return [
...(c.cannotRun !== null && c.alsoOff.length > 0 ? ["写了 cannotRun(这一格跑不起来)时 alsoOff 要交空数组:两种做法只能选一种"] : []),
...(c.alsoOff.includes(k.off) ? [`alsoOff 不能写本格要关的因子 ${k.off},只写还要一起关的别的因子`] : []),
...within("alsoOff", c.alsoOff.filter((x) => x !== k.off), k.factors.filter((x) => x !== k.off)),
...repeated(c.alsoOff).map((x) => `alsoOff 里 ${x} 写了不止一次`),
...(c.cannotRun === null ? codeProblems(code!) : []),
];
}
/** 代码存不了、改了不许改的文件(方法.md 4.3 ⑨)。 */
function codeProblems(code: ForwardCode | AblationCode): string[] {
if (!code.saved) return [`代码存不成版本:${code.path}(${code.reason})。改掉它再交`];
return code.protectedFiles.length === 0 ? [] : [`改了不许改的文件:${code.protectedFiles.join("、")}。把它们改回本批基线的样子再交`];
}
// ---------------------------------------------------------------------------
// 共用
// ---------------------------------------------------------------------------
/** 规则 1:三个数严格递增(方法.md 第五部分"三个数")。 */
function increasing(field: string, r: Range): string[] {
return r.q1 < r.q5 && r.q5 < r.q9 ? [] : [`${field} 的三个数要严格递增 q1 < q5 < q9,你写的是 ${r.q1} / ${r.q5} / ${r.q9}`];
}
/** 一组编号必须恰好覆盖 expected:不多、不少、不重复。只写出现了的几段。 */
function covers(field: string, actual: readonly string[], expected: readonly string[]): string[] {
const unknown = unique(actual.filter((x) => !expected.includes(x)));
const missing = expected.filter((x) => !actual.includes(x));
const twice = repeated(actual);
if (unknown.length + missing.length + twice.length === 0) return [];
return [
[
`${field}应当是 ${expected.length === 0 ? "没有(交空数组)" : expected.join("、")}`,
...(unknown.length > 0 ? [`你写的 ${unknown.join("、")} 不在里面`] : []),
...(missing.length > 0 ? [`还缺 ${missing.join("、")}`] : []),
...(twice.length > 0 ? [`${twice.join("、")} 写了不止一次`] : []),
].join(";"),
];
}
/** 一组编号都要在 allowed 里(可以只写其中几个)。 */
function within(field: string, actual: readonly string[], allowed: readonly string[]): string[] {
const unknown = unique(actual.filter((x) => !allowed.includes(x)));
if (unknown.length === 0) return [];
return [`${field} 只能写 ${allowed.length === 0 ? "(现在没有可写的编号)" : allowed.join("、")};你写的 ${unknown.join("、")} 不在里面`];
}
function unique(xs: readonly string[]): string[] {
return [...new Set(xs)];
}
/** 出现了不止一次的值,各列一次。 */
function repeated(xs: readonly string[]): string[] {
return unique(xs.filter((x, i) => xs.indexOf(x) !== i));
}
+14
View File
@@ -0,0 +1,14 @@
// submissions:一份提交收下还是退回,先程序检查、再问 Jev(docs/结构.md 第二部分);
// 另有冻结协议的拼法,和核验、消融核验收下之后通过没有。不读也不写账本。
export { ablationOutcome, verificationOutcome } from "./outcomes.ts";
export { protocolBody } from "./protocol.ts";
export { receive } from "./receive.ts";
export {
type AblationCode,
type ForwardCode,
type Incoming,
type KnownOf,
type OriginalProposal,
type ProtocolBody,
type Verdict,
} from "./types.ts";
+36
View File
@@ -0,0 +1,36 @@
import type { Submission } from "../vocab/index.ts";
// 一份已经收下的核验、消融核验的结论(任务书 10 第三部分第 2、3 条):判的是"收下之后通过没有",不是"收不收"。
/**
* 核验通过没有(方法.md 4.3 ⑨):任何一项没落实,或者任何一条没有归属的改动判为越界,就不通过。
* 没落实的条款、因子、保障和越界的改动分开列:保障短名可能和别的编号同形。
*/
export function verificationOutcome(v: Submission<"verification">): {
passed: boolean;
clauses: string[];
factors: string[];
safeguards: string[];
outOfScope: string[];
} {
const clauses = v.clauses.filter((x) => !x.found).map((x) => x.clause);
const factors = v.factors.filter((x) => !x.found).map((x) => x.factor);
const safeguards = v.safeguards.filter((x) => !x.found).map((x) => x.safeguard);
const outOfScope = v.unassigned.filter((x) => x.outOfScope).map((x) => x.location);
return { passed: clauses.length + factors.length + safeguards.length + outOfScope.length === 0, clauses, factors, safeguards, outOfScope };
}
/**
* 消融核验通过没有(方法.md 4.3 ⑯):这一格要关的因子(off)和实验员报的一起关的因子(alsoOff)都写了"关着",
* 其余因子都写了"照留",才算通过。leftOn 是该关却照留的,wronglyOff 是该留却关了的。
*/
export function ablationOutcome(
v: Submission<"ablationVerification">,
off: string,
alsoOff: readonly string[],
): { passed: boolean; leftOn: string[]; wronglyOff: string[] } {
const shouldBeOff = new Set([off, ...alsoOff]);
const leftOn = v.factors.filter((f) => shouldBeOff.has(f.factor) && !f.off).map((f) => f.factor);
const wronglyOff = v.factors.filter((f) => !shouldBeOff.has(f.factor) && f.off).map((f) => f.factor);
return { passed: leftOn.length + wronglyOff.length === 0, leftOn, wronglyOff };
}
+25
View File
@@ -0,0 +1,25 @@
import type { Submission } from "../vocab/index.ts";
import type { OriginalProposal, ProtocolBody } from "./types.ts";
/**
* 拼冻结协议的主体,只在这一处拼(任务书 10 第四部分"回应怎么拼成冻结协议")。
* 回应:因子、保障、指标、正文四项,changes 里写了哪一项就整项换成新的,没写的用原提案的;实现条款取回应自己的。
* 统一:直接用统一提交自己的字段,不叠原提案。答复不进协议(用户 2026-10-03 定)。
*/
export function protocolBody(
from: { kind: "response"; content: Submission<"response">; original: OriginalProposal } | { kind: "unification"; content: Submission<"unification"> },
): ProtocolBody {
if (from.kind === "unification") {
const { factors, safeguards, metrics, body, clauses } = from.content;
return { factors, safeguards, metrics, body, clauses };
}
const { changes, clauses } = from.content;
const o = from.original;
return {
factors: changes.factors ?? o.factors,
safeguards: changes.safeguards ?? o.safeguards,
metrics: changes.metrics ?? o.metrics,
body: changes.body ?? o.body,
clauses,
};
}
+71
View File
@@ -0,0 +1,71 @@
import { checkHunk, checkProtocol, type CodeHunk } from "../jev/index.ts";
import type { JevJudgment } from "../vocab/index.ts";
import { programProblems } from "./checks.ts";
import { protocolBody } from "./protocol.ts";
import type { Incoming, ProtocolBody, Verdict } from "./types.ts";
// 判一份提交收不收(任务书 10 第二部分第 4、5 步):先程序检查,有一项不过就退回、不问 Jev;
// 全过了再问 Jev,几问全问完再决定,有一问判"是"就退回。收不收只在这一处判(结构.md 第二部分"几条边界")。
// Jev 调不通、回答读不懂时 jev 报错,这里不接住:那不是模型交得不对,flow 按内部错误停下。
/** 判一份提交。回应、统一先拼好冻结协议主体,程序检查、问 Jev、交回都用这同一份。 */
export async function receive(inc: Incoming): Promise<Verdict> {
const protocol =
inc.kind === "response"
? protocolBody({ kind: "response", content: inc.content, original: inc.known.original })
: inc.kind === "unification"
? protocolBody({ kind: "unification", content: inc.content })
: null;
const problems = programProblems(inc, protocol);
if (problems.length > 0) return { accepted: false, reason: ["这份提交没有收下,要改的地方:", ...problems.map((p) => `- ${p}`)].join("\n"), jev: null };
const asked = await askJev(inc, protocol);
if (asked === null) return { accepted: true, jev: null, protocol };
if (asked.flagged.length > 0) {
return { accepted: false, reason: ["这份提交没有收下,Jev 判出下面几处有问题:", ...asked.flagged].join("\n\n"), jev: asked.judgments };
}
return { accepted: true, jev: asked.judgments, protocol };
}
/**
* 问 Jev(方法.md 4.3 ⑥⑨):回应、统一对拼好的协议问冻结条款三问;实现、修复每个差异块问代码改动五问,顺序发。
* 没有差异块的改动(二进制、只改可执行位、空文件增删)Jev 看不到内容,不问;一块都没有就等于没问,交回 null。
* 消融实现不问:冻结协议里没有登记"关掉以后"的随机性和停止条件,五问没有对照(照参考项目)。
* flagged 是退回原因里每一处的文字:只列判"是"的几问,问题名用 jev 的英文名(用户 2026-10-03 定);判"是"只看 jev 交回的 yes。
*/
async function askJev(inc: Incoming, protocol: ProtocolBody | null): Promise<{ judgments: JevJudgment[]; flagged: string[] } | null> {
if (inc.kind === "response" || inc.kind === "unification") {
const c = await checkProtocol(inc.jevModel, protocol!);
return { judgments: c.judgments, flagged: c.flagged ? [`- 冻结协议:${flaggedText(c.judgments)}`] : [] };
}
if (inc.kind !== "implementation" && inc.kind !== "repair") return null;
// 走到这里程序检查已过,代码一定存成了版本。
const code = inc.known.code as Extract<typeof inc.known.code, { saved: true }>;
if (code.hunks.length === 0) return null;
const judgments: JevJudgment[] = [];
const flagged: string[] = [];
const nth = new Map<string, number>();
for (const hunk of code.hunks) {
nth.set(hunk.path, (nth.get(hunk.path) ?? 0) + 1);
const c = await checkHunk(inc.jevModel, hunk, code.protocol);
judgments.push(...c.judgments);
if (c.flagged) flagged.push(hunkText(hunk, nth.get(hunk.path)!, c.judgments));
}
return { judgments, flagged };
}
/** 判"是"的那几问:问题名、概率、阈值。 */
function flaggedText(judgments: readonly JevJudgment[]): string {
return judgments
.filter((j) => j.yes)
.map((j) => `${j.question} 概率 ${j.probability}(阈值 ${j.threshold})`)
.join(";");
}
const CHANGE = { added: "新增的文件", deleted: "删掉的文件", modified: "改动的文件" } as const;
/** 一个差异块的退回文字:哪个文件的第几块、判"是"的几问,附差异原文。原文里有 ``` 时围栏加长,免得提前收尾。 */
function hunkText(hunk: CodeHunk, n: number, judgments: readonly JevJudgment[]): string {
const longest = Math.max(0, ...(hunk.text.match(/`+/g) ?? []).map((s) => s.length));
const fence = "`".repeat(Math.max(3, longest + 1));
return [`- ${hunk.path}(${CHANGE[hunk.change]})的第 ${n} 块:${flaggedText(judgments)}`, "", `${fence}diff`, hunk.text, fence].join("\n");
}
+52
View File
@@ -0,0 +1,52 @@
import type { CodeHunk, ProtocolRules } from "../jev/index.ts";
import type { ChecklistOf, JevJudgment, Submission, SubmissionKind } from "../vocab/index.ts";
// submissions 收的和交回的东西(任务书 10 第三部分)。submissions 只依赖 jev 和 vocab,
// workspace 的存版本结果、materials 的冻结协议在这里按相同形状另写,flow 赋值时由 tsc 对齐。
/**
* 冻结协议的主体(方法.md 4.3 ⑥"冻结协议包括"):因子、保障、指标、正文、实现条款。
* 不带答复:答复留在回应或统一的提交记录里(用户 2026-10-03 定)。flow 补上轮次、基线、合并了哪些原提案,写成 materials 的 FrozenProtocol。
*/
export type ProtocolBody = Pick<Submission<"unification">, "factors" | "safeguards" | "metrics" | "body" | "clauses">;
/** 回应拼协议时叠在下面的那条原提案(作者自评不进协议)。 */
export type OriginalProposal = Pick<Submission<"proposal">, "factors" | "safeguards" | "metrics" | "body">;
/** 实验员的工作目录存不成版本:workspace 给的路径和原因(例如符号链接)。 */
type NotSaved = { saved: false; path: string; reason: string };
/**
* 实现、修复的代码:存成了版本就带改了的不许改的文件(tasks 的 protectedChanges)、
* 和本批基线比出的差异块(jev 的 codeHunks 切好的),以及给 Jev 对照的协议正文和要采集的指标。
*/
export type ForwardCode = NotSaved | { saved: true; protectedFiles: string[]; hunks: CodeHunk[]; protocol: ProtocolRules };
/** 消融实现的代码:只查存没存成、改没改不许改的文件。 */
export type AblationCode = NotSaved | { saved: true; protectedFiles: string[] };
/** 按种类区分的"提交之后才知道的东西",没有的种类为 null。 */
type KnownByKind = {
/** 本人本阶段已经收下几条提案(替换时不算被替换的那条)。 */
proposal: { accepted: number };
/** 作者那条原提案。 */
response: { original: OriginalProposal };
implementation: { code: ForwardCode };
repair: { code: ForwardCode };
/** 交了"跑不起来"(cannotRun 不是 null)时 flow 不存代码,code 为 null。 */
ablationImplementation: { code: AblationCode | null };
};
export type KnownOf<K extends SubmissionKind> = K extends keyof KnownByKind ? KnownByKind[K] : null;
/** 交给 submissions 判的一份提交,以 kind 区分:内容、这次任务的对号清单、提交之后才知道的东西、Jev 的模型名。 */
export type Incoming = {
[K in SubmissionKind]: { kind: K; content: Submission<K>; checklist: ChecklistOf<K>; known: KnownOf<K>; jevModel: string };
}[SubmissionKind];
/**
* 判的结论。jev 是用到的全部 Jev 判断(判"否"的也在),没问 Jev 的为 null(账本的 JevUsed 要求至少一条)。
* 收下时 protocol 只有回应、统一才有,其余为 null。
*/
export type Verdict =
| { accepted: true; jev: JevJudgment[] | null; protocol: ProtocolBody | null }
| { accepted: false; reason: string; jev: JevJudgment[] | null };
+4 -2
View File
@@ -6,7 +6,7 @@ import { HypothesisMetric } from "./runs.ts";
/**
* 每种提交的字段定义(方法.md 第五部分)。同一份定义同时是类型、收件时的格式检查、
* 发给模型的提交工具参数,所以字段说明写给模型看,每个字段都写明出口。
* 这里只管单个字段长什么样;跨字段的规则(q₁ ≤ q₅ ≤ q₉、因子数上限、覆盖是否齐全等)
* 这里只管单个字段长什么样;跨字段的规则(q₁ < q₅ < q₉ 严格递增、因子数上限、覆盖是否齐全等)
* 在 submissions 模块。
* 程序分配的编号(候选、挑刺、入口编号)不让模型填;因子、实现条款的编号就是它在清单里的位置。
*/
@@ -479,7 +479,9 @@ export const VerificationSubmission = Type.Object(
),
body: Type.String({
...TEXT,
description: "说明,Markdown:逐项结果之外需要说明的事。没有要补充的就写\"无\"",
description:
"说明,Markdown:逐项结果之外需要说明的事。材料里列出的同一行既属于因子又属于保障的地方,在这里各写一句提醒(只是提醒,不判越界)。" +
"没有要补充的就写\"无\"",
}),
},
{