5.6 KiB
ADR-0529:有界只读 Local 旧面板 Cron Adapter
- 状态:Accepted(D-427 双架构阶段实物已交付)
- 日期:2026-09-02
- 关联 RFC:QL-RFC-0001 D-427、D-423、D-424、D-426c3
背景
QingLong 2.x 面板以 /api/crons、数值型 Cron 行和 {code,data} 包络读取定时任务;QingLong 3.0 Local API 则以 Project-scoped Task、immutable Trigger revision 和 /api/v3 为权威。当前 Console Alpha 已经可以操作 Task、Trigger、Run、日志和 Secret,但它携带的是小型离线 Console,不包含 33 MiB 的 2.x 静态面板,也不能把 2.x JWT、明文 Env、整数 ID 或旧 Service 直接当作 3.0 authority。
直接让旧面板写入 3.0 SQLite 会绕过 Project Policy、credential reconfirm、durable audit、revision/content digest fence,并重新耦合已经分离的 Task 与 Trigger。另一方面,要求所有部署者立即迁移到新 Console 会阻断现有页面的渐进复用。因此需要一个显式、窄面、可逐步扩展的 Adapter,而不是恢复完整旧后端。
决策
第一切片在既有 @qinglong/local-api 内新增 panel-compatibility/ 子域,不新增 workspace package、依赖、数据库表、连接、listener、timer、watcher 或后台进程。
HTTP 与权限边界
- 仅新增
GET /api/crons;没有 POST、PUT、DELETE、login、session、WebSocket 或静态面板分发。 - 请求仍走正式 Local API Bearer credential、
task.readProject Policy、durable security audit 和 credential reconfirm;审计 operation 固定为panel.cron.list。 - 第一切片固定投影
defaultProject。多 Project 选择必须在后续 capability/session 设计中显式增加,不能从未受信 Header、Cookie 或查询参数猜测。 - 默认 headless Application 不包含
@qinglong/local-api,因此没有新增端口或稳态开销。Cluster 不复用本 Adapter,后续使用独立 Cluster Panel Gateway。
有界查询
- 接受旧页面初始读取所需的
page、size、空searchValue、空filters={}和 Axios cache-bustert。 - 非空搜索、排序、View query 或其他字段暂时返回
400 invalid_panel_cron_list_query,不能静默忽略并给出错误结果。 size最大 64;页起点(page - 1) * size必须小于 Edge 64、Standalone 256 行预算。Adapter 以min(page * size, maximumRows)向 Trigger source 做一次有界 keyset 前缀读取,再截取所需页,因此最后一个不足整页的窗口仍可到达,但不能越过 Profile 上限。total是当前已观察前缀加一个truncated继续标记;它足以让旧分页逐页推进,但不执行无界 COUNT 或全表扫描。
领域映射
- 每个
qinglong/cron@v1Trigger 投影为一条旧面板 Cron 行,稳定id使用triggerId字符串,不构造有碰撞风险的伪数值 ID。 - Adapter 按 Trigger 固定的
taskId + taskRevision + taskContentDigest读取 pinned Task revision,并复算 Trigger/Task record 与 cron semantic;缺失、漂移、未知 Trigger schema 或异常页整体返回 503。 schedule、timezone、misfire policy 来自已规范化 Trigger;名称、启停状态来自 pinned Task 与 Trigger 的合成结果。command只返回ql3:<kind>:<taskId>@<revision>描述符,不返回 Task spec、argv、环境、Secret、label、mutation ID 或 content digest。- 返回行附带只读
ql3identity,明确 Project、Task/Trigger revision 与readOnly=true;旧页面当前未知字段会忽略它,后续改造版面板可据此关闭写按钮。
明确不做
本 ADR 不声明现有 2.x 面板可以零修改登录或完整运行。尤其不允许:
- 把
ql3c_credential 放入 2.x 登录密码字段或长期存入 Local Storage; - 复用 2.x Auths/Users/JWT 作为 3.0 Identity/Policy;
- 猜测
/api/crons写操作、整数 ID、Cron View、Subscription 或 Script 文件语义; - 回显 Secret/Env 明文,或将 Task 与 Trigger 的独立 revision 压回一个可直接覆盖的旧对象;
- 为兼容页面扩大 loopback、CSP、response byte、并发或低配资源预算。
验证与后续门禁
本切片通过:
- Adapter 单测:分页、禁用合成、pinned identity、未知 schema、预算和 storage failure;
- HTTP 契约:编码的
{}查询、正式 operation 解析、拒绝非空搜索且不进入 Admission; - Admission 契约:authenticate →
task.read→ audit → confirm → route; - 真实 SQLite 集成:正式 credential、Policy、Task/Trigger revision 和 durable audit,且响应不出现真实 argv;
- Local API 全包、18-package build/test、dependency/import、Console/Headless image 与双架构 artifact 门。
提交 83966a128705f6ef0ae54367c8f656e3d578e9c6 的显式 Local headless run 33576693917 与 Local Console run 33578754251 均成功。headless amd64/arm64/milestone artifact 为 9827434236/9827259756/9827553099,Console 为 9828792297/9828714089/9828941802;均绑定同一 source revision,保留至 2026-10-02。headless 继续不携带 Local API/旧面板;Console artifact 携带本 Adapter,但仍只证明后端兼容切片,不包含改造后的 2.x 静态面板。
后续按 health/system/user capability → Run/Log read → 显式写操作 推进。ADR-0530 已完成 capability/bootstrap、同源静态装配、真实浏览器 journey 和双架构阶段实物;ADR-0531 继续以规范 v3 API、caller-driven 读取和 Profile 预算开放 Cron 日志。完整 2.x 零改兼容不作为 3.0 目标。