Files
InfoRSI/src/tasks/task.ts
T
iomgaa cf6bc4c844 feat: 任务书 05,runner 与 tasks 两个模块
runner 用 Docker 跑一条命令并交回结局和资源用量;tasks 读 task.json、准备运行目录、给出正式运行/校验器/轻量检查的运行定义,并从原始产物独立判分。任务目录从参考项目搬到 tasks/circle_packing/,新增 task.json;vocab 的 VERDICTS 删掉"判不了",ProcessResult 的 cancelled 改为"容器从外面被杀了"。

全新上下文审阅后的两处修复:计量 shell 以容器内 root 运行,命令经 setpriv 降权,结果写在容器自己的文件层、结束后 docker cp 取出,并加 no-new-privileges,候选无法伪造计量结果把超时记成正常退出;harness 开跑前删掉旧的 solution.json、metrics.json,旧产物不会被当成本次的值。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 13:55:47 -04:00

108 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { readFileSync } from "node:fs";
import { join, resolve } from "node:path";
import type { HypothesisMetric, JudgedResult, MetricDefinition, ProcessResult, RunDefinition } from "../vocab/index.ts";
import { circlePacking } from "./circle-packing.ts";
import { checkTaskFacts, type TaskKind } from "./facts.ts";
import type { JudgingStep, LightCheckOutcome, Task, TaskImplementation } from "./types.ts";
// tasks 对外的函数。任务目录的约定:task.json(任务事实)、TASK.md(给实验员的说明)、
// Dockerfile 与依赖清单(建镜像)、baseline/(基线代码),以及各任务自己的 Python 工具。
// 每个函数按 task.facts.kind 从对照表里取出那种任务的实现来做。
/** 任务种类 → 实现。加一种任务就在这里加一行。 */
const IMPLEMENTATIONS: Readonly<Record<TaskKind, TaskImplementation>> = {
circle_packing: circlePacking,
};
function implementation(task: Task): TaskImplementation {
return IMPLEMENTATIONS[task.facts.kind];
}
/** 读一个任务目录:严格检查 task.json,同时交回 Dockerfile 所在目录、基线目录和 TASK.md 全文。 */
export function loadTask(taskDir: string): Task {
const dir = resolve(taskDir);
const path = join(dir, "task.json");
let value: unknown;
try {
value = JSON.parse(readFileSync(path, "utf8"));
} catch (error) {
throw new Error(`${path} 读不出来或不是合法的 JSON:${(error as Error).message}`, { cause: error });
}
return {
facts: checkTaskFacts(value),
dir,
baselineDir: join(dir, "baseline"),
guide: readFileSync(join(dir, "TASK.md"), "utf8"),
};
}
/** 最终指标与常规辅助指标的定义。最终指标的编号和方向取自 task.json。 */
export function metricDefinitions(task: Task): MetricDefinition[] {
return implementation(task).metricDefinitions(task.facts);
}
/**
* 准备一次正式运行的运行目录:把候选代码、任务的 harness 和这次要测的假设专用指标说明
* (metrics-spec.json)放进去。runDir 由调用方建好,应为空目录。
* 消融格里传进来的指标应当已经全部改成"缺了只记缺失"(vocab 指标说明),这里不区分。
*/
export function prepareRun(task: Task, candidateDir: string, runDir: string, hypothesisMetrics: HypothesisMetric[]): void {
implementation(task).prepareRun(task, candidateDir, runDir, hypothesisMetrics);
}
/**
* 正式运行的运行定义。种子由调用方给,同一份研究的全部运行用同一个种子:
* 评测要确定,消融不重跑基线也靠这一点(方法.md 第十部分第 9 条)。
*/
export function formalRun(task: Task, imageDigest: string, seed: string): RunDefinition {
return implementation(task).formalRun(task, imageDigest, seed);
}
/**
* 轻量检查:把候选代码和检查脚本放进 checkDir(调用方建好的空目录),交回运行定义。
* 只看语法和入口函数,不执行候选代码。seconds 来自配置;次数上限不归这里管。
*/
export function lightCheck(task: Task, candidateDir: string, checkDir: string, imageDigest: string, seconds: number): RunDefinition {
return implementation(task).lightCheck(task, candidateDir, checkDir, imageDigest, seconds);
}
/** 解读轻量检查:通过,或者不通过及原因。检查脚本自己没正常跑完时报错停下。 */
export function readLightCheck(task: Task, checkDir: string, result: ProcessResult): LightCheckOutcome {
return implementation(task).readLightCheck(checkDir, result);
}
/**
* 判分第一步。正式运行没正常结束、或者没有产物时直接判不算数;否则把产物和宿主保管的校验器
* 复制进 judgeDir(调用方建好的空目录),交回校验器的运行定义。
*/
export function prepareJudging(
task: Task,
runDir: string,
judgeDir: string,
imageDigest: string,
formal: ProcessResult,
hypothesisMetrics: HypothesisMetric[],
): JudgingStep {
return implementation(task).prepareJudging(task, runDir, judgeDir, imageDigest, formal, hypothesisMetrics);
}
/**
* 判分第二步:读校验器写的判定、运行目录里候选写的指标文件,加上正式运行的结果,交回判分结果。
* 校验器自己没正常跑完、判定文件认不出、修后分数高于原分数时报错停下:这些是宿主一侧的问题。
*/
export function judge(
task: Task,
runDir: string,
judgeDir: string,
formal: ProcessResult,
validator: ProcessResult,
hypothesisMetrics: HypothesisMetric[],
): JudgedResult {
return implementation(task).judge(task, runDir, judgeDir, formal, validator, hypothesisMetrics);
}
/** 改动过的文件里,属于 task.json"不许改"名单的那些。路径都相对候选代码目录。 */
export function protectedChanges(task: Task, changedFiles: string[]): string[] {
return changedFiles.filter((file) => task.facts.protectedFiles.includes(file));
}