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,129 @@
|
|||||||
|
// 模块之间的依赖规则,来源是 docs/结构.md 第二部分(模块表)和第六部分(检查规则)。
|
||||||
|
// 模块按 src/<模块名>/ 认;新建模块目录后自动受这些规则检查。规则只管 src 下的模块,tests/ 不受"只有某模块能用某外部包"的限制。
|
||||||
|
|
||||||
|
// 模块表"依赖"一列:每个模块除了自己和 vocab 以外,还能引用哪些模块。plugin 装配时可用各模块。
|
||||||
|
const ALLOWED = {
|
||||||
|
plugin: [
|
||||||
|
"flow", "export", "config", "materials", "submissions",
|
||||||
|
"ledger", "agents", "runner", "tasks", "workspace", "jev", "rules",
|
||||||
|
],
|
||||||
|
flow: [
|
||||||
|
"ledger", "rules", "materials", "submissions", "agents",
|
||||||
|
"runner", "tasks", "workspace", "config", "jev",
|
||||||
|
],
|
||||||
|
export: ["ledger", "rules", "materials"],
|
||||||
|
config: [],
|
||||||
|
materials: ["ledger", "rules"],
|
||||||
|
submissions: ["jev"],
|
||||||
|
ledger: [],
|
||||||
|
agents: [],
|
||||||
|
runner: [],
|
||||||
|
tasks: [],
|
||||||
|
workspace: [],
|
||||||
|
jev: [],
|
||||||
|
rules: [],
|
||||||
|
vocab: [],
|
||||||
|
};
|
||||||
|
const MODULES = Object.keys(ALLOWED);
|
||||||
|
const anyOf = (names) => `(${names.join("|")})`;
|
||||||
|
|
||||||
|
// 外部的东西各由哪些名字认出来。核心模块带不带 node: 前缀都认;
|
||||||
|
// npm 包装好后解析成 node_modules/ 下的路径,没装时是包名本身,两种都认。
|
||||||
|
const npm = (names) => `(^|node_modules/)${anyOf(names)}(/|$)`;
|
||||||
|
const SUBPROCESS = [
|
||||||
|
"^(node:)?(child_process|cluster)$",
|
||||||
|
npm(["cross-spawn", "execa"]),
|
||||||
|
];
|
||||||
|
const SQLITE = ["^node:sqlite$", npm(["better-sqlite3", "sqlite3"])];
|
||||||
|
const PI = npm(["@earendil-works/[^/]+"]);
|
||||||
|
|
||||||
|
const directionRules = MODULES.map((name) => ({
|
||||||
|
name: `direction-${name}`,
|
||||||
|
comment: `${name} 只能引用自己、vocab 和模块表里列出的模块:${ALLOWED[name].join("、") || "无"}`,
|
||||||
|
severity: "error",
|
||||||
|
from: { path: `^src/${name}/` },
|
||||||
|
to: { path: "^src/", pathNot: `^src/${anyOf([name, "vocab", ...ALLOWED[name]])}/` },
|
||||||
|
}));
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
forbidden: [
|
||||||
|
{
|
||||||
|
name: "no-circular",
|
||||||
|
comment: "不许循环引用",
|
||||||
|
severity: "error",
|
||||||
|
from: {},
|
||||||
|
to: { circular: true },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown-module",
|
||||||
|
comment: `src 下只有模块表里的模块目录:${MODULES.join("、")}`,
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/", pathNot: `^src/${anyOf(MODULES)}/` },
|
||||||
|
to: {},
|
||||||
|
},
|
||||||
|
...directionRules,
|
||||||
|
{
|
||||||
|
name: "through-index",
|
||||||
|
comment: "别的模块只能通过目录下的 index.ts 引用它",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/([^/]+)/" },
|
||||||
|
to: { path: "^src/[^/]+/", pathNot: ["^src/$1/", "^src/[^/]+/index\\.ts$"] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "outside-src",
|
||||||
|
comment: "src 下的文件只许引用 src/、node_modules/ 与 Node 内置模块,不能借 src 外的文件绕开规则",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/" },
|
||||||
|
to: { pathNot: ["^src/", "^node_modules/"], dependencyTypesNot: ["core"] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pure-allowlist",
|
||||||
|
comment:
|
||||||
|
"rules 和 vocab 不碰文件、数据库、网络、子进程和 Pi:除了 src 下允许的模块," +
|
||||||
|
"只许引用 typebox 和 node:crypto",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/(rules|vocab)/" },
|
||||||
|
to: { pathNot: ["^src/", "^node_modules/typebox/", "^(node:)?crypto$"] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sqlite-only-ledger",
|
||||||
|
comment: "只有 ledger 能用 SQLite",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/", pathNot: "^src/ledger/" },
|
||||||
|
to: { path: SQLITE },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pi-only-agents-plugin",
|
||||||
|
comment: "只有 agents 和 plugin 能用 Pi",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/", pathNot: "^src/(agents|plugin)/" },
|
||||||
|
to: { path: PI },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "subprocess-only-runner-workspace",
|
||||||
|
comment: "只有 runner 和 workspace 能起子进程",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/", pathNot: "^src/(runner|workspace)/" },
|
||||||
|
to: { path: SUBPROCESS },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tasks-not-runner",
|
||||||
|
comment: "tasks 不许引用 runner:runner 只管怎么跑,tasks 只管怎么判,由 flow 串起来",
|
||||||
|
severity: "error",
|
||||||
|
from: { path: "^src/tasks/" },
|
||||||
|
to: { path: "^src/runner/" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
doNotFollow: { path: "node_modules" },
|
||||||
|
tsPreCompilationDeps: true,
|
||||||
|
tsConfig: { fileName: "tsconfig.json" },
|
||||||
|
// 按 Node 的 ESM 规则读 npm 包的 exports(dependency-cruiser 默认不读),
|
||||||
|
// 否则 typebox 这类只在 exports 里给入口的包解析不到。
|
||||||
|
enhancedResolveOptions: {
|
||||||
|
extensions: [".ts", ".js"],
|
||||||
|
exportsFields: ["exports"],
|
||||||
|
conditionNames: ["import", "node", "default"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
+25
-3
@@ -11,6 +11,7 @@
|
|||||||
- CLAUDE.md:按官方写法写成(提交 3861fd8 及之后)。
|
- CLAUDE.md:按官方写法写成(提交 3861fd8 及之后)。
|
||||||
- `docs/方法.md`:由参考项目的研究方案 v3、设计-15、设计-16 合成(f32c0bd)。之后按用户决定改过:消融只测信息价值选中的格(5e4f8c2);保障的定义借用"控制变量"和"重构"来说明(141494f)。
|
- `docs/方法.md`:由参考项目的研究方案 v3、设计-15、设计-16 合成(f32c0bd)。之后按用户决定改过:消融只测信息价值选中的格(5e4f8c2);保障的定义借用"控制变量"和"重构"来说明(141494f)。
|
||||||
- `docs/结构.md`:13 个模块加 vocab、步骤清单、账本 12 种记录、可替换点、检查规则、粗估规模(6cf9f0c)。
|
- `docs/结构.md`:13 个模块加 vocab、步骤清单、账本 12 种记录、可替换点、检查规则、粗估规模(6cf9f0c)。
|
||||||
|
- 任务书 01(工具链 + vocab):npm 包、TypeScript、`node --test`、dependency-cruiser 规则全写,临时违规实测 14 条都报错;vocab 1030 行,`npm run check` 通过,`npm test` 24 条通过。结构.md 第八部分补了 vocab 对外内容。
|
||||||
|
|
||||||
## 关键决定(用户原话见各提交说明)
|
## 关键决定(用户原话见各提交说明)
|
||||||
|
|
||||||
@@ -19,13 +20,34 @@
|
|||||||
- 不设轮数上限,只限运行次数。
|
- 不设轮数上限,只限运行次数。
|
||||||
- 第一次预测放在挑刺之前、看原始提案;第二次预测在冻结之后、看冻结协议。
|
- 第一次预测放在挑刺之前、看原始提案;第二次预测在冻结之后、看冻结协议。
|
||||||
- 子 Agent 调研时不写 md 文件,结论直接回报。
|
- 子 Agent 调研时不写 md 文件,结论直接回报。
|
||||||
- 提交的字段定义放 vocab,submissions 只判收不收;vocab 只放两个函数(规范序列化、哈希),上限 600 行。用户原话:"我都同意"。
|
- 提交的字段定义放 vocab,submissions 只判收不收;vocab 只放两个函数(规范序列化、哈希)。用户原话:"我都同意"。
|
||||||
|
- vocab 上限改为 1100 行(多出来的主要是发给模型的字段说明)。用户原话:"同意 A,vocab 上限改为 1100 行。结构.md 由监督者改,你不用动。"
|
||||||
|
- 每份预测只对一个候选、每份挑刺只针对一个目标;入口、因子、实现条款的编号由程序按位置分配;实现提交不设代码字段(程序从工作目录冻结);运行结果用"没启动 / 正常退出 / 超时 / 被取消"一个字段表示启动与结束;四种 token 数都可记测不到。用户原话:"3、4、5、6:同意保留你的写法。方法.md 第五部分的表(入口编号、实现的代码字段)由监督者改。"
|
||||||
|
- 解释的每格三个数改为可选,关掉消融时不交。用户原话:"同意 A:解释的 cells 去掉 minItems,改为可选"。
|
||||||
|
- 答案路径、保障动手次数只记读到的原始内容或读不到的原因;在不在位置表里,由以后拿得到位置表的模块现算,不存。用户原话:"按'能现算的不存',tasks 只记读到的原始内容……对表由以后有位置表的模块现算,把这条记进 WORK_LOG。"
|
||||||
|
|
||||||
## 未完成
|
## 未完成
|
||||||
|
|
||||||
- `inbox/调研.md`:要迁进 `docs/调研/` 的调研结论。
|
- `inbox/调研.md`:要迁进 `docs/调研/` 的调研结论。
|
||||||
- `docs/方法.md` 里还有 7 条标〔自定〕的规则,待用户过目。
|
- `docs/方法.md` 里还有 7 条标〔自定〕的规则,待用户过目。
|
||||||
- 还没有代码、工具链和测试入口。
|
- 监督者待改:结构.md 第七部分 vocab 上限改 1100;第二部分"同一层之间不互相依赖"改为"图中同一行的模块之间不互相依赖";方法.md 第五部分的表(入口编号由程序分配、实现不设代码字段)。
|
||||||
|
- 留给 submissions 的跨字段规则(vocab 字段定义不管):
|
||||||
|
1. q1 ≤ q5 ≤ q9:作者自评、两次预测、解释的每一格。
|
||||||
|
2. 因子数不超过上限,回应改过的因子清单同样适用。
|
||||||
|
3. 同一提案里两个因子不能延续同一个因子;延续的编号要在信念本里看得到。
|
||||||
|
4. 保障短名不能是 `main`,不能写成 `p<数字>`,同一清单里不重复。
|
||||||
|
5. 开着消融时每条解释必须交 `cells`,并恰好覆盖材料列出的格;关掉消融时不交。
|
||||||
|
6. 解释最多 M 条。
|
||||||
|
7. 志愿至少 min(N, 入口总数) 个、不重复、编号都在入口清单里。
|
||||||
|
8. 挑刺针对的因子编号在被挑提案的因子范围内。
|
||||||
|
9. 回应的逐条答复恰好覆盖针对本候选的全部挑刺;统一的 `sources` 恰好覆盖被合并的原提案。
|
||||||
|
10. `reused` 引用的指标存在且是假设专用指标;新登记的指标短名不重复。
|
||||||
|
11. 实现和修复:位置表覆盖每个因子和每条保障;代码位置起始行不大于结束行。
|
||||||
|
12. 核验:条款、因子、保障逐项全覆盖;没有归属的改动与材料列出的逐条对应。
|
||||||
|
13. 消融实现:写了 `cannotRun` 时 `alsoOff` 为空;`alsoOff` 不含本格要关的因子,且都在因子清单里。
|
||||||
|
14. 消融核验覆盖每个因子。
|
||||||
|
- 答案路径、保障名字"在不在位置表里"的对表,由以后拿得到位置表的模块现算(多半是 materials 或 flow)。
|
||||||
|
- 写 plugin 时实测:源码导入路径带 `.ts` 扩展名,Pi 加载插件能否接受。
|
||||||
|
|
||||||
## 工作方式(用户 2026-10-01 定)
|
## 工作方式(用户 2026-10-01 定)
|
||||||
|
|
||||||
@@ -38,4 +60,4 @@
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
`任务书/01-vocab.md` 已写好(vocab 加工具链),等用户另开 Agent 执行,监督者核对。之后依次是 rules → ledger → …。
|
任务书 01 已执行完,等监督者核对。之后依次是 rules → ledger → …。
|
||||||
|
|||||||
+47
@@ -196,3 +196,50 @@
|
|||||||
## 八、各模块对外的函数与类型
|
## 八、各模块对外的函数与类型
|
||||||
|
|
||||||
写到哪个模块时,再把它对外的函数和类型补在这里。
|
写到哪个模块时,再把它对外的函数和类型补在这里。
|
||||||
|
|
||||||
|
### vocab
|
||||||
|
|
||||||
|
下面是 `src/vocab/index.ts` 导出的全部内容。标"字段定义"的,是一份 TypeBox 定义,同名的 TypeScript 类型也一起导出:同一份东西既当类型用,也能在运行时检查数据(`Value.Check`),还能直接当发给模型的工具参数。
|
||||||
|
|
||||||
|
**两个函数**
|
||||||
|
|
||||||
|
- `canonical(value)`:规范序列化。对象的键排序后再拼成字符串,数字必须是有限数,只接受纯 JSON;内容相同就一定得到同一个字符串。
|
||||||
|
- `hash(value)`:对规范序列化的结果算 sha256,得到 64 位十六进制的指纹。
|
||||||
|
|
||||||
|
**基础**
|
||||||
|
|
||||||
|
- `Json`(类型):任意纯 JSON 值。
|
||||||
|
- `Id`(字段定义):编号的格式,只许字母、数字和 `._-`,首字符是字母或数字,最长 128 个字符,因为编号会被拼进文件路径和命令行。
|
||||||
|
- `DIRECTIONS`、`Direction`(字段定义):指标方向,只有越大越好(maximize)和越小越好(minimize)两种。
|
||||||
|
- `Measured`(字段定义):测到 / 测不到。测到带值,测不到带原因,所有可能测不到的量都用它,不拿 0 或 null 冒充。
|
||||||
|
|
||||||
|
**名单**
|
||||||
|
|
||||||
|
- `ROLES`、`Role`:三个角色,研究者、实验员、核验员。
|
||||||
|
- `STAGES`、`Stage`:有人交提交的各个阶段,按流程先后排;程序自己做的步骤不在里面。
|
||||||
|
- `SUBMISSION_KINDS`、`SubmissionKind`:每种提交属于哪个阶段、由哪个角色交,各模块都从这张表取。
|
||||||
|
|
||||||
|
**指标**
|
||||||
|
|
||||||
|
- `METRIC_LAYERS`、`MetricLayer`:指标的三个层次,最终、常规辅助、假设专用。
|
||||||
|
- `MISSING_POLICIES`、`MissingPolicy`:测不到时怎么办的两档,缺了只记缺失、缺了整次作废。
|
||||||
|
- `MISSING_POLICY_EFFECTS`:两档各自后果的白话说明,字段说明和给模型的材料共用这一份。
|
||||||
|
- `HypothesisMetric`(字段定义):研究者在提案里登记的一条假设专用指标。
|
||||||
|
- `MetricDefinition`(字段定义):一条完整的指标定义,带编号和层次。
|
||||||
|
|
||||||
|
**运行**
|
||||||
|
|
||||||
|
- `RunDefinition`(字段定义):tasks 交给 runner 的运行定义,写明命令、工作目录、按 sha256 钉死的镜像和运行时、时限、核数和环境变量;候选进程不继承宿主的环境变量。
|
||||||
|
- `ProcessResult`(字段定义):runner 交出的进程层面结果,分没启动、正常退出、超时、被取消四种;启动过的带资源用量。
|
||||||
|
- `VERDICTS`、`Verdict`:一次运行的判定,算数、不算数、判不了三选一。
|
||||||
|
- `ANSWER_PATH_KEY`、`SAFEGUARD_TRIGGERS_KEY`:候选在指标文件里多写的两个保留键名,`answer-path` 和 `safeguard-triggers`。
|
||||||
|
- `AnswerPath`(字段定义):读到的答案路径原样字符串,或读不到及原因;在不在位置表里由拿得到位置表的模块现算。
|
||||||
|
- `SafeguardTriggers`(字段定义):读到的"保障短名 → 动手次数"原样内容,或读不到及原因。
|
||||||
|
- `JudgedResult`(字段定义):tasks 从原始产物独立判分的结果,包括判定和理由、分数、各指标取值、答案路径、保障动手次数和任务自己的附加记录。
|
||||||
|
- `ModelUsage`(字段定义):一次模型调用的用量,四种 token 数和价格,每一项都可能测不到。
|
||||||
|
|
||||||
|
**提交**
|
||||||
|
|
||||||
|
- `Range`(字段定义):三个数 q1、q5、q9,即一个分数有一成、一半、九成可能低于多少。
|
||||||
|
- `SUBMISSION_SCHEMAS`:每种提交的字段定义,键与 `SUBMISSION_KINDS` 相同;同一份定义是类型、收件格式检查和提交工具参数。
|
||||||
|
- `Submission<K>`(类型):某种提交的内容类型,例如 `Submission<"proposal">`。
|
||||||
|
|||||||
Generated
+609
@@ -0,0 +1,609 @@
|
|||||||
|
{
|
||||||
|
"name": "inforsi",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "inforsi",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"typebox": "1.3.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "22.19.19",
|
||||||
|
"dependency-cruiser": "18.5.0",
|
||||||
|
"typescript": "5.9.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "22.19.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
|
||||||
|
"integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.18.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz",
|
||||||
|
"integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-jsx": {
|
||||||
|
"version": "5.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
|
||||||
|
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-jsx-walk": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/acorn-loose": {
|
||||||
|
"version": "8.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-loose/-/acorn-loose-8.5.2.tgz",
|
||||||
|
"integrity": "sha512-PPvV6g8UGMGgjrMu+n/f9E/tCSkNQ2Y97eFvuVdJfG11+xdIeDcLyNdC8SHcrHbRqkfwLASdplyR6B6sKM1U4A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^8.15.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-walk": {
|
||||||
|
"version": "8.3.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz",
|
||||||
|
"integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "^8.11.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "4.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
|
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "15.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-15.0.0.tgz",
|
||||||
|
"integrity": "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dependency-cruiser": {
|
||||||
|
"version": "18.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dependency-cruiser/-/dependency-cruiser-18.5.0.tgz",
|
||||||
|
"integrity": "sha512-LFvJOvufjQ7XTucSI40NHqse0gcQBevDGkZabLAKcGwO+VWOywTqZ5EyFRYWSokNxvLOO7v6EXyNi+9I72oWUA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"acorn": "8.18.0",
|
||||||
|
"acorn-jsx": "5.3.2",
|
||||||
|
"acorn-jsx-walk": "2.0.0",
|
||||||
|
"acorn-loose": "8.5.2",
|
||||||
|
"acorn-walk": "8.3.5",
|
||||||
|
"commander": "15.0.0",
|
||||||
|
"enhanced-resolve": "5.26.0",
|
||||||
|
"ignore": "7.0.11",
|
||||||
|
"interpret": "3.1.1",
|
||||||
|
"is-installed-globally": "1.0.1",
|
||||||
|
"json5": "2.2.3",
|
||||||
|
"picomatch": "4.0.7",
|
||||||
|
"prompts": "2.4.2",
|
||||||
|
"rechoir": "0.8.0",
|
||||||
|
"safe-regex": "2.1.1",
|
||||||
|
"semver": "7.8.5",
|
||||||
|
"tsconfig-paths-webpack-plugin": "4.2.0",
|
||||||
|
"watskeburt": "6.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"depcruise": "bin/dependency-cruiser.mjs",
|
||||||
|
"depcruise-baseline": "bin/depcruise-baseline.mjs",
|
||||||
|
"depcruise-fmt": "bin/depcruise-fmt.mjs",
|
||||||
|
"depcruise-wrap-stream-in-html": "bin/wrap-stream-in-html.mjs",
|
||||||
|
"dependency-cruise": "bin/dependency-cruiser.mjs",
|
||||||
|
"dependency-cruiser": "bin/dependency-cruiser.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^22||^24||>=26"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/enhanced-resolve": {
|
||||||
|
"version": "5.26.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.0.tgz",
|
||||||
|
"integrity": "sha512-9vhedylFonb2YGogzUKX6+Ja72gOJbN1QHAqdrvqLwhdl/QWbKopzoUC9EbQNVsAns/bx/4uyqalrQAoy1IByw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"graceful-fs": "^4.2.4",
|
||||||
|
"tapable": "^2.3.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/es-errors": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/global-directory": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ini": "4.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/graceful-fs": {
|
||||||
|
"version": "4.2.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||||
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ignore": {
|
||||||
|
"version": "7.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.11.tgz",
|
||||||
|
"integrity": "sha512-YChdK5txDjwGUvgR7oCJcLGkwi3LDh8Zx8tS7ndYciLEg/oOCL26VUBSUEtgS7EjiybcBqFxh5j0rAdDX7/bbg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ini": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/interpret": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.17.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.17.0.tgz",
|
||||||
|
"integrity": "sha512-J/vG0zBCbIKOQFfufSwyXdMrsohyJIUNkrnmo6WZGzoM7tr/lsbfW5b2BvisL6zsyMzK9UxV9L6c7AoFbyXHOA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"hasown": "^2.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-installed-globally": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-li88H4QGRoNMX27jEmY8nR515oJVspAgC/WFMlhHfFV8RJ4QyGpHhXnwAe9gH5RIgKmdYRUmrOhQobcgEjIdSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"global-directory": "^4.0.1",
|
||||||
|
"is-path-inside": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-path-inside": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/json5": {
|
||||||
|
"version": "2.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
|
||||||
|
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"json5": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/kleur": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/minimist": {
|
||||||
|
"version": "1.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
|
||||||
|
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/picomatch": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prompts": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"kleur": "^3.0.3",
|
||||||
|
"sisteransi": "^1.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/rechoir": {
|
||||||
|
"version": "0.8.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
|
||||||
|
"integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"resolve": "^1.20.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/regexp-tree": {
|
||||||
|
"version": "0.1.27",
|
||||||
|
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
|
||||||
|
"integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"regexp-tree": "bin/regexp-tree"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.12",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
|
||||||
|
"integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"es-errors": "^1.3.0",
|
||||||
|
"is-core-module": "^2.16.1",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/safe-regex": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"regexp-tree": "~0.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/semver": {
|
||||||
|
"version": "7.8.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
|
||||||
|
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"bin": {
|
||||||
|
"semver": "bin/semver.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/sisteransi": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
|
||||||
|
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/strip-bom": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tapable": {
|
||||||
|
"version": "2.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
||||||
|
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsconfig-paths": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"json5": "^2.2.2",
|
||||||
|
"minimist": "^1.2.6",
|
||||||
|
"strip-bom": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tsconfig-paths-webpack-plugin": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.1.0",
|
||||||
|
"enhanced-resolve": "^5.7.0",
|
||||||
|
"tapable": "^2.2.1",
|
||||||
|
"tsconfig-paths": "^4.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typebox": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/typebox/-/typebox-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-meKuifc33Pccx0O6PdIzYMq3Og8zvP4TIi/a+Bw3AEMZMxOD0+RHGQvpglEe6Zdy3wZ8nqn/j95h8LUZLk/6Hg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/watskeburt": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/watskeburt/-/watskeburt-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-jfiuDABaxSkC71T6oZ3vCS99roYkSHm/+As+G0Dz8taAHQb+SJBvLEm5RlsgG71XdfAj3rv7eudUBTgwcQUPlQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"watskeburt": "dist/run-cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^22.13||^24||>=26"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "inforsi",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.19.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"check": "tsc --noEmit -p tsconfig.json && depcruise src tests --config .dependency-cruiser.cjs",
|
||||||
|
"test": "node --test \"tests/**/*.test.ts\""
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"typebox": "1.3.7"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "22.19.19",
|
||||||
|
"dependency-cruiser": "18.5.0",
|
||||||
|
"typescript": "5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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]>;
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { Value } from "typebox/value";
|
||||||
|
import {
|
||||||
|
canonical,
|
||||||
|
hash,
|
||||||
|
JudgedResult,
|
||||||
|
RunDefinition,
|
||||||
|
SUBMISSION_KINDS,
|
||||||
|
SUBMISSION_SCHEMAS,
|
||||||
|
type Submission,
|
||||||
|
type SubmissionKind,
|
||||||
|
} from "../src/vocab/index.ts";
|
||||||
|
|
||||||
|
// 预期字符串手写;预期哈希用命令行 `printf '%s' '<预期字符串>' | sha256sum` 算出。
|
||||||
|
|
||||||
|
test("规范序列化:键的顺序不同,结果相同", () => {
|
||||||
|
const expected = '{"a":[true,null,"x"],"b":1,"c":{"y":"中","z":2.5}}';
|
||||||
|
const one = { b: 1, a: [true, null, "x"], c: { z: 2.5, y: "中" } };
|
||||||
|
const two = { c: { y: "中", z: 2.5 }, a: [true, null, "x"], b: 1 };
|
||||||
|
assert.equal(canonical(one), expected);
|
||||||
|
assert.equal(canonical(two), expected);
|
||||||
|
assert.equal(hash(one), "668eb8c3a097ac33abf5f690ab18c5f753b911ff1c9bbfd6163deb5939b68e5d");
|
||||||
|
assert.equal(hash(two), "668eb8c3a097ac33abf5f690ab18c5f753b911ff1c9bbfd6163deb5939b68e5d");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("规范序列化:嵌套数组保持顺序,数组里的对象也排键", () => {
|
||||||
|
const value = [{ b: 0, a: [{ d: 1, c: -1 }] }, [1, [2, []]], {}, "s"];
|
||||||
|
const expected = '[{"a":[{"c":-1,"d":1}],"b":0},[1,[2,[]]],{},"s"]';
|
||||||
|
assert.equal(canonical(value), expected);
|
||||||
|
assert.equal(hash(value), "521ca7249ce6c90890b5645dd2f8d46b42f6e000df5ad86aff42415035c7e8cb");
|
||||||
|
assert.equal(hash({}), "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("规范序列化:字符串转义(引号、换行、制表、控制字符、反斜杠、中文)与 -0", () => {
|
||||||
|
const value = { "中": [-0], z: -0, s: 'a"b\n\t\u0001中\\' };
|
||||||
|
const expected = String.raw`{"s":"a\"b\n\t\u0001中\\","z":0,"中":[0]}`;
|
||||||
|
assert.equal(canonical(value), expected);
|
||||||
|
assert.equal(hash(value), "d4605ee7ceddf1373f60f837ae86ce24de7319705bbcbce4706309fc4415c79a");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("规范序列化:NaN 和 Infinity 报错,嵌套在里面也报错", () => {
|
||||||
|
for (const bad of [NaN, Infinity, -Infinity, { a: [1, NaN] }, [{ x: Infinity }]]) {
|
||||||
|
assert.throws(() => canonical(bad), /有限数/);
|
||||||
|
assert.throws(() => hash(bad), /有限数/);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("规范序列化:带自定义原型的对象和非 JSON 值报错", () => {
|
||||||
|
class Point {
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
const bad: unknown[] = [
|
||||||
|
new Point(),
|
||||||
|
Object.create(null),
|
||||||
|
new Date(0),
|
||||||
|
new Map(),
|
||||||
|
{ a: new Point() },
|
||||||
|
{ a: undefined },
|
||||||
|
() => 1,
|
||||||
|
1n,
|
||||||
|
];
|
||||||
|
for (const value of bad) assert.throws(() => canonical(value), /纯 JSON/);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 每种提交一份合法样例,同时用来核对字段写的是不是方法.md 第五部分的意思。
|
||||||
|
const range = { q1: 2.6, q5: 2.62, q9: 2.63 };
|
||||||
|
const factors = [
|
||||||
|
{ name: "多起点局部优化", method: "从 8 个随机起点各跑一次 SLSQP,取最好的", keySettings: "起点数 8", continues: null },
|
||||||
|
{
|
||||||
|
name: "停滞期扰动",
|
||||||
|
method: "连续 20 步没有改进时扰动半径",
|
||||||
|
keySettings: "幅度 0.004",
|
||||||
|
offBehavior: "不扰动",
|
||||||
|
continues: "r1-c2.p1",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const metrics = {
|
||||||
|
defined: [
|
||||||
|
{
|
||||||
|
id: "first-feasible-seconds",
|
||||||
|
meaning: "第一次得到合法摆法的时间",
|
||||||
|
unit: "秒",
|
||||||
|
direction: "minimize" as const,
|
||||||
|
howMeasured: "从进程启动计时,到第一个通过校验的摆法写出为止,每次运行记一次",
|
||||||
|
missing: "recordMissing" as const,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
reused: ["wall-seconds"],
|
||||||
|
};
|
||||||
|
const body = "## 为什么可能有效\n……";
|
||||||
|
|
||||||
|
const samples: { [K in SubmissionKind]: Submission<K> } = {
|
||||||
|
entry: { entries: [{ name: "起点分布", body: body }, { name: "收尾修复", body: body }] },
|
||||||
|
discussion: {
|
||||||
|
body,
|
||||||
|
entries: [
|
||||||
|
{ name: "a", body },
|
||||||
|
{ name: "b", body },
|
||||||
|
{ name: "c", body },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
claim: { choices: ["researcher-2-e1", "researcher-1-e2"] },
|
||||||
|
proposal: { factors, safeguards: ["final-shrink"], metrics, authorEstimate: range, body },
|
||||||
|
firstPrediction: { range, body: "理由" },
|
||||||
|
critique: { target: "p2", body: "(推测)扰动可能和起点数混杂" },
|
||||||
|
response: {
|
||||||
|
replies: [{ critique: "cr-1", reply: "改了 p2 的幅度" }],
|
||||||
|
changes: { factors: [factors[0]!] },
|
||||||
|
clauses: ["在 solve.py 的 main 里加多起点循环"],
|
||||||
|
body: "答复",
|
||||||
|
},
|
||||||
|
unification: {
|
||||||
|
factors,
|
||||||
|
safeguards: [],
|
||||||
|
metrics: { defined: [], reused: [] },
|
||||||
|
body,
|
||||||
|
replies: [],
|
||||||
|
clauses: ["c1 的内容", "c2 的内容"],
|
||||||
|
sources: [{ proposal: "r2-p3", coverage: "体现在 p1" }],
|
||||||
|
},
|
||||||
|
secondPrediction: { range: { q1: 2.6, q5: 2.6, q9: 2.6 }, body: "理由" },
|
||||||
|
implementation: {
|
||||||
|
factorLocations: [{ factor: "p1", locations: ["solve.py:10-40"] }],
|
||||||
|
safeguardLocations: [{ safeguard: "final-shrink", locations: ["solve.py:88", "util.py"] }],
|
||||||
|
unassigned: [{ location: "solve.py:5", note: "补了一个 import" }],
|
||||||
|
concerns: "无",
|
||||||
|
body: "说明",
|
||||||
|
},
|
||||||
|
repair: {
|
||||||
|
factorLocations: [],
|
||||||
|
safeguardLocations: [],
|
||||||
|
unassigned: [],
|
||||||
|
concerns: "无",
|
||||||
|
body: "按核验意见补上 c2",
|
||||||
|
},
|
||||||
|
verification: {
|
||||||
|
clauses: [{ clause: "c1", found: true, where: "solve.py:10-40" }],
|
||||||
|
factors: [{ factor: "p1", found: false, where: "看了 solve.py 全文" }],
|
||||||
|
safeguards: [{ safeguard: "final-shrink", found: true, where: "solve.py:88" }],
|
||||||
|
unassigned: [{ location: "solve.py:5", outOfScope: false, reason: "只是 import" }],
|
||||||
|
body: "说明",
|
||||||
|
},
|
||||||
|
explanation: {
|
||||||
|
explanations: [
|
||||||
|
{ body: "p1 在出力", cells: [{ factor: "p1", range }, { factor: "p2", range }] },
|
||||||
|
{ body: "我没有具体的机制判断", cells: [{ factor: "p1", range: { q1: 2.3, q5: 2.55, q9: 2.64 } }] },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ablationImplementation: { alsoOff: [], cannotRun: null, concerns: "无" },
|
||||||
|
ablationVerification: { factors: [{ factor: "p1", off: true, where: "solve.py:10-40" }] },
|
||||||
|
wrapUp: { body: "被支持的:……" },
|
||||||
|
};
|
||||||
|
|
||||||
|
test("每种提交都有阶段、角色和字段定义", () => {
|
||||||
|
assert.deepEqual(Object.keys(SUBMISSION_SCHEMAS).sort(), Object.keys(SUBMISSION_KINDS).sort());
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const kind of Object.keys(SUBMISSION_KINDS) as SubmissionKind[]) {
|
||||||
|
test(`合法样例通过字段定义检查:${kind}`, () => {
|
||||||
|
const schema = SUBMISSION_SCHEMAS[kind];
|
||||||
|
const errors = [...Value.Errors(schema, samples[kind])].map(
|
||||||
|
(e) => `${e.instancePath} ${e.message}`,
|
||||||
|
);
|
||||||
|
assert.deepEqual(errors, []);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("关掉消融时,不带 cells 的解释通过字段定义检查", () => {
|
||||||
|
const submission = { explanations: [{ body: "机制:p1 在出力" }] };
|
||||||
|
assert.equal(Value.Check(SUBMISSION_SCHEMAS.explanation, submission), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 复现:TypeBox 的 Record 对带格式的键只生成 patternProperties,不合格式的键原先完全不检查。
|
||||||
|
test("环境变量和指标取值里,不合格式的键被拒收", () => {
|
||||||
|
const run = {
|
||||||
|
command: ["python3", "solve.py"],
|
||||||
|
workdir: "/work",
|
||||||
|
image: { digest: `sha256:${"a".repeat(64)}`, runtime: "runc" },
|
||||||
|
wallSeconds: 120,
|
||||||
|
cores: 1,
|
||||||
|
inheritEnv: false,
|
||||||
|
env: { OMP_NUM_THREADS: "1" },
|
||||||
|
};
|
||||||
|
assert.equal(Value.Check(RunDefinition, run), true);
|
||||||
|
assert.equal(Value.Check(RunDefinition, { ...run, env: { "1BAD-KEY": "x" } }), false);
|
||||||
|
assert.equal(Value.Check(RunDefinition, { ...run, env: { "x-y": 5 } }), false);
|
||||||
|
const judged = {
|
||||||
|
verdict: "valid",
|
||||||
|
verdictReason: "合法",
|
||||||
|
score: { status: "measured", value: 2.6 },
|
||||||
|
metrics: { "wall-seconds": { status: "measured", value: 3 } },
|
||||||
|
answerPath: { status: "read", value: "main" },
|
||||||
|
safeguardTriggers: { status: "unreadable", reason: "没写" },
|
||||||
|
extra: {},
|
||||||
|
};
|
||||||
|
assert.equal(Value.Check(JudgedResult, judged), true);
|
||||||
|
const badMetrics = { "../x": { status: "measured", value: 1 } };
|
||||||
|
assert.equal(Value.Check(JudgedResult, { ...judged, metrics: badMetrics }), false);
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2023",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"erasableSyntaxOnly": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "tests/**/*.ts"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user