docs(platform): describe controlled judging startup and recovery

This commit is contained in:
2026-10-09 10:06:17 +08:00
parent 3e44779578
commit 81f8dc3204
7 changed files with 99 additions and 13 deletions
+4 -3
View File
@@ -5,8 +5,8 @@
当前版本为 **0.0.1**:沙箱已有受控隔离、C/C++ 编译及 native 执行;平台已实现真实身份、
不可变题目、工件与幂等提交创建。事务 outbox、持久化 Coordinator、普通 Worker 和受控
helper 工厂已接通 C/C++ 编译一次、多测试点评测;前端业务接入仍在开发,不能将前端预览
成绩当作实际结果。
helper 工厂已接通 C/C++ 编译一次、多测试点评测;登录、题库、提交和个人成绩页面
使用真实接口。训练、比赛、社区等扩展页面仍为开发预览。
## 工程划分
@@ -71,7 +71,8 @@ make sandbox-probe # 只读观察 Linux 环境,不创建隔离资源
开发中的规划文档仅在本地维护,不包含在远端仓库中。
单机提交服务的配置、最小权限、停止恢复与独立 VM 验收要求见
[判题说明](judge/README.md#单机持久化协调与常驻-worker)和[部署说明](deploy/README.md#单机提交服务的启动与停止)。
后续接入平台成绩查询与前端,再实现分布式恢复、并行优化和学习功能。
当前范围为单机普通题、已有账号、固定 GNU C17/C++20 与精确字节比较;自助注册、
自测、SPJ/交互题、Python/Rust 用户程序、分布式调度和学习能力仍待后续实现。
## 许可证
+10 -2
View File
@@ -1,7 +1,15 @@
KF_OJ_ENVIRONMENT=development
KF_OJ_LOG_LEVEL=INFO
# Optional until database-backed use cases are implemented.
# Dedicated platform login inherits only kf_oj_platform; replace development credentials.
KF_OJ_DATABASE_URL=postgresql+asyncpg://kf_oj:local-dev-only@127.0.0.1:5432/kf_oj
KF_OJ_AMQP_URL=amqp://kf_oj:local-dev-only@127.0.0.1:5672/kf_oj
KF_OJ_BROWSER_ORIGINS='["http://localhost:5173","http://localhost:8000"]'
KF_OJ_BROWSER_ORIGINS='["http://localhost:5173","http://localhost:8000","http://127.0.0.1:5173","http://127.0.0.1:8000"]'
KF_OJ_SESSION_LIFETIME_SECONDS=43200
# Required for submissions: private absolute artifact root and trusted JSON admission policy.
# KF_OJ_ARTIFACT_ROOT=/srv/kf-platform/artifacts
KF_OJ_ARTIFACT_MAX_BYTES=536870912
KF_OJ_ARTIFACT_MAX_FILES=4096
# KF_OJ_JUDGE_ADMISSION=<JSON matching the Coordinator and Worker policy>
# Cancellation requires both; token equals Coordinator platform_token, never a Worker token.
# KF_OJ_COORDINATOR_URL=http://127.0.0.1:8100
# KF_OJ_COORDINATOR_TOKEN=<privately generated 64 lowercase hexadecimal characters>
+13 -2
View File
@@ -17,7 +17,7 @@ make worker-check
API 提供存活、真实登录/退出/当前用户、公开题目与有效 Worker 租约的清单/工件读取。
配置数据库后在 lifespan 内组合模块,配置缺失时业务端口明确不可用,存活接口仍可用。
Worker 目前仅支持 `--check`,默认拒绝进入尚未实现的任务循环。
学习 Worker 目前仅支持 `--check`;Rust 判题 Worker 使用单独的常驻消费入口。
提交创建 API 在持久化源码后,同一事务写入初始评测和 outbox;按用户与幂等键
防止重复创建,待评测总量和个人待评测数有固定上限。
@@ -28,7 +28,18 @@ Worker 目前仅支持 `--check`,默认拒绝进入尚未实现的任务循环
队列最多 64 条、32 KiB,拒绝溢出,消息有效期 10 分钟;broker 不可用采用有界退避,
不阻塞提交事务。日志仅固定错误代码,不打印源码、答案或连接口令。
后台每次投递最多 12 秒,关闭连接最多 2 秒,SIGTERM 在当前有界操作结束后退出。
账号/题目/工件命令及限制见对应模块 README,前端成绩读取与模型接入尚未实现。
账号/题目/工件命令及限制见对应模块 README;模型能力尚未接入。
个人提交查询 `/api/v1/submissions` 使用 `(created_at,id)` 降序游标,最多100条;
`/{id}`、`/{id}/source` 仅允许提交本人读取,统一拒绝外来/不存在记录。
成绩投影不包含租约、工件摘要、隐藏用例 ID/输入/答案或程序输出;编译诊断至多64KiB,
缺失计量保留 null,未完成报告不展示成绩。身份/提交/内部响应均为 no-store。
`POST /{id}/cancel` 验证本人、Origin、会话和 CSRF,再调用 Coordinator 接受取消;
202仅表示已持久化意图,实际停止与资源回收由执行服务确认。
配置 `KF_OJ_COORDINATOR_URL` 为数值 loopback HTTP origin,
`KF_OJ_COORDINATOR_TOKEN` 与私有 Coordinator 的 `platform_token` 一致;
缺少配置或通信失败返回503,不直接修改执行状态。新增运行依赖 `httpx` 仅负责这个
3秒有界控制调用,禁用代理/重定向,不缓存或打印远端响应及内部凭据。
Python 虚拟环境位于 `backend/.venv`,可在 `backend/` 直接使用 `uv sync --locked` 和 `uv run --locked`。
数据库由 Alembic 唯一管理;业务模型在 `migrations/env.py` 中显式登记元数据。
@@ -3,7 +3,10 @@
负责提交幂等请求、不可变源码引用、创建初始评测、历史查询与学生可见结果。
创建提交、评测和待投递事件必须处于同一事务。
协调器拥有领取后的执行状态与最终结果写入权;本模块不能绕过协议覆盖终态。
`queries.py` 提供已验证主体的私有源码读取,其他账号不可读取。
`queries.py` 提供已验证主体的提交历史、详情与私有源码读取,其他账号不可读取;
历史按创建时间/UUID降序游标翻页,一次至多100条。读取完整成绩时严格核对报告契约和
评测绑定,仅投影允许公开的诊断/计量/测试点序号。取消通过注入的 Coordinator 控制
端口接受意图,不更新执行状态;202与清理完成的终态不同。
`worker_access.py` 核对当前 evaluation / attempt / Worker / fencing、未撤销及未过期凭证、
固定 submission 与版本及任务清单摘要;只允许读取该清单声明的工件。
内部 API 使用 WorkerLease bearer 和 X-Worker-ID / X-Fencing-Version,不使用用户会话授权。
@@ -14,5 +17,9 @@
Python/Rust `manifest_bytes` 与共享字节样例保持一致;Coordinator 必须用同一序列化定义存快照。
读取凭证与客户端提交的身份分开:身份和归属从实际数据库绑定获得,不接受客户端自报 user ID。
当前只实现读取与授权基础。提交创建/幂等/outbox、Coordinator 正式 claim、报告及用户成绩
查询仍在后续模块阶段,不能把测试数据库 fixture 的租约当作正式领取或实际执行证据。
`service.py` 按账号与幂等UUID绑定版本/语言/源码摘要,持久化源码后同一事务创建
submission、queued evaluation 和 outbox;同键同内容复用既有提交,不同内容409。
待评测固定总量64、个人8,取消清理中也占用容量;配置或容量不足明确拒绝。
Coordinator 通过正式 claim 创建短期领取权,Worker 不写业务数据库。
真实服务链与安全条件必须在独立 VM 验收;数据库 fixture 的租约或合成报告只能
验证控制规则、授权和投影,不能作为真实程序执行/停止证明。
+43
View File
@@ -59,6 +59,49 @@ Sandbox 同版本仅为独立 VM 测试数据生成的开发依赖,Engine 不
最后停止工厂和本地依赖。强制死亡后的旧 attempt 必须撤销并取得实际停止证明;
`cleanup_failed` 不等于普通终态,先核对保留资源再由管理员恢复工厂/Worker。
平台配置私有 `KF_OJ_ARTIFACT_ROOT`、有界字节/文件容量以及
`KF_OJ_JUDGE_ADMISSION`(完整可信 JSON);其工具链、编译/运行上限、总体作业期限、
并发/prefetch/retry 必须与 Coordinator/Worker 一致。单机使用并发1、prefetch1,
不能仅提高 Worker 数量绕过节点容量。源码最多256KiB,测试点最多256个;
stdout/stderr 各不超过 file_bytes,合计不超过 workdir_bytes。
迁移账号与平台账号分开配置,命令均在 `backend/` 运行:
```sh
uv run --locked alembic upgrade head # 私有迁移账号,仅执行受控 schema 操作
uv run --locked kf-accounts create local_user # 换成平台角色;终端读取密码并二次确认
uv run --locked kf-problems /srv/private-import
uv run --locked uvicorn kf_oj.api.app:create_app --factory --host 127.0.0.1 --port 8000 \
--no-proxy-headers --limit-concurrency 32 --backlog 64
uv run --locked python -m kf_oj.infra.messaging.publisher
```
每个账号操作/导入目录都由管理员准备,不从浏览器接受私有路径。导入格式见
[题目模块](../backend/src/kf_oj/modules/problems/README.md),不可变版本以 UUID
在题库/题面链接和提交请求中传递。更新题目需导入新版本,不覆盖已有提交的题目版本。
临时验收使用合成数据和测试口令,实际业务数据与服务配置都保存在仓库外的私有目录。
取消另需 API 私有配置 `KF_OJ_COORDINATOR_URL=http://127.0.0.1:8100` 与
`KF_OJ_COORDINATOR_TOKEN`(Coordinator 的 platform_token)。所有服务令牌私下生成,
不可互用或放进前端环境。Coordinator/Worker 私有 JSON 为0600、单链接、有界文件;
Root 镜像/配置/二进制由 root 持有,任务委派子树独立于监督器自身 cgroup。
运行前实际读回整体节点、服务和任务 memory/swap/pids/CPU/文件/运行期限上限,
并通过真实隔离门控;配置解析、readiness、存活探测都不能替代执行验收。
前端从 `frontend/` 启动 `pnpm dev`,默认127.0.0.1:5173;平台明确允许相同浏览器
Origin,`/api` 同源代理。生产需另行配置 HTTPS、安全 cookie、SPA 回退与 `/api`
反向代理,本仓库不自动部署。用户以受控账号登录,在题面选择支持语言、提交源码,
随后查看同一 submission UUID 的持久化状态。等待/取消中/系统失败都不等于成功成绩。
broker 暂停时 API 仍能持久化 queued/outbox;恢复后 publisher confirm 与 Coordinator
补偿重新投递。Worker/Coordinator 重启使用原私有配置、工件、日志与 DB,不删除
attempt 或强改状态。旧租约不能覆盖新结果,有限基础设施重试最终收敛。
容量满时返回429或503;先确认待执行/清理状态,恢复服务或由管理员扩充经过核对的
存储容量,不删除仍被提交/题目引用的工件。`kf-artifacts --limit 100` 仅回收满足年龄
要求的无引用工件和已标记删除项;它不会通过删除用户源码来腾出容量。
恢复 Root 必须核对 journal/inode/实际排空;未知资源保留,管理员处理自己确认的
故障来源后再重启。不要递归清空任务目录,也不要把 cleanup_failed 改成已成功。
这些入口和顺序不表示生产部署已完成。隔离/超限与用户程序仅在无 NIC、无宿主共享
挂载、无业务凭据且整体上限已读回的独立测试 VM 内验收。开发 Compose 仍只提供可信
数据库和 broker,不在宿主或业务服务器运行用户代码。workflow 如需启用由指定
+18 -2
View File
@@ -1,7 +1,8 @@
# Vue 前端工程
Vue 3 + TypeScript + Vite + Element Plus,使用 Vue Router 与 Pinia。
当前页面是开发预览和实际 API 连接检查;没有模拟登录或模拟评测结果。
登录、已发布题库、题面、提交与个人成绩页面使用真实 API。训练、比赛、社区等
本轮以外页面仍为明确的开发预览,不提供这些尚未实现的业务能力。
```bash
pnpm install --frozen-lockfile
@@ -24,4 +25,19 @@ pnpm build
API 类型更新执行根目录 `make contracts`;前端通过 `pnpm check:api` 拒绝过期类型。
登录凭据、模型密钥和数据库连接不能进入 `VITE_` 环境变量。
`pnpm test` 验证代码草稿在刷新、切到后台与页面离开时的保存行为,已纳入 `make check-frontend`。
已有账号登录使用 HttpOnly/Strict cookie 与单独 CSRF cookie;401清除内存身份,
保护页面重新登录后继续。自助注册暂不开放。密码/会话不存入 localStorage。
浏览器与 API 使用同一 origin 的 `/api`,明确配置 `KF_OJ_BROWSER_ORIGINS`;
生产必须使用 HTTPS、安全 cookie 与独立部署的反向代理。
编辑器保留每题/每语言本地草稿,新增 C17 并保留原 C++/Python/Rust 草稿;
仅题目声明支持的 C17/C++20 可提交。提交凭据按账号/版本/内容 SHA-256 绑定,
POST前先保存 UUID,双击共用请求,网络超时/刷新重试复用原键;内容变化产生新键。
草稿和已保存凭据不因失败/退出而清除,本地存储不可用时拒绝发起无法稳定重试的提交。
详情和列表查询每2秒一次,每轮最多240次/10分钟;终态、错误或离页停止,隐藏页面
取消当前查询,恢复可见时继续原预算。路由变化/会话变化后的旧响应不能覆盖当前视图。
耗尽后可手动刷新;不会用预览成绩填补错误。取消受理与最终停止分别展示。
运行指标仅来自完整持久化报告,null显示“—”;诊断和源码按纯文本显示。
`pnpm test` 覆盖草稿、幂等重试、轮询期限/隐藏/终态/失效响应和401处理,
已纳入 `make check-frontend`;真实浏览器编译/判题另在受限独立 VM 验收。
+1 -1
View File
@@ -58,7 +58,7 @@ helper 不接管 root 或其他身份的输入工件;每任务保留相同的
Engine 仅在真实、无基础设施错误的 exit 0 后逐字节比较输出。非零退出/信号为 RuntimeError,CPU/墙钟、内存、输出限制分别映射 TLE/MLE/OLE,取消与 SystemError 单独表达;不可用计量保持 null。汇总在 Engine 内按输入顺序确定,基础设施错误或取消停止后续执行;空用例和重复 run ID 在执行前拒绝。`judge-once` 返回结构化 verdict、用例与实际计量。新增 `serde` 直接依赖用于本地作业/领域结果类型,Engine 不引入 sandbox 或 OS 库。
本地已提供 native 单用例、编译一次多测试点与平台消费/持久化协调入口;
用户成绩展示、交互题、SPJ 和生产部署仍未实现。`kf-worker --check` 只检查入口,不消费或执行任务。权限 helper 和镜像必须由部署方配置,Worker 不调用 sudo、不修改账号或权限。
个人成绩通过平台归属查询展示;交互题、SPJ 和生产部署仍未实现。`kf-worker --check` 只检查入口,不消费或执行任务。权限 helper 和镜像必须由部署方配置,Worker 不调用 sudo、不修改账号或权限。
## 编译一次、多测试点