Files
qinglong/docs/adr/ADR-0446-system-crond-stable-shadow-admission.md
T

82 lines
5.9 KiB
Markdown
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.
# ADR-0446System Crond 稳定 Shadow 准入与回调重放
- 状态:Accepted
- 日期:2026-08-18
- 关联 RFCQL-RFC-0001 D-02、D-354、PR-4
- 关联 ADRADR-0001、ADR-0002、ADR-0445
- AmendsADR-0002 的 Alpha Shadow origin allowlist 与 system crond callback 关联规则
## 上下文
system crond 不由 Node worker spawn。它执行 `crontab.list` 中的 Shell 命令,`task.sh/share.sh` 再分别向
`/open/crons/status` 发送 running 与 idle callback。旧 callback 只有 Cron ID、PID、log path 和秒级开始时间:running callback 丢失时,finish
无法证明 accepted identity;重复、乱序或 HTTP response loss 又可能把同一次执行创建为多个 Shadow Run。因此 D-353 明确拒绝从结束事实直接伪造
`scheduled_system` Run。
这条边界还必须区分 Node scheduler 和面板手动执行。三者最终都可能进入同一 Shell 脚本;仅凭 `ID`、PID 或 `real_log_path` 猜测来源会把同一次手动
执行同时记为 `manual``scheduled_system`
## 决策
1. 只有 `CronService.setCrontab` 在实际 system scheduler 模式写出的命令增加
`QL_EXECUTION_ORIGIN=scheduled_system`。Node scheduler 注册命令、manual/boot `runSingle` 和直接 Shell 调用不带该标记。
2. Shell 仅在标记存在时生成一次 `legacy-system:<start-seconds>:<uuid-v4>` execution ID,并在 start/finish callback 复用。UUID 优先读取 Linux
kernel random UUID,其次使用 `uuidgen`,最后使用已存在的 Node runtime;三者都不可用或输出非法时不发送 ID,Legacy 执行仍继续且不创建
`scheduled_system` Shadow Run。
3. `/open/crons/status` 只接受严格小写 UUIDv4 和正整数秒时间的可选 `execution_id`。旧客户端没有该字段时继续走原 Cron ID/PID/log correlation
不改变 2.x 请求兼容性。
4. 带稳定 ID 的 callback 使用专用 detached observation,不注册虚构 ChildProcess,也不进入易歧义的本机 registry。running 映射为
accepted→spawned→runningfinish 映射为 accepted→spawned→exited,因此 start request/response 丢失后,finish-only 仍能形成完整的终态聚合。
5. Shadow Run 固定 `executionOwner=legacy`、origin/trigger type `scheduled_system``triggeredBy=legacy:system-crond`、Project `default`
`legacy-cron:<id>` task identity。accepted/scheduled 时间从 execution ID 内的开始秒派生,task revision 摘要与 manual/node Cron 使用相同字段集合。
6. 带 request ID 的 `LegacyShadowRunWriter` 使用 request ID 和 accepted time 派生稳定 UUIDv7 形态的 Run/Attempt ID,并写既有
`(project_id,idempotency_key)` 唯一键。重放只有在 Run、Attempt、task revision、Cron ID、origin、request ID 与创建时间全部一致时才复用;任一
漂移都失败开放,不创建第二个 Run,也不改 Legacy callback 结果。
7. `QL3_SHADOW_ORIGINS` 增加 `scheduled_system`,但默认仍为 off;本决定不开放 Primary,不调用 Executor,不增加网络重试、timer、watcher 或后台
reconciler。
## Response-loss 与乱序语义
- start 成功且 response 丢失:finish 使用同一 execution ID,复用既有 Run 并终结。
- start request 未到达:finish-only 创建同一确定性 Run/Attempt 后直接终结。
- finish 成功且 response 丢失后重放:唯一键和确定性 ID 命中 exact replay,终态与 Event 数不增加。
- finish 先于迟到 start:迟到 start 命中已终态聚合,writer 的幂等状态推进保持终态不变。
- 相同 ID 携带不同任务定义:exact replay 校验失败,Shadow 记录有界 accept failureLegacy 状态更新与任务结果不受影响。
- 无 ID、ID 非法或来源未显式标记:不创建 `scheduled_system` Run;无 ID 的既有 callback correlation 保持原行为。
## 部署与资源影响
- 不新增 workspace package、生产依赖、schema、migration、表、索引、端口、Kubernetes object 或常驻进程。
- 复用 Run 表既有 idempotency unique indexSQLite 与 PostgreSQL Repository contract 不变。
- 默认关闭时后端只多一次缓存 Set 查询;system crond 命令仍可执行,Shell 只在显式标记下读取一个 UUID。
- Edge/路由设备优先读取 `/proc/sys/kernel/random/uuid`,不额外启动 Node;只有缺少 kernel UUID 与 `uuidgen` 时才使用已有 Node runtime 作为
兼容 fallback。
- 不增加 callback retry,避免低配设备网络阻塞扩大;本决定保证重放安全和 finish-only 收敛,不把“最终一定送达”伪装成已解决问题。
## 被拒绝的替代方案
### 继续使用 Cron ID、PID 与 log path
拒绝。它们在并发、PID 复用、`/dev/null` 日志和进程重启后都不能证明一次 execution identity。
### 每个 callback 在后端生成新 ID
拒绝。start/finish 以及 response-loss 重放会产生不同 Run,无法幂等收敛。
### 为 Shell callback 注册虚构 ChildProcess
拒绝。Node 不拥有 system crond 子进程;伪造 handle 会污染取消、恢复和 owner 语义。
### 默认启用 scheduled_system Shadow
拒绝。低写入寿命设备必须显式选择迁移观测成本,且 Primary 与对账门仍未完成。
## 验证
- Shell contract 覆盖显式 origin 才生成 ID、UUID 格式和 callback JSON 原样复用;
- SQLite 集成覆盖 running→finish、start response-loss replay、finish-only、重复终态和 task revision drift
- Bridge/registry/correlation/ChildProcess/ScheduleService/rollout 聚焦回归 38/38 通过,包含真实隔离 crontab 文件写入与 system/node 模式差异;
- `build:back`、4 个 Shell 文件语法检查与完整 backend 回归通过(1,415 pass、2 条条件 skip、0 fail);
- 18-package clean build/test 退出 014/14 静态审计与 14/14 artifact 档位均 compatibleartifact 字节与 D-353 相同;
- 物理 PostgreSQL HA/K3s 只在数据库或部署面变化时重跑,本决定不以相邻阶段证据冒充新运行。