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:
+10
-15
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 ? "(只关指定的因子)" : "(指定的因子加实验员报的一起关的因子)"),
|
||||
]);
|
||||
|
||||
@@ -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)}`);
|
||||
|
||||
@@ -8,7 +8,9 @@ export { queryMaterial, readPrompts } from "./prompts.ts";
|
||||
export {
|
||||
callId,
|
||||
candidateId,
|
||||
CELL_NOT_RUN,
|
||||
cellId,
|
||||
cellNotRunEvent,
|
||||
clauseKey,
|
||||
critiqueId,
|
||||
DECISION_KINDS,
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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
@@ -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] });
|
||||
|
||||
@@ -9,7 +9,8 @@ import { adoptionLine } from "./forward.ts";
|
||||
/**
|
||||
* 一格的结局(6.6"一格的几种结局"):
|
||||
* - valid:有效分数(含合法化后的分数),只有它进标签的求和、判确认与否定、记比特和惊讶;
|
||||
* - insufficient:证据不足(启动了,但跑崩、超时、没有产物、修不了或判为违规);
|
||||
* - insufficient:证据不足:启动了(扣了次数),但跑崩、超时、没有产物、修不了或判为违规;
|
||||
* 或者消融核验不通过、重做次数用完,这一格不启动、不扣次数;
|
||||
* - untested:没测;
|
||||
* - cannotRun:跑不起来(不启动、不扣次);
|
||||
* - joint:一起关,off 是实际关掉的全部因子;score 是那次运行的有效分数,没有时为 null。
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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";
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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:逐项结果之外需要说明的事。材料里列出的同一行既属于因子又属于保障的地方,在这里各写一句提醒(只是提醒,不判越界)。" +
|
||||
"没有要补充的就写\"无\"",
|
||||
}),
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user