feat: 工具链与 vocab 模块(任务书 01)
- npm 包、TypeScript、node --test、dependency-cruiser;结构.md 第六部分的检查规则一次写全,临时违规实测均报错。 - vocab:基础类型与编号格式、规范序列化与哈希、角色/阶段/提交名单、指标与运行定义及结果、模型用量、16 种提交的 TypeBox 字段定义。 - 测试:规范序列化预期串手写,哈希预期值用 sha256sum 算出;每种提交一份合法样例。 - 结构.md 第八部分补 vocab 对外内容;WORK_LOG 记下用户决定。 用户决定(2026-10-01):"同意 A,vocab 上限改为 1100 行";"同意 A:解释的 cells 去掉 minItems,改为可选";"3、4、5、6:同意保留你的写法";答案路径与保障动手次数"tasks 只记读到的原始内容",对表由以后有位置表的模块现算。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { Type, type Static } from "typebox";
|
||||
|
||||
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
|
||||
|
||||
/**
|
||||
* 编号:会被拼进文件路径和命令行,所以只许字母、数字和 `._-`,首字符必须是字母或数字
|
||||
* (挡住 `..` 这类路径和 `-x` 这类会被当成命令行选项的写法),最长 128 个字符。
|
||||
*/
|
||||
export const Id = Type.String({ pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$" });
|
||||
|
||||
/** 指标方向:越大越好、越小越好,只有这两种。 */
|
||||
export const DIRECTIONS = ["maximize", "minimize"] as const;
|
||||
export const Direction = Type.Enum(DIRECTIONS, { type: "string" });
|
||||
export type Direction = Static<typeof Direction>;
|
||||
|
||||
/**
|
||||
* 测到 / 测不到。测到就带值,测不到就带原因。
|
||||
* 凡是可能测不到的量(资源用量、分数、指标、价格)都用它,不用 0 或 null 冒充测不到:
|
||||
* 崩溃的运行若把 CPU 秒记成 0,看起来就和一次很省的运行一样,没人会发现。
|
||||
*/
|
||||
export const Measured = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
status: Type.Literal("measured"),
|
||||
value: Type.Number(),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
status: Type.Literal("unmeasured"),
|
||||
reason: Type.String({ minLength: 1 }),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
),
|
||||
]);
|
||||
export type Measured = Static<typeof Measured>;
|
||||
|
||||
/**
|
||||
* 规范序列化:内容相同就一定得到同一个字符串。
|
||||
* - 对象的键按 JavaScript 默认的字符串顺序(UTF-16 码元)排序后再拼;
|
||||
* - 数字必须是有限数(JSON.stringify 会把 NaN 悄悄写成 null);-0 和 JSON.stringify 一样写成 0;
|
||||
* - 字符串的转义与 JSON.stringify 相同;
|
||||
* - 只接受纯 JSON:对象的原型必须是 Object.prototype,undefined、函数、bigint 等一律报错。
|
||||
*/
|
||||
export function canonical(value: unknown): string {
|
||||
if (value === null || typeof value === "boolean" || typeof value === "string") {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
if (!Number.isFinite(value)) throw new Error(`规范序列化只接受有限数,收到 ${value}`);
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const items: string[] = [];
|
||||
// for...of 会把稀疏数组的空位读成 undefined,从而报错,不会拼出不合法的 JSON。
|
||||
for (const item of value) items.push(canonical(item));
|
||||
return `[${items.join(",")}]`;
|
||||
}
|
||||
if (typeof value !== "object" || Object.getPrototypeOf(value) !== Object.prototype) {
|
||||
const what = typeof value === "object" ? "原型不是 Object.prototype 的对象" : typeof value;
|
||||
throw new Error(`规范序列化只接受纯 JSON 值,收到 ${what}`);
|
||||
}
|
||||
const record = value as Record<string, unknown>;
|
||||
const fields = Object.keys(record)
|
||||
.sort()
|
||||
.map((key) => `${JSON.stringify(key)}:${canonical(record[key])}`);
|
||||
return `{${fields.join(",")}}`;
|
||||
}
|
||||
|
||||
/** 哈希:规范序列化结果(UTF-8)的 sha256,64 位小写十六进制。 */
|
||||
export function hash(value: unknown): string {
|
||||
return createHash("sha256").update(canonical(value), "utf8").digest("hex");
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// vocab:各模块共用的类型、名单、提交字段定义,以及规范序列化与哈希两个函数(docs/结构.md 第二部分)。
|
||||
export { canonical, hash, Direction, DIRECTIONS, Id, Measured, type Json } from "./basics.ts";
|
||||
export {
|
||||
ROLES,
|
||||
STAGES,
|
||||
SUBMISSION_KINDS,
|
||||
type Role,
|
||||
type Stage,
|
||||
type SubmissionKind,
|
||||
} from "./roster.ts";
|
||||
export {
|
||||
ANSWER_PATH_KEY,
|
||||
AnswerPath,
|
||||
HypothesisMetric,
|
||||
JudgedResult,
|
||||
METRIC_LAYERS,
|
||||
MetricDefinition,
|
||||
MISSING_POLICIES,
|
||||
MISSING_POLICY_EFFECTS,
|
||||
ModelUsage,
|
||||
ProcessResult,
|
||||
RunDefinition,
|
||||
SAFEGUARD_TRIGGERS_KEY,
|
||||
SafeguardTriggers,
|
||||
VERDICTS,
|
||||
type MetricLayer,
|
||||
type MissingPolicy,
|
||||
type Verdict,
|
||||
} from "./runs.ts";
|
||||
export { Range, SUBMISSION_SCHEMAS, type Submission } from "./submissions.ts";
|
||||
@@ -0,0 +1,50 @@
|
||||
/** 三个角色(方法.md 第三部分):研究者、实验员、核验员。 */
|
||||
export const ROLES = ["researcher", "experimenter", "verifier"] as const;
|
||||
export type Role = (typeof ROLES)[number];
|
||||
|
||||
/**
|
||||
* 有人交提交的阶段,按方法.md 4.2、4.3 的先后排。
|
||||
* 程序自己做的步骤(分配、判重、选批、采纳、选格、结算等)不在这里,步骤清单归 config。
|
||||
*/
|
||||
export const STAGES = [
|
||||
"entryDraft", // 0.1 闭门写入口
|
||||
"discussion", // 0.2 讨论,可配置为 1 或 2 次,每次都是这个阶段
|
||||
"claim", // 0.3 认领
|
||||
"proposal", // ② 提案
|
||||
"firstPrediction", // ④ 第一次预测
|
||||
"critique", // ⑤ 挑刺
|
||||
"response", // ⑥ 回应与统一
|
||||
"secondPrediction", // ⑦ 第二次预测
|
||||
"implementation", // ⑨ 实现,以及核验不通过后的修复
|
||||
"verification", // ⑨ 核验
|
||||
"explanation", // ⑭ 解释
|
||||
"ablationImplementation", // ⑯ 消融实现
|
||||
"ablationVerification", // ⑯ 消融核验
|
||||
"wrapUp", // ⑱ 收尾
|
||||
] as const;
|
||||
export type Stage = (typeof STAGES)[number];
|
||||
|
||||
/**
|
||||
* 每种提交属于哪个阶段、由哪个角色交(方法.md 第五部分)。
|
||||
* ledger、materials、submissions、flow、agents 都从这张表取,不在别处另写。
|
||||
* 每种提交的字段定义在 submissions.ts 的 SUBMISSION_SCHEMAS,键与这张表相同。
|
||||
*/
|
||||
export const SUBMISSION_KINDS = {
|
||||
entry: { stage: "entryDraft", role: "researcher" },
|
||||
discussion: { stage: "discussion", role: "researcher" },
|
||||
claim: { stage: "claim", role: "researcher" },
|
||||
proposal: { stage: "proposal", role: "researcher" },
|
||||
firstPrediction: { stage: "firstPrediction", role: "researcher" },
|
||||
critique: { stage: "critique", role: "researcher" },
|
||||
response: { stage: "response", role: "researcher" },
|
||||
unification: { stage: "response", role: "researcher" },
|
||||
secondPrediction: { stage: "secondPrediction", role: "researcher" },
|
||||
implementation: { stage: "implementation", role: "experimenter" },
|
||||
repair: { stage: "implementation", role: "experimenter" },
|
||||
verification: { stage: "verification", role: "verifier" },
|
||||
explanation: { stage: "explanation", role: "researcher" },
|
||||
ablationImplementation: { stage: "ablationImplementation", role: "experimenter" },
|
||||
ablationVerification: { stage: "ablationVerification", role: "verifier" },
|
||||
wrapUp: { stage: "wrapUp", role: "researcher" },
|
||||
} as const satisfies Record<string, { stage: Stage; role: Role }>;
|
||||
export type SubmissionKind = keyof typeof SUBMISSION_KINDS;
|
||||
@@ -0,0 +1,260 @@
|
||||
import { Type, type Static } from "typebox";
|
||||
import { DIRECTIONS, Id, Measured, type Json } from "./basics.ts";
|
||||
|
||||
/** 不认识的字段、不合格式的键一律不收。 */
|
||||
const CLOSED = { additionalProperties: false } as const;
|
||||
const REASON = Type.String({ minLength: 1 });
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 指标(方法.md 第五部分"指标")
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 指标层次:最终指标由任务规定;常规辅助指标由程序统一采集;假设专用指标由研究者登记。 */
|
||||
export const METRIC_LAYERS = ["final", "auxiliary", "hypothesis"] as const;
|
||||
export type MetricLayer = (typeof METRIC_LAYERS)[number];
|
||||
|
||||
/** "测不到时怎么办"只有两档。"只记缺失"排在前面:它是诊断量的常规选择。 */
|
||||
export const MISSING_POLICIES = ["recordMissing", "invalidateRun"] as const;
|
||||
export type MissingPolicy = (typeof MISSING_POLICIES)[number];
|
||||
|
||||
/** 每一档的后果,字段说明和给模型看的材料都用这一份。 */
|
||||
export const MISSING_POLICY_EFFECTS: Readonly<Record<MissingPolicy, string>> = {
|
||||
recordMissing: "缺了只记缺失:这次运行的成绩照常算。诊断用的量一般选这一种。",
|
||||
invalidateRun:
|
||||
"缺了整次作废:这次运行不能被采纳,运行次数照扣。只有没有这个量成绩就不可信时,才选这一种。",
|
||||
};
|
||||
|
||||
/** 一条指标除编号和层次以外的内容。研究者登记假设专用指标时填的就是这些,所以说明写给模型看。 */
|
||||
const METRIC_FIELDS = {
|
||||
meaning: Type.String({
|
||||
minLength: 1,
|
||||
description: "含义:这个量测的是什么",
|
||||
}),
|
||||
unit: Type.String({
|
||||
minLength: 1,
|
||||
description: "单位;没有单位(计数、比例)写\"无\"",
|
||||
}),
|
||||
direction: Type.Enum(DIRECTIONS, {
|
||||
type: "string",
|
||||
description: "maximize:越大越好;minimize:越小越好",
|
||||
}),
|
||||
howMeasured: Type.String({
|
||||
minLength: 1,
|
||||
description:
|
||||
"怎么采样和汇总:在什么时刻测、测几次、怎么合成一个数,写到换一个人照着写代码能测出同一个数",
|
||||
}),
|
||||
missing: Type.Enum(MISSING_POLICIES, {
|
||||
type: "string",
|
||||
description:
|
||||
`测不到时怎么办。recordMissing——${MISSING_POLICY_EFFECTS.recordMissing}` +
|
||||
`invalidateRun——${MISSING_POLICY_EFFECTS.invalidateRun}` +
|
||||
"消融格里一律只记缺失。引用别人登记过的指标时,沿用原登记的选法",
|
||||
}),
|
||||
};
|
||||
|
||||
/** 研究者在协议里登记的一条假设专用指标。编号由研究者起,候选代码按这个编号写出取值。 */
|
||||
export const HypothesisMetric = Type.Object(
|
||||
{
|
||||
id: Type.String({
|
||||
...Id,
|
||||
description: "指标短名,只用字母、数字和 ._-,候选代码按它写出取值",
|
||||
}),
|
||||
...METRIC_FIELDS,
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
export type HypothesisMetric = Static<typeof HypothesisMetric>;
|
||||
|
||||
/** 一条完整的指标定义:任务给的最终指标、常规辅助指标,以及冻结后的假设专用指标。 */
|
||||
export const MetricDefinition = Type.Object(
|
||||
{
|
||||
id: Id,
|
||||
layer: Type.Enum(METRIC_LAYERS, { type: "string" }),
|
||||
...METRIC_FIELDS,
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
export type MetricDefinition = Static<typeof MetricDefinition>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 运行定义:tasks 交给 runner,告诉它怎么起这个进程
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const RunDefinition = Type.Object(
|
||||
{
|
||||
/** 命令行,按参数切好,不经过 shell。 */
|
||||
command: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
|
||||
/** 进程在容器里的工作目录,绝对路径。 */
|
||||
workdir: Type.String({ pattern: "^/" }),
|
||||
/**
|
||||
* 镜像按 sha256 钉死,不接受 tag:tag 可以被重新指向另一份内容。
|
||||
* digest 填本机镜像 ID,即 `docker image inspect -f '{{.Id}}'` 的值;
|
||||
* 不是仓库清单摘要(RepoDigests),本地构建的镜像没有那个值。
|
||||
* 运行时必须写明:本机 Docker 的默认运行时不一定是 runc,跟着默认走,换台机器环境就悄悄变了。
|
||||
*/
|
||||
image: Type.Object(
|
||||
{
|
||||
digest: Type.String({ pattern: "^sha256:[0-9a-f]{64}$" }),
|
||||
runtime: Type.String({ minLength: 1 }),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
/** 单次运行的墙钟时限(秒)和核数,由任务规定,是同批候选共用的比较条件。 */
|
||||
wallSeconds: Type.Number({ exclusiveMinimum: 0 }),
|
||||
cores: Type.Integer({ minimum: 1 }),
|
||||
/**
|
||||
* 候选进程不继承宿主的环境变量,类型上只允许 false。参考项目实测过:宿主环境被污染时,
|
||||
* 同一份代码的分数从 0.9598 变成 0.8849,退出码是 0,没有任何报错。
|
||||
*/
|
||||
inheritEnv: Type.Literal(false),
|
||||
/** 进程拿到的全部环境变量。 */
|
||||
env: Type.Record(Type.String({ pattern: "^[A-Za-z_][A-Za-z0-9_]*$" }), Type.String(), CLOSED),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
export type RunDefinition = Static<typeof RunDefinition>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 运行结果第一层:runner 交出的进程层面结果
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 资源用量,每一项都可能测不到。 */
|
||||
const RESOURCE_USAGE = {
|
||||
cpuSeconds: Measured,
|
||||
wallSeconds: Measured,
|
||||
peakMemoryBytes: Measured,
|
||||
};
|
||||
|
||||
/**
|
||||
* 以什么方式结束。有没有启动由它决定:只有 notStarted 是没启动,
|
||||
* 其余三种都启动过、要扣次数(方法.md 第七部分)。
|
||||
* 启动前就取消的记 notStarted;启动后取消的记 cancelled。
|
||||
* 退出码只有正常退出才有,资源用量只有启动过才有,所以分成四种形状。
|
||||
*/
|
||||
export const ProcessResult = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
ending: Type.Literal("notStarted"),
|
||||
reason: REASON,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
ending: Type.Literal("exited"),
|
||||
exitCode: Type.Integer(),
|
||||
...RESOURCE_USAGE,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
ending: Type.Literal("timeout"),
|
||||
...RESOURCE_USAGE,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
ending: Type.Literal("cancelled"),
|
||||
...RESOURCE_USAGE,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
]);
|
||||
export type ProcessResult = Static<typeof ProcessResult>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 运行结果第二层:tasks 从原始产物独立判分
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 判定三选一:算数、不算数、判不了(判定所需的证据没拿到)。 */
|
||||
export const VERDICTS = ["valid", "invalid", "undecidable"] as const;
|
||||
export type Verdict = (typeof VERDICTS)[number];
|
||||
|
||||
/**
|
||||
* 候选在写最终指标的同一个文件里多写的两个保留键(方法.md 第五部分"答案路径")。
|
||||
* 实现的字段说明(tasks 一侧写)和给模型的材料(materials 一侧读)都用这两个常量。
|
||||
*/
|
||||
export const ANSWER_PATH_KEY = "answer-path";
|
||||
export const SAFEGUARD_TRIGGERS_KEY = "safeguard-triggers";
|
||||
|
||||
/**
|
||||
* 答案路径:候选写的最终答案出自哪里(main、某个因子编号或某条保障的短名)。
|
||||
* tasks 只记读到的原始内容:读到了就原样存字符串;没写或写的不是字符串,记读不到并附原因。
|
||||
* 取值在不在位置表里不在这里判断,由以后拿得到位置表的模块现算。
|
||||
*/
|
||||
export const AnswerPath = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
status: Type.Literal("read"),
|
||||
value: Type.String(),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
status: Type.Literal("unreadable"),
|
||||
reason: REASON,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
]);
|
||||
export type AnswerPath = Static<typeof AnswerPath>;
|
||||
|
||||
/**
|
||||
* 各条保障这次动手的次数。tasks 只记读到的原始内容:读到"名字 → 非负整数"的对象就原样存;
|
||||
* 没写,或者写的不是对象、值不是非负整数,记读不到并附原因。
|
||||
* 名字和位置表对不对得上不在这里判断,由以后拿得到位置表的模块现算。
|
||||
*/
|
||||
export const SafeguardTriggers = Type.Union([
|
||||
Type.Object(
|
||||
{
|
||||
status: Type.Literal("read"),
|
||||
counts: Type.Record(Type.String(), Type.Integer({ minimum: 0 })),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
Type.Object(
|
||||
{
|
||||
status: Type.Literal("unreadable"),
|
||||
reason: REASON,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
]);
|
||||
export type SafeguardTriggers = Static<typeof SafeguardTriggers>;
|
||||
|
||||
export const JudgedResult = Type.Object(
|
||||
{
|
||||
verdict: Type.Enum(VERDICTS, { type: "string" }),
|
||||
verdictReason: REASON,
|
||||
/** 最终指标的分数(合法化之后的)。 */
|
||||
score: Measured,
|
||||
/** 各指标的取值,键是指标编号。 */
|
||||
metrics: Type.Record(Id, Measured, CLOSED),
|
||||
answerPath: AnswerPath,
|
||||
safeguardTriggers: SafeguardTriggers,
|
||||
/** 任务自己的附加记录,任务想记什么就记什么,例如填圆的合法化。 */
|
||||
extra: Type.Unsafe<Json>({}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
export type JudgedResult = Static<typeof JudgedResult>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 模型用量(方法.md 第七部分:拿不到用量时记"未知",不记 0)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 一次模型调用的用量。四种 token 互不重叠;价格单位为美元,与 Pi 的用量报告一致。 */
|
||||
export const ModelUsage = Type.Object(
|
||||
{
|
||||
inputTokens: Measured,
|
||||
cacheReadTokens: Measured,
|
||||
cacheWriteTokens: Measured,
|
||||
outputTokens: Measured,
|
||||
cost: Measured,
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
export type ModelUsage = Static<typeof ModelUsage>;
|
||||
@@ -0,0 +1,615 @@
|
||||
import { Type, type Static, type TSchema } from "typebox";
|
||||
import { Id } from "./basics.ts";
|
||||
import type { SubmissionKind } from "./roster.ts";
|
||||
import { HypothesisMetric } from "./runs.ts";
|
||||
|
||||
/**
|
||||
* 每种提交的字段定义(方法.md 第五部分)。同一份定义同时是类型、收件时的格式检查、
|
||||
* 发给模型的提交工具参数,所以字段说明写给模型看,每个字段都写明出口。
|
||||
* 这里只管单个字段长什么样;跨字段的规则(q₁ ≤ q₅ ≤ q₉、因子数上限、覆盖是否齐全等)
|
||||
* 在 submissions 模块。
|
||||
* 程序分配的编号(候选、挑刺、入口编号)不让模型填;因子、实现条款的编号就是它在清单里的位置。
|
||||
*/
|
||||
|
||||
/** 文字字段:不能为空,也不能只有空白。 */
|
||||
const TEXT = { pattern: "\\S" };
|
||||
/** 不认识的字段一律不收。 */
|
||||
const CLOSED = { additionalProperties: false } as const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 共用字段
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 三个数:一个分数的事前判断(方法.md 第二部分"范围")。 */
|
||||
export const Range = Type.Object(
|
||||
{
|
||||
q1: Type.Number({ description: "有一成可能低于这个数" }),
|
||||
q5: Type.Number({ description: "有一半可能低于这个数" }),
|
||||
q9: Type.Number({ description: "有九成可能低于这个数" }),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description:
|
||||
"三个数,按分数原值写,不管越大越好还是越小越好。要满足 q1 ≤ q5 ≤ q9,可以相等。" +
|
||||
"没有\"不知道\"这个选项:拿不准就把 q1 和 q9 拉开",
|
||||
},
|
||||
);
|
||||
export type Range = Static<typeof Range>;
|
||||
|
||||
const FactorKey = Type.String({
|
||||
pattern: "^p[1-9][0-9]*$",
|
||||
description: "因子编号:它在冻结协议因子清单里的位置,第 1 个是 p1",
|
||||
});
|
||||
|
||||
const SafeguardName = Type.String({
|
||||
pattern: "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$",
|
||||
description:
|
||||
"保障短名:字母、数字和 ._-。不能叫 main,也不能写成 p1、p2 这种因子编号的样子," +
|
||||
"因为答案路径里 main、因子编号和保障短名放在一起;位置表也按它对号",
|
||||
});
|
||||
|
||||
const ClauseKey = Type.String({
|
||||
pattern: "^c[1-9][0-9]*$",
|
||||
description: "实现条款编号:它在冻结协议实现条款里的位置,第 1 条是 c1",
|
||||
});
|
||||
|
||||
const CodeLocation = Type.String({
|
||||
pattern: "^[^:\\s]+(:[1-9][0-9]*(-[1-9][0-9]*)?)?$",
|
||||
description: "代码位置:\"文件\"、\"文件:行号\"或\"文件:起-止\",行号是候选代码里的行号",
|
||||
});
|
||||
|
||||
const CONCERNS = Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"照冻结原文实现、但你认为会出问题的地方,尤其是某条保障会挡掉或改变什么。" +
|
||||
"它会转给研究者,不阻止运行,也不改动协议。没有就写\"无\"",
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 第零轮:入口、讨论、志愿(方法.md 4.2)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Entry = Type.Object(
|
||||
{
|
||||
name: Type.String({
|
||||
...TEXT,
|
||||
description: "名称:离开这份清单单独看也读得懂",
|
||||
}),
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"Markdown:从哪里开始看(先读哪段代码、先问哪个问题、先质疑哪个设定)、为什么值得有人去走。" +
|
||||
"只写起点,不写\"应该改成什么、能到多少分\"",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
const ENTRIES = Type.Array(Entry, {
|
||||
minItems: 2,
|
||||
maxItems: 3,
|
||||
description: "2–3 个入口,彼此尽量不重叠;想不出第三个就交两个。编号由程序按顺序分配",
|
||||
});
|
||||
|
||||
export const EntrySubmission = Type.Object(
|
||||
{
|
||||
entries: ENTRIES,
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
export const DiscussionSubmission = Type.Object(
|
||||
{
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"讨论正文,Markdown。目标是分工,不是达成共识:哪些入口其实是同一条路," +
|
||||
"哪条路没人站但值得走,你自己想走哪条",
|
||||
}),
|
||||
entries: Type.Optional({
|
||||
...ENTRIES,
|
||||
description: "可选:整份替换你自己的入口清单(2–3 个)。不换就不交这个字段",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
export const ClaimSubmission = Type.Object(
|
||||
{
|
||||
choices: Type.Array(Id, {
|
||||
minItems: 1,
|
||||
description:
|
||||
"志愿:按你想走的先后排入口编号,照材料里列的写,可以排别人的入口;至少排材料里写明的个数",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 提案(方法.md 4.3 ②、第五部分"提案")
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Factor = Type.Object(
|
||||
{
|
||||
name: Type.String({
|
||||
...TEXT,
|
||||
description: "名称:离开这份提案也读得懂",
|
||||
}),
|
||||
method: Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"做法:改什么、怎么改。配套设定(离开这个因子就没有意义的设定值)写在这里,跟着它一起开、一起关",
|
||||
}),
|
||||
keySettings: Type.String({
|
||||
...TEXT,
|
||||
description: "关键设定:决定这个因子效果的设定值,例如\"幅度 0.004、窗口 15 秒\";没有写\"无\"",
|
||||
}),
|
||||
offBehavior: Type.Optional(
|
||||
Type.String({
|
||||
...TEXT,
|
||||
description: "关掉这个因子时换成什么。省略就回到基线在这件事上的做法",
|
||||
}),
|
||||
),
|
||||
continues: Type.Union([Id, Type.Null()], {
|
||||
description:
|
||||
"延续信念本里的哪个因子:写它的因子编号。只用于同一个想法的新版本(换了设定、换了基线);" +
|
||||
"受它启发想出来的新想法不算延续。新因子写 null",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
const FACTORS = Type.Array(Factor, {
|
||||
minItems: 1,
|
||||
description:
|
||||
"因子清单:每个因子是单独放到基线上也说得通的改动,编号就是位置(第 1 个是 p1)。" +
|
||||
"只有一个因子就写一个;拿不准一处改动算因子还是保障,就算因子。" +
|
||||
"超过因子数上限时把多出来的留到下一轮,不要几个捆成一个",
|
||||
});
|
||||
|
||||
const SAFEGUARDS = Type.Array(SafeguardName, {
|
||||
description:
|
||||
"保障清单,每条一个短名。保障在任何一格里都不改变评测打出来的分数,只在评测本来不收的情形下动手," +
|
||||
"动手时只修到刚好合法、退回基线做法或直接报错;做不到的、拿不准的都是因子。没有保障交空数组",
|
||||
});
|
||||
|
||||
const METRICS = Type.Object(
|
||||
{
|
||||
defined: Type.Array(HypothesisMetric, {
|
||||
description: "新登记的假设专用指标;没有就交空数组",
|
||||
}),
|
||||
reused: Type.Array(Id, {
|
||||
description: "引用别人登记过的假设专用指标,照材料里已登记指标清单的编号写;没有就交空数组",
|
||||
}),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description:
|
||||
"指标登记:这个候选要采集哪些假设专用指标。最终指标和常规辅助指标程序每次都会采集,不用在这里写",
|
||||
},
|
||||
);
|
||||
|
||||
const PROPOSAL_BODY = Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"提案正文,Markdown,带小标题:每个因子为什么可能有效;" +
|
||||
"每条保障做什么、属于拦 / 补 / 改哪一种、自检时怎么想的;" +
|
||||
"协议(不许改什么、随机性怎么处理、何时停、什么样的运行不算数);因子之间的关系。" +
|
||||
"某方面不适用写\"不适用:原因\"。不写自己估的分数或提升量;引用已测到的分数要写明出自哪次运行;" +
|
||||
"文献和别人报告的数值只当起点,说明与本任务的区别;从第 2 轮起说明哪些新的观测影响了这条提案",
|
||||
});
|
||||
|
||||
export const ProposalSubmission = Type.Object(
|
||||
{
|
||||
factors: FACTORS,
|
||||
safeguards: SAFEGUARDS,
|
||||
metrics: METRICS,
|
||||
authorEstimate: Range,
|
||||
body: PROPOSAL_BODY,
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description:
|
||||
"一条提案。authorEstimate 是你自己估的这个改法跑出来的最终指标,交上来就封存," +
|
||||
"到本轮结束前不给任何人看。交不出合格提案时不要凑,在回复里写清卡在哪里",
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 两次预测、挑刺(方法.md 4.3 ④⑤⑦)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 第一次、第二次预测用同一个定义:对一个候选的最终指标给三个数。 */
|
||||
export const PredictionSubmission = Type.Object(
|
||||
{
|
||||
range: Range,
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description: "理由,Markdown",
|
||||
}),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description: "对这个候选跑出来的最终指标给三个数",
|
||||
},
|
||||
);
|
||||
|
||||
export const CritiqueSubmission = Type.Object(
|
||||
{
|
||||
target: Type.String({
|
||||
pattern: "^(whole|p[1-9][0-9]*)$",
|
||||
description: "针对谁:整条提案写 whole,针对某个因子写它的编号(p1、p2……)",
|
||||
}),
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"挑刺内容,Markdown:混杂因素、写得不清楚没法照着做、值不值得花一次运行、因子划得对不对;" +
|
||||
"对每条保障按拦、补、改三问追问。没有对照代码或已测运行核实过的判断," +
|
||||
"写成\"(推测)\"并写出依据",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 回应与统一(方法.md 4.3 ⑥)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const REPLIES = Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
critique: Type.String({
|
||||
...Id,
|
||||
description: "挑刺编号,照材料里列的写",
|
||||
}),
|
||||
reply: Type.String({
|
||||
...TEXT,
|
||||
description: "怎么答复:改了哪里,或者为什么不改",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "逐条答复针对这个候选的每一条挑刺;没有挑刺交空数组",
|
||||
},
|
||||
);
|
||||
|
||||
const CLAUSES = Type.Array(
|
||||
Type.String({
|
||||
...TEXT,
|
||||
description: "一条实现条款:要实现什么、落在哪个文件的哪里",
|
||||
}),
|
||||
{
|
||||
minItems: 1,
|
||||
description: "实现条款,编号就是位置(第 1 条是 c1)。实验员照它实现,核验员逐条对号",
|
||||
},
|
||||
);
|
||||
|
||||
export const ResponseSubmission = Type.Object(
|
||||
{
|
||||
replies: REPLIES,
|
||||
changes: Type.Object(
|
||||
{
|
||||
factors: Type.Optional(FACTORS),
|
||||
safeguards: Type.Optional(SAFEGUARDS),
|
||||
metrics: Type.Optional(METRICS),
|
||||
body: Type.Optional(PROPOSAL_BODY),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description:
|
||||
"改动了的部分:写上的一项整项替换原提案的对应部分,没写的沿用原提案。" +
|
||||
"什么都没改就交空对象。作者自评不能改",
|
||||
},
|
||||
),
|
||||
clauses: CLAUSES,
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description: "答复正文,Markdown:逐条答复之外要补充的话。没有挑刺、也没有要补充的,写\"无\"",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
export const UnificationSubmission = Type.Object(
|
||||
{
|
||||
factors: FACTORS,
|
||||
safeguards: SAFEGUARDS,
|
||||
metrics: METRICS,
|
||||
body: PROPOSAL_BODY,
|
||||
replies: REPLIES,
|
||||
clauses: CLAUSES,
|
||||
sources: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
proposal: Type.String({
|
||||
...Id,
|
||||
description: "被合并的原提案编号,照材料里列的写",
|
||||
}),
|
||||
coverage: Type.String({
|
||||
...TEXT,
|
||||
description: "这份原提案在统一后的协议里哪里体现;没采纳的部分写明为什么",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
minItems: 1,
|
||||
description: "逐条写明每份原提案在哪里体现",
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description:
|
||||
"几条提案合成的候选,由你写成一份完整的冻结协议。只能用原提案和挑刺里有的改动,不能另加新改动",
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 实现、修复、核验(方法.md 4.3 ⑨)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const LOCATIONS = Type.Array(CodeLocation, { minItems: 1 });
|
||||
|
||||
/** 实现和修复用同一个定义。代码本身由程序从实验员的工作目录冻结,不作为字段交。 */
|
||||
export const ImplementationSubmission = Type.Object(
|
||||
{
|
||||
factorLocations: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
factor: FactorKey,
|
||||
locations: LOCATIONS,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description:
|
||||
"因子位置表:冻结协议里每个因子在哪几行,每个因子一项。一处代码服务两个因子,两边都写",
|
||||
},
|
||||
),
|
||||
safeguardLocations: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
safeguard: SafeguardName,
|
||||
locations: LOCATIONS,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description:
|
||||
"保障位置表:每条保障在哪几行。同一行既属于某个因子又属于某条保障时,两边都写。" +
|
||||
"冻结协议没有保障就交空数组",
|
||||
},
|
||||
),
|
||||
unassigned: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
location: CodeLocation,
|
||||
note: Type.String({
|
||||
...TEXT,
|
||||
description: "为什么不属于任何因子或保障,一句说明",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "不属于任何因子或保障的改动;没有就交空数组",
|
||||
},
|
||||
),
|
||||
concerns: CONCERNS,
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description: "实现说明,Markdown:位置表之外需要说明的事。没有要补充的就写\"无\"",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
const FOUND = Type.Boolean({
|
||||
description: "找到对应实现写 true,找不到写 false(记为没落实)",
|
||||
});
|
||||
|
||||
const WHERE = Type.String({
|
||||
...TEXT,
|
||||
description: "在哪里;没找到就写看了哪里",
|
||||
});
|
||||
|
||||
export const VerificationSubmission = Type.Object(
|
||||
{
|
||||
clauses: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
clause: ClauseKey,
|
||||
found: FOUND,
|
||||
where: WHERE,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "每条实现条款一项",
|
||||
},
|
||||
),
|
||||
factors: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
factor: FactorKey,
|
||||
found: FOUND,
|
||||
where: WHERE,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "每个因子一项",
|
||||
},
|
||||
),
|
||||
safeguards: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
safeguard: SafeguardName,
|
||||
found: FOUND,
|
||||
where: WHERE,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "每条保障一项;冻结协议没有保障就交空数组",
|
||||
},
|
||||
),
|
||||
unassigned: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
location: Type.String({
|
||||
...CodeLocation,
|
||||
description: "材料里列出的那处改动,照材料的写法抄",
|
||||
}),
|
||||
outOfScope: Type.Boolean({
|
||||
description: "超出冻结协议、碰了测量或做了协议没写的事写 true",
|
||||
}),
|
||||
reason: Type.String({
|
||||
...TEXT,
|
||||
description: "判断依据",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "材料里列出的没有归属的改动,逐条判断是否越界;材料说没有就交空数组",
|
||||
},
|
||||
),
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description: "说明,Markdown:逐项结果之外需要说明的事。没有要补充的就写\"无\"",
|
||||
}),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description: "只交逐项结果,通过与否由程序推出:任何一项没落实或判为越界,就退回实验员修复",
|
||||
},
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 解释、消融、收尾(方法.md 4.3 ⑭⑯⑱)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const Explanation = Type.Object(
|
||||
{
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"Markdown:机制是什么、哪个因子在出力、什么情况下不成立、因子之间在效果上怎么互相依赖" +
|
||||
"(例如\"没有 p2,p3 就没用\",当作预测交给消融检验)、结论覆盖哪些设定、没覆盖哪些。" +
|
||||
"没有具体的机制判断就直说",
|
||||
}),
|
||||
cells: Type.Optional(
|
||||
Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
factor: FactorKey,
|
||||
range: Range,
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description:
|
||||
"每一格三个数:假如这条解释是对的,关掉这个因子后最终指标的范围。" +
|
||||
"材料说要给每格的数时(开着消融),材料里列出的每一格都要有;" +
|
||||
"没有具体的机制判断时,照实把范围写宽。材料说不给每格的数时(关掉了消融),不交这个字段",
|
||||
},
|
||||
),
|
||||
),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
export const ExplanationSubmission = Type.Object(
|
||||
{
|
||||
explanations: Type.Array(Explanation, {
|
||||
minItems: 1,
|
||||
description: "一般交一条就够,真有第二个机制判断时再交第二条,条数上限见材料",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
export const AblationImplementationSubmission = Type.Object(
|
||||
{
|
||||
alsoOff: Type.Array(FactorKey, {
|
||||
description:
|
||||
"需要一起关的因子:只有关掉指定因子后代码真的跑不起来、必须连带关掉别的因子时才写。" +
|
||||
"只关指定因子就交空数组",
|
||||
}),
|
||||
cannotRun: Type.Union([Type.Null(), Type.String(TEXT)], {
|
||||
description:
|
||||
"这一格跑不起来、也给不出一起关的方案时写原因,这一格不启动、不扣次数。跑得起来写 null",
|
||||
}),
|
||||
concerns: CONCERNS,
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description: "从完整代码出发,把指定的因子换回关掉时的做法,其余因子和全部保障照留",
|
||||
},
|
||||
);
|
||||
|
||||
export const AblationVerificationSubmission = Type.Object(
|
||||
{
|
||||
factors: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
factor: FactorKey,
|
||||
off: Type.Boolean({
|
||||
description: "这份代码里这个因子是关着的写 true,照完整版本保留写 false",
|
||||
}),
|
||||
where: Type.String({
|
||||
...TEXT,
|
||||
description: "依据:在哪几行看到的",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
),
|
||||
{
|
||||
description: "冻结协议里每个因子一项",
|
||||
},
|
||||
),
|
||||
},
|
||||
{
|
||||
...CLOSED,
|
||||
description: "只查一件事:是不是只关了指定的因子",
|
||||
},
|
||||
);
|
||||
|
||||
export const WrapUpSubmission = Type.Object(
|
||||
{
|
||||
body: Type.String({
|
||||
...TEXT,
|
||||
description:
|
||||
"收尾,Markdown:什么被支持了、什么被挑战了、下一步想在哪个因子上继续。" +
|
||||
"推广结论时写清它覆盖哪些设定。某方面没有写\"没有:为什么\"。" +
|
||||
"原样放进下一轮的提案材料,所有人都读得到",
|
||||
}),
|
||||
},
|
||||
CLOSED,
|
||||
);
|
||||
|
||||
/** 每种提交用哪个字段定义,键与 roster.ts 的 SUBMISSION_KINDS 相同。 */
|
||||
export const SUBMISSION_SCHEMAS = {
|
||||
entry: EntrySubmission,
|
||||
discussion: DiscussionSubmission,
|
||||
claim: ClaimSubmission,
|
||||
proposal: ProposalSubmission,
|
||||
firstPrediction: PredictionSubmission,
|
||||
critique: CritiqueSubmission,
|
||||
response: ResponseSubmission,
|
||||
unification: UnificationSubmission,
|
||||
secondPrediction: PredictionSubmission,
|
||||
implementation: ImplementationSubmission,
|
||||
repair: ImplementationSubmission,
|
||||
verification: VerificationSubmission,
|
||||
explanation: ExplanationSubmission,
|
||||
ablationImplementation: AblationImplementationSubmission,
|
||||
ablationVerification: AblationVerificationSubmission,
|
||||
wrapUp: WrapUpSubmission,
|
||||
} as const satisfies Record<SubmissionKind, TSchema>;
|
||||
|
||||
/** 某种提交的内容类型,例如 Submission<"proposal">。 */
|
||||
export type Submission<K extends SubmissionKind> = Static<(typeof SUBMISSION_SCHEMAS)[K]>;
|
||||
Reference in New Issue
Block a user