Appearance
题目版本管理(草稿、发布与有效成绩)
Neuro OJ 的每道题都有不可变的版本:用户作答、评测与历史查看都绑定在某个具体版本上。 本文面向出题人、管理员与选手,说明草稿与发布、提交时版本、有效版本策略、批量升级与 重测,以及历史数据的读法。
术语速查:草稿是共享的可编辑内容;版本是发布后不可修改的完整作答与评测内容; 提交时版本是某条提交创建时实际使用的版本;评测尝试是用某个版本评测某条提交的 一次执行;有效版本策略决定当前统计接受哪些版本的成绩。
1. 草稿与发布(出题人)
编辑页面有两个独立动作:
| 动作 | 作用 | 影响谁 |
|---|---|---|
| 保存草稿 | 写入每题唯一的共享草稿,携带 revision 乐观锁 | 只有编辑者看得到 |
| 发布版本 | 把当前草稿固定为新的不可变版本(V1、V2…) | 之后的新作答使用该版本 |
规则:
- 草稿允许暂时不完整(缺支持包、缺完整运行配置、客观题缺小题都可以先存);发布前会做完整预检, 未通过会给出具体原因(缺文件、引用路径不存在、客观题没有小题等)。
- 多人同时编辑同一题:草稿写入必须携带当前
revision,缺失返回428、过时返回409, 重新加载后再保存即可,不会互相覆盖。 - 相同内容重复发布不会制造空版本:服务端按内容哈希去重,返回
unchanged: true与既有版本号。 - 每题只能有一个共享草稿;「保存草稿」不会改变正在作答的题目内容——只有发布才生效。
- 发布后题型(OI / AI / 客观题)与 AI 题的提交模式(代码 / 产物)固定,跨类型转换请新建题目。
- 未发布题目对普通访问者等同于不存在(详情 404、不进题库列表与搜索);编辑者通过草稿接口读取。
题包导入(CLI
problems import或管理端导入)是「写草稿 → 显式发布」:不发布时题目只有草稿, 生产演练与开发初始化会显式发布(--publish)。
2. 提交时版本与评测版本(选手)
- 题库默认作答版本:策略为
any时用最新版,为exact(X)时用要求版本 X。 - 竞赛默认作答版本:该竞赛固定的题目版本(加入竞赛时钉住,之后题库发布新版不影响本场比赛)。
- 提交时客户端携带
version_id:- 题库允许显式提交其他已发布版本(用于复现历史问题);
- 竞赛只接受固定版本,客户端覆盖会被拒绝;
- 竞赛固定版本已变化时返回
409 CONTEST_PROBLEM_VERSION_CHANGED,请刷新页面后重新提交 (编辑器会保留你已经写的代码或答案)。
- 老客户端缺少
version_id时会收到明确的升级提示,服务端不会静默绑定最新版。
3. 有效版本策略:历史均有效 vs 精确版本(管理员)
ts
type EffectiveVersionPolicy =
| { mode: "any" } // 默认:历史版本均可产生有效成绩
| { mode: "exact"; version_id: string };- 题库策略与**每个「竞赛 × 题目」**的策略相互独立。
- 发布新版本、重测指定版本、切换有效版本策略是三个独立动作:发布不会自动改策略, 改策略不会自动重测。
- 策略切换在事务提交后立即生效,不依赖缓存过期或榜单刷新。
- 竞赛采用
exact(X)时,固定作答版本必须同时为 X;单独升级固定版本时可同时提交新策略, 冲突会返回409。
管理端入口:「题目版本」页可逐题切换题库策略与竞赛固定版本(带 revision 乐观锁,冲突后自动重载)。
跨版本成绩如何保留
一条提交可以同时拥有多个版本的当前判定;每个版本只采用最近一次完成的正式判定。 举例(选手先后用 V1、V2 评测):
| 操作 | V1 当前成绩 | V2 当前成绩 | any 下通过 | exact(V2) 下通过 |
|---|---|---|---|---|
| 初次提交 V1,通过 | AC | 无 | 是 | 否 |
| 发布 V2 | AC | 无 | 是 | 否 |
| 用 V2 重测,失败 | AC | WA | 是 | 否 |
| 再次用 V1 重测,失败 | WA | WA | 否 | 否 |
| 用 V2 重测,通过 | WA | AC | 是 | 是 |
要点:
- 基础设施错误不替换已有正式判定(平台失败与「评测完成但不通过」严格区分)。
- 正在重测时,已有判定继续有效,直到新结果落库。
- 是否通过由「通过候选」单独计算,不看最高分——因此存在「最高分版本不通过、其他版本通过」的情况。
- 一条提交即使在多个版本上评测,统计里只算一次提交;用户升级会产生新提交,因此会增加提交数。
4. 用户升级与管理员重测(批量任务)
4.1 选手:升级到最新版
在「我的提交」列表勾选提交 → 批量升级到最新版(单次最多 500 条)。服务端会:
- 在受理时固定每题最新版;沿用原用户,生成新的提交并记录「由旧版本升级而来」;
- 新提交使用当前时间(历史提交的原始时间不变);
- 竞赛来源的升级可要求仍留在原竞赛(
context = source_contest),此时新版必须恰好等于固定版本; - 重复提交(同一幂等键同请求)返回原任务,不会重复生成提交。
常见跳过原因:原提交已经在最新版(ALREADY_LATEST,需要重新评测请找管理员重测)、 原提交正在评测、题目未发布新版本等。用户升级失败不会影响原提交与原成绩的读取。
4.2 管理员:重测
「批量重测」页支持三种范围与三种目标:
| 范围 | 说明 |
|---|---|
| 整题 | 某道题的全部提交(不受 500 条上限约束,后台分批执行) |
| 整场 | 某场竞赛某题的全部提交 |
| 手选 | 手动勾选提交,单次上限 500 条 |
| 目标 | 说明 |
|---|---|
| 提交时版本 | 用不可变的提交时版本重测(历史提交不会换版) |
| 最新版 | 受理时解析并固定每题最新版 |
| 全部用 V3 | 先把 V3 展开成逐题版本映射,任一题没有 V3 则整次受理失败(不产生任务、不改策略) |
任务受理返回任务 ID 与条目进度;条目级失败会给出 reason_code(例如 LEGACY_VERSION_UNKNOWN 表示历史提交没有提交时版本、ARTIFACT_MISSING 表示旧产物已删除、 LANGUAGE_NOT_SUPPORTED、ARTIFACT_TOO_LARGE_FOR_VERSION)。失败条目可一键重试, 重试生成关联的新任务并保留目标版本映射,不会再次修改有效策略。
手动选择的提交集合在受理时固定:受理之后产生的新提交不会进入本批任务。
5. 客观题:标识与强行重判
- 客观题小题有跨版本稳定的
key:编辑保留 key,新增小题生成新 key;题包questions.json可显式提供key(重复会被拒绝),未提供时由服务端生成。 - 卷面以提交时版本的快照为准:小题增删或改答案不会改变已有提交的判定与展示。
- 强行重判(管理员重测)按目标版本的 key 匹配原答案:
- 目标版本新增的小题按未作答;原版本被删除的小题不计分;
- 题型变化导致旧答案形式不兼容时按未作答;
- 原始 answers 永不被改写,详情里会说明匹配/缺失/形式不兼容的情况。
- 竞赛模式仍然保持「一次性提交」约束:已提交的选手不能再次提交,但可以升级为练习提交, 管理员可以重测原提交。
6. 产物、存储与历史缺失
- 支持包与 OI 数据文件都登记在对象存储引用表里;发布时复制引用(不复制字节), 因此后续替换草稿文件不会影响历史版本。
- 新产物提交的对象保留到提交被显式删除,不设置自动过期时间。
- 历史数据可能真的「缺失」,系统保留未知状态而不是伪造版本:
- 迁移前的历史提交保留
submitted_version_id = NULL(legacy_unknown):在any策略下 历史成绩继续有效;exact(X)下不匹配; - 按「提交时版本」重测这类提交会跳过(
LEGACY_VERSION_UNKNOWN); - 旧产物已被删除时重测条目失败(
ARTIFACT_MISSING),保留已有成绩; - 迁移前客观题小题若已被替换,旧卷面无法还原,提交详情按空卷面展示(原始答案仍可读)。
- 迁移前的历史提交保留
- 物理删除统一经过引用守卫:仍被历史版本引用的对象字节不会被误删。
7. 出题与管理速查
| 我想… | 去哪里 |
|---|---|
| 改题面/配置但不影响现在的作答 | 编辑器「保存草稿」 |
| 让新内容生效 | 编辑器「发布版本」 |
| 只认某一版成绩 | 管理端「题目版本」→ 题库策略 exact(X) |
| 换掉某场竞赛的固定版本 | 管理端「题目版本」→ 竞赛固定版本(可同时提交新策略) |
| 用最新版重测一批提交 | 管理端「批量重测」→ 目标「最新版」 |
| 让选手用最新版重做 | 选手在「我的提交」勾选 → 批量升级(管理员可在管理端代做) |
| 查看某条提交在各版本的判定 | 提交详情 →「各版本判定」表格 |
版本号只在单题内递增:P12 的 V3 与 P13 的 V3 是不同版本。数据库与 API 使用版本 UUID, 数字版本号只用于展示与批量选择。