docs(admin): document bounded problem maintenance operations

This commit is contained in:
2026-10-12 03:15:51 +08:00
parent 835df6b7e7
commit 2f860a17c9
4 changed files with 151 additions and 1 deletions
+6 -1
View File
@@ -8,6 +8,11 @@
helper 工厂已接通 C17/C++20、固定 CPython 3.13 与 OpenJDK 21 的普通题评测;登录、题库、提交和个人成绩页面
使用真实接口。训练、比赛、社区等扩展页面仍为开发预览。
管理控制台使用同源 Cookie 和服务端维护权限,支持有界 ZIP 题包上传、候选预览、
不可变版本发布、题目下架/恢复及维护审计。初次授权与恢复走独立运维身份的本地 CLI;
导入 Worker 仅解析数据,不执行题包内容或学生代码。操作、限额和恢复步骤见
[题目维护运维说明](deploy/admin-problems.md)。
题目工作台支持当前版本的“本题我的提交”、右侧详情与完整个人详情。
性能展示读取受验证最终 AC 的 CPU 总和与内存峰值,只比较同版本、语言、精确工具链、
预算与可信测量环境的最近有效记录。缺测、旧环境无法验证或其他记录不足五条时明确显示
@@ -17,7 +22,7 @@ helper 工厂已接通 C17/C++20、固定 CPython 3.13 与 OpenJDK 21 的普通
| 目录 | 职责 |
| --- | --- |
| `backend/` | Python 模块化单体、平台 API、学习 Worker、待投递事件发布器及唯一数据库迁移入口 |
| `backend/` | Python 模块化单体、平台 API、题包导入 Worker、待投递事件发布器及唯一数据库迁移入口 |
| `judge/` | Rust workspace:协议、沙箱、判题编排、判题 Worker 和协调器 |
| `frontend/` | 页面、路由、请求生命周期与生成的 API 类型 |
| `contracts/` | 版本化 JSON Schema 与 OpenAPI 快照 |
+16
View File
@@ -9,6 +9,22 @@ upgrade head`,API 不在启动时创建表。迁移账号拥有该数据库对
协调器组领取和更新评测、插入 attempt / 用例成绩及恢复通知,不能修改账号、题库或提交身份。
Worker 没有数据库角色,也不需要数据库连接配置。
后续迁移另建 `kf_oj_identity_operator` 与 `kf_oj_problem_import` 两个受限 `NOLOGIN`
权限组。前者只通过固定账号授权函数维护管理员能力/启用状态,不能读取口令哈希、
直接修改角色字段或绕过最后一名启用管理员保护;后者仅处理已授权导入、候选/工件与
追加审计,不能写评测状态、attempt、终态或表现指标。判题 Worker 继续无数据库身份。
平台写入权按列收敛;当前会话与授权版本在每次维护事务内重新核对。
可信表现指标由 Coordinator 在有效最终报告事务中派生,平台、导入器和普通 Worker
不得自行写入。候选与发布版本/用例不可变,成功维护回执及审计只追加;清理保留所有
已发布版本、合法旧提交和未结束候选的工件引用。
升级前停止新维护写入、备份专用数据库与私有工件,并用独立迁移账号执行升级和
`uv run --locked alembic check`。既有登录角色继续绑定各自最小权限组,迁移账号不进入
运行服务配置。降级不作为丢弃候选或回滚一次发布的手段;含新语言/已引用数据时的
降级拒绝须先核对原因,不能清空历史来通过检查。账号 bootstrap/恢复步骤见
[维护运维说明](../../deploy/admin-problems.md)。
服务使用各自独立的普通 `LOGIN` 角色,分别加入对应权限组;登录角色不能拥有数据库/表、
不能加入迁移账号或其他高权限组,且必须为 `NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS`。
初始迁移拒绝已有的高权限同名组和继承其他角色的同名组。应用启动时不运行 migration。
+3
View File
@@ -1,5 +1,8 @@
# 本地开发依赖
管理员账号 bootstrap/恢复、有界题包解析服务、不可变发布/下架、审计与回收步骤见
[题目维护运维说明](admin-problems.md)。该说明与服务模板用于受控部署准备,不表示已部署生产。
`compose.dev.yml` 只提供 PostgreSQL 和 RabbitMQ,端口绑定 localhost。
先复制 `.env.example` 为 `.env`,然后在仓库根目录运行:
+126
View File
@@ -0,0 +1,126 @@
# 题目维护的运行与恢复
本说明描述当前工程接口与受限服务配置。独立 VM 验收使用合成数据与仅 VM 有效的
临时口令,生产迁移、安装服务和部署仍需单独安排。运行服务用项目锁文件、Python
3.13.16 和 uv;请求不调用 uvx/pip 或联网安装依赖。
## 迁移、账号和授权
Backend Alembic 是唯一迁移入口。先停止新维护写入并备份专用数据库/私有工件,
以数据库所有者的独立配置执行下列命令;随后切换到平台配置创建普通账号:
```sh
cd backend
uv run --locked alembic upgrade head
uv run --locked alembic check
uv run --locked kf-accounts create maintenance_admin
```
用户名是示例,可按实际账号替换;密码仅在终端交互读取,不写入命令、仓库或浏览器
存储。迁移、平台、Coordinator、授权运维和导入服务分别使用专用登录身份,均不共享
高权限账号。普通登录角色为 `NOSUPERUSER NOCREATEDB NOCREATEROLE NOBYPASSRLS`,
只继承对应 `NOLOGIN` 权限组。判题 Worker 无数据库连接。
通过仓库外的私有配置设置 `KF_OJ_IDENTITY_OPERATOR_DATABASE_URL`,只为授权 CLI
提供继承 `kf_oj_identity_operator` 的专用身份;它必须与平台数据库身份不同。然后执行:
```sh
uv run --locked kf-accounts bootstrap-admin maintenance_admin
uv run --locked kf-accounts grant-admin recovery_admin
uv run --locked kf-accounts revoke-admin maintenance_admin
uv run --locked kf-accounts enable maintenance_admin
```
恢复账号必须先存在。bootstrap 只用于没有启用管理员时;后续授权使用 grant。
数据库在固定操作函数中序列化变更,禁止撤销或禁用最后一名启用管理员。授权版本
变化会使旧会话失效,管理员须重新登录。不要直接更新管理员字段来绕过保护。
## 私有存储与导入服务
API 与普通 Python 导入 Worker 使用同一服务用户可访问的私有工件/暂存目录,目录
归该用户所有、权限 0700;数据库登录身份分别继承 `kf_oj_platform` 和
`kf_oj_problem_import`。导入 Worker 不执行上传内容、参考解或学生代码,不调用 sudo,
不写评测数据库中的执行状态/成绩,也不持有 Coordinator 或 SSH/GitHub 凭据。
外部私有配置需提供 `KF_OJ_DATABASE_URL`、`KF_OJ_ARTIFACT_ROOT`、
`KF_OJ_IMPORT_STAGING_ROOT`、可信 `KF_OJ_JUDGE_ADMISSION` 与
`KF_OJ_IMPORT_WORKER_CGROUP`。工件根、精确固定镜像及准入须与平台一致,网页题包
不能改写工具链/镜像/权限。示例摘要不能作为真实镜像摘要使用。
[导入服务模板](problem-import-worker.service.example) 使用普通 `User=kf-oj`,
128 MiB 内存、swap 0、PIDs 16、最多一核、文件 64 MiB、FD 64、core 0、
`NoNewPrivileges`、整组停止和 30 秒故障重启间隔。部署方先核对实际用户、路径和
环境文件再安装为 `kf-problem-import.service`;其 PrivateNetwork/AF_UNIX 要求使用
可访问的 PostgreSQL Unix socket。API 的连接路径可不同,但必须连到同一专用数据库。
配置好实际受限 cgroup 后,组合入口为 `uv run --locked kf-problem-import-worker`。
仅解析配置或存在模板不代表实际资源隔离已经验证;启动及每次解析均读回必要上限,
身份、目录、上限或新鲜资源证明缺失时拒绝处理并报告不可用。
在已安装且路径核对的服务上,可信运维可执行:
```sh
systemctl start kf-problem-import.service
systemctl show kf-problem-import.service -p MainPID -p ControlGroup -p MemoryMax -p MemorySwapMax -p TasksMax -p CPUQuotaPerSecUSec
systemctl stop kf-problem-import.service
```
停止后读回对应 cgroup 的 `pids.current=0`,再处理自己确认的临时文件;不得只观察
父 PID。父进程异常退出时,活解析子进程仍持有内核文件锁,GC 不能提前释放或复用
该槽位。恢复使用原服务配置,让有界租约/重试收敛;不通过删除任务记录或放宽上限
恢复。错误配置的 Worker 不写就绪证明,网页维护能力明确不可用。
## 题包、候选与发布
管理入口为同源 `/admin/index.html#/problems`,共享安全 Cookie/CSRF 与实时账号权限。
普通用户没有维护能力。题包只接受 stored/deflate 单层 ZIP,`problem.json` 清单、
UTF-8 题面、显式公开样例与隐藏输入/答案必须分别声明。只接受有界 ASCII 普通文件名;
拒绝链接、设备、路径穿越、重复名称、加密/嵌套/未知成员、损坏 CRC 和虚假大小。
不接收外部下载 URL、生成器、脚本或 SPJ,不执行包内内容。
压缩及实际解包分别最多 64 MiB,最多 534 个文件;上传最多 4096 帧、每帧 1 MiB、
整体 30 秒;解析整体 60 秒。每次暂存预约 128 MiB,总预约 256 MiB,上传/解析
全局并发 1、每管理员 1,未结束候选本人最多 10 个/全实例 32 个,操作 24 小时到期,
解析死亡最多领取两次。能力接口返回实际固定边界,管理员不能在清单中放宽。
题包规范与固定语言入口见[题目模块](../backend/src/kf_oj/modules/problems/README.md)。
先以稳定 UUID、实际内容 SHA-256 和字节数建立导入,再向同一操作流式 PUT。
双击、超时、响应丢失和刷新沿用原 UUID;变更内容创建新操作。浏览器只保存账号、
UUID、摘要、字节数和前置条件,不保存包正文、口令、会话或管理员能力。
自动状态查询有界,失败后手动核对原操作。`ready` 是未发布候选,学生不能读取。
候选预览只含公开题面/样例/预算,隐藏答案/工件引用和宿主路径不会下发。
发布使用独立稳定 UUID、本人候选/导入 UUID 与预期活动版本;事务内复核当前授权、
准入和工件实际内容,原子创建新 revision、切换活动版本并追加审计。修改题目通过
新包发布新不可变版本;不得修改已发布内容或硬删被提交引用的旧版本。
发布响应不确定时读回原维护回执与导入状态,不能先在页面声称成功。409 后读回
最新活动版本/状态,再由操作者决定是否按新前置条件操作,不静默覆盖。
下架关闭公开读取和新提交,合法已受理任务继续通过有效租约读取原已发布版本;
本人历史/成绩保留。恢复只切换可用性,不重评旧提交。题目内提交与源码仍只对本人
开放;性能只比较同不可变版本、语言、精确镜像/工具链/预算和可信测量环境的最终 AC,
缺测/无法证明/其他样本不足五条明确不可比较。
## 审计、故障和回收
管理端有限分页展示真实题目、版本历史、本人导入/维护回执和维护审计。
审计含实际主体、对象、结果、时间和操作 UUID,不记录口令、隐藏正文;应用角色没有
UPDATE/DELETE 审计权限。失败/回滚不会创建成功回执、半发布版本或成功审计。
比赛、节点、站点设置等管理区仍明确未实现。
408 表示实际上传期限超出;409 表示内容/版本/状态冲突;413/415 为字节或格式拒绝;
503 可能是资源槽位占用、配置/服务不可用。先检查原 UUID、真实状态、Worker 心跳、
数据库预约及实际 cgroup,按原因有限重试,不能密集重试或把错误当成成功。
解析服务/API 重启后保留持久化操作和原 UUID,让有界恢复完成。丢弃、失败和到期
先清理暂存字节再释放预约;未知/仍被活解析进程锁定的资源保持拒绝并供运维核对。
工件收集器每次只处理有界老且无引用的数据:
```sh
uv run --locked kf-artifacts --limit 100
```
按返回的 metadata cursor 分次继续核对;候选、已发布用例、旧提交/成绩的引用必须
保留。不要手工清空私有工件目录、删除审计或通过改写预约数字腾容量。
业务数据、口令、题包、镜像/构建产物及本地验收记录不进入工程提交。
本地阶段保存使用明确工程分支与串行分次推送,CI/CD 如需运行只使用约定的 GitHub
私有仓库和明确并发限制;Gitea 不执行 workflow。