Files
qinglong/docs/adr/ADR-0448-bounded-legacy-shadow-startup-reconciliation.md
T

90 lines
6.7 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-0448:有界 Legacy Shadow 启动恢复
- 状态:Accepted
- 日期:2026-08-18
- 关联 RFCQL-RFC-0001 D-02、D-356、PR-4
- 关联 ADRADR-0001、ADR-0002、ADR-0445、ADR-0446、ADR-0447
- AmendsADR-0002 的 Shadow 启动与恢复边界
- Amended byADR-0449
## 上下文
Legacy Shadow 已能旁路观察同一 ChildProcess、关联跨 worker callback/stop,并为 system crond 提供 response-loss-safe execution ID;但 HTTP worker
重启后,原 worker 的内存注册表和 ChildProcess listener 都已消失。数据库可能留下 queued/dispatching/running 的 legacy-owned Run,而 Legacy
`initData` 又会在 HTTP 启动期间先把旧 RunningInstance 从 running 归一为 stopped。
因此不能照搬 Primary ReconcilerLegacy Attempt 没有可复验的 durable Executor handlePID 可能重用,system crond 还可能独立于 HTTP worker
继续运行。仅用 PID 存活检查会伪造 owner 连续性;把所有非终态 Run 一律 lost,又会提前终结仍可通过稳定 callback 收敛的 system crond。
部署范围同时覆盖低性能路由设备、standalone 和 cluster 节点。启动恢复不得引入常驻 watcher、第二个 SQLite authority 或对 cluster profile 的本机
数据库误装配。
## 决策
1. 只有 `QL3_SHADOW_ORIGINS` 至少显式启用一个已审 origin 时,HTTP worker 才加载恢复 Source、Run Repository、Writer 和 Reconciler。默认 off
不导入重型 adapter、不查询数据库,也不创建 timer/listener。
2. 恢复发生在 Legacy `initData` 完成 RunningInstance 状态归一之后、manual Primary activation 与 HTTP listen 之前。这样启动期间没有新的 HTTP
manual admission 与恢复扫描竞争,Primary 也不会先于 Shadow 遗留事实审计激活。
3. 新的只读 Source 使用 `(created_at_ms, run_id)` keyset 分页,只扫描 enabled origin、legacy owner 且状态为 queued/dispatching/running 的
Run。单页硬上限 64;每个 Cron 的 RunningInstance 证据硬上限 8,超限、重复身份或冲突一律 ambiguous,不猜测更新。
4. Source 只把 RunningInstance 的 log path 转换为现有 36 字符 opaque log artifact ID;原始路径、command、用户名、Run ID 和 Cron ID 不进入
audit message 或指标 label。
5. 状态裁决如下:
- 唯一 PID/log/唯一实例证据已经 finished/error:复用 Shadow Writer 补齐 succeeded/failed
- 唯一 stopped 且带 finished time:补齐 cancelledreason 为 reconcile
- queued + claimed 且没有 spawn 证据:以 reconciler actor 收敛为 cancelled,并记录 acceptance abandoned
- dispatching/running 的 Node worker-owned origin 在重启后没有终态证据:Attempt/Run 收敛为 lost
- scheduled_system 没有终态证据:保持 pending,等待稳定 execution ID callback,不能因 HTTP worker 重启提前终结外部 crond
- 多 Attempt、证据截断、身份冲突或非法状态:只计 ambiguous/failed,不覆盖 Legacy UI 状态。
6. lost/abandoned 的 Attempt 与 Run 使用两个既有原子命令事务推进。若 Attempt 已提交而 Run 响应丢失或进程退出,下次启动会从唯一 terminal Attempt
修复 active Run;不产生第二个 Attempt,也不重放外部副作用。
7. Reconciler 每次 HTTP 启动只运行一次:edge 为 `8 × 1 page`standalone 为 `32 × 4 pages`。页预算耗尽返回稳定 resume cursor 和
`remaining=true`,不在进程内排队、不自动循环;后续差异报表和正式 Primary gate 必须把 remaining/ambiguous/failed 视为未闭合证据。
8. `cluster-control``worker` profile 拒绝本机 Legacy Shadow 恢复装配。它们未来必须使用 PostgreSQL/shared authority 的独立 Reconciler,不能复用
Legacy SQLite。
9. Source、写入、配置或 audit sink 失败均保持 Shadow fail-open,只输出低敏 error type/有界 summary,不阻止 2.x HTTP 服务启动。
## 资源与部署影响
- 不新增 package、生产依赖、schema、migration、表、索引、进程、线程、端口、timer、watcher 或 Kubernetes 对象。
- edge 每次启动最多扫描 8 个 Run,每个带 Cron identity 的候选最多读取 8 条 RunningInstancestandalone 最多扫描 128 个 Run。
- 查询使用现有 Runs/RunAttempts 索引与 keyset,不用 OFFSET、不全表加载 Attempt,也不把原始日志路径带出 adapter。
- 默认 off 和 cluster/worker profile 都是零 Repository、零恢复查询、零写入;只有显式 Shadow 的本机 profile 承担一次性启动成本。
## 被拒绝的替代方案
### 复用 Primary Startup Reconciler
拒绝。Primary 依赖 durable Executor handle、receipt journal 和 runtime ownerLegacy Shadow 不具备这些证据,复用会把 PID 猜测伪装成精确身份。
### 启动后定时全表扫描
拒绝。它会给路由设备增加常驻 timer、重复数据库唤醒和不可控历史扫描,也与当前一次性启动门边界不符。
### 所有旧 active Run 一律 lost
拒绝。system crond 独立于 HTTP worker,并可用稳定 execution ID 在重启后补发终态 callback;提前 lost 会丢弃更强事实。
### 在 Source 中复用原始 log path 作为跨层 identity
拒绝。原始路径可能暴露任务结构或用户信息;adapter 内必须先转换为现有 opaque artifact ID。
### 在 cluster profile 打开同一 SQLite 恢复器
拒绝。多节点会形成多个本机 authority,既无法看到共享事实,也可能产生冲突终态。
## 验证
- 真实 SQLite 覆盖唯一 RunningInstance 成功终态、startup-reset lost、spawn 前 abandoned、显式 stopped、system-crond pending、重复身份拒绝、
terminal Attempt response-loss 修复和稳定 keyset 分页。
- Bootstrap 合同覆盖默认关闭零 execute、cluster/worker 拒绝、edge/standalone 独立预算、低敏失败开放,以及
`Legacy normalization → Shadow recovery → Primary activation → HTTP listen` 顺序。
- 聚焦测试 `15/15`Legacy/Shadow 扩展 `63/63`Legacy 身份专项 `8/8`,两者串行组合 `71/71`;完整 backend
`1,434 pass / 0 fail / 2 conditional skip`18 个 QL3 package 均完成 clean build/test`build:back` 通过。
- 14/14 static audit 与 14/14 artifact audit 通过。edge/standalone 的 base、adopted、application、application-api、AI、
application+AI、MCP 产物分别为 `2,589,998 / 2,590,076``2,809,293 / 2,809,416`
`3,632,877 / 3,632,997``3,800,430 / 3,800,574``3,069,251 / 3,069,341`
`4,493,151 / 4,493,283``7,315,930 / 7,316,038` bytes,与前一阶段一致。
- 本切片不改 schema/migration、数据库连接拓扑、容器或 Kubernetes 资源,未重跑物理 PostgreSQL HA/K3s 门;对象存储
backup/WAL/restore/PITR 与 cert-manager mTLS 轮换继续保留为发布最终化现场证据门。