Files
qinglong/docs/adr/ADR-0067-sqlite-fact-driven-local-run-startup-recovery-preflight.md
T

90 lines
6.3 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-0067SQLite 事实驱动的本机 Run 启动恢复预检
- 状态:Superseded(由 ADR-0068 细化)
- 日期:2026-07-20
- 关联 RFCQL-RFC-0001 D-02、D-17、D-37、D-42、D-52、D-55、D-62、D-65、D-66
- 关联 ADRADR-0001、ADR-0007、ADR-0040、ADR-0044、ADR-0053、ADR-0056、ADR-0063、ADR-0066、ADR-0068
## 上下文
ADR-0066 已建立 `adopted storage → assembly → recovery → lifecycle → admission` 激活门,但由注入 stack 返回的 recovery summary 仍是进程内声明。如果具体 stack 尚未装入真正的 Run Reconciler,一个恒真的 `safe: true` 就可能让 SQLite 中已有 `dispatching/running` Run 的 target 开放 admission。这违反数据库事实源原则,也会把“组合边界已成立”误报成“历史执行副作用已收敛”。
本机 Profile 还必须覆盖低性能路由设备。启动检查不能扫描全表、创建第二个 SQLite 连接、安装 timer/watcher,或为零候选启动完整 recovery supervisor;但 standalone 也不能因为数据更多而静默截断后继续启动。
## 决策
### 1. 在 runtime-core 定义 Profile-neutral 只读候选端口
新增 `LocalRunStartupRecoverySource`,只暴露 `inspectCandidates({ limit })`。返回页只有严格字段:
- `candidates``runId``runStatus``activeAttemptCount`
- `truncated`:查询结果是否超过本页硬上限。
候选状态只允许 runtime-owned Run 的 `dispatching/running``queued`、等待、retry、terminal 和 legacy-owned Run 不属于这次启动遗留预检。端口只发现事实,不 claim、不推进状态、不调用 Executor,也不决定是否重放。
### 2. SQLite adapter 复用唯一 Repository authority
`@qinglong/local-sqlite` 在现有 `LocalSqliteRunRepository` 上实现该端口,复用同一个 `DatabaseSync`、串行 operation queue、256 pending-operation 上限和 close fence。禁止为预检创建第二连接、后台 worker、timer 或 watcher。
查询按 Run ID 确定性排序,使用 `limit + 1` 判定截断;单次 limit 必须在 1–256 内。每个候选同时计算 `claimed/starting/running` Attempt 数量,但候选身份由 Run durable status 决定。任何 row、计数或页契约畸形都由上层 fail closed。
### 3. Application 在 stack recovery 前强制数据库候选门
enabled 激活顺序细化为:
```text
adopted storage ready
-> assemble side-effect-free application stack
-> receipt-first local Run reconciliation
-> domain recovery summary safe
-> lifecycles started
-> admission installed
-> active
```
组合根固定以 256 为 limit。最初切片只允许 `candidates=[] && truncated=false` 进入 stack recoveryADR-0068 已将该只读门细化为 receipt-first Reconciler:候选可被原子终态化,或在双重 verifier 中被证明为同一个仍存活进程。截断、存储错误、畸形或证据不足仍抛出 `LocalApplicationStartupRecoveryRequiredError`,不得启动 lifecycle 或 admission,并按 ADR-0066 关闭 stack 与 storage、释放 source fence。
错误只暴露候选计数和截断标志,不携带 Run ID、命令、路径或用户输出。stack factory 只允许装配资源,不得在构造期启动 listener、timer、Executor 或 admission;需要副作用的初始化必须进入受 gate 约束的 recovery/lifecycle。
### 4. 本 ADR 的只读切片不冒充 Reconciler
本 ADR 当时交付的是 read-only fail-closed gate,不会把 Run/Attempt 标记为 `lost`,不会检查 completion receipt 或 durable process identity,不会消费取消意图,也不会创建新 Attempt 或重放任务。
ADR-0068 已交付首个真正的本机 Run Reconciler:逐候选先复核 receipt、再检查进程身份,并以 Repository transaction 原子推进 Run/Attempt/event;最终 verifier 允许精确稳定的活进程继续存在。它仍不自动 replay、创建新 Attempt 或替代人工 recovery 产品流程。
## 被否决的替代方案
1. **只信任 stack 的 `safe: true`**:注入契约可在实现缺失时误报,拒绝。
2. **扫描全部非终态 Run**:正常 queued/等待工作会阻止启动且查询无界,拒绝。
3. **发现 `running` 后直接标记 lost**:数据库状态不能证明外部进程或副作用已经停止,拒绝。
4. **截断后检查前 256 条并继续**:会把未观察事实当作不存在,拒绝。
5. **另开 SQLite 连接或后台定时扫描**:制造双 authority、锁竞争和路由设备隐性资源成本,拒绝。
6. **复用 cluster PostgreSQL recovery supervisor**:会把 claim、多副本与 cluster driver 职责带入本机产物,拒绝。
## 影响与未完成项
正向影响:
- application admission 首次直接受真实 SQLite Run 事实约束;
- edge 零候选只支付一次有界只读查询,不增加常驻 timer、连接或内存表;
- standalone 数据超过上限时显式转入恢复要求,而非静默漏检;
- local 与 cluster 共享“独立事实 verifier”原则,但保持不同 adapter 与 recovery authority。
仍未完成:
- 3.0 launcher 生成 completion receipt 与 durable process handle 的纵向装配;
- cancellation/retry、人工 recovery 与 receipt cleanup journal
- 恢复进度、候选详情和人工裁决 API/UI;
- 断电、强杀、真实 Linux x64/arm64 与固定路由设备资源基线。
因此本 ADR 的历史结论仍只证明“存在 durable Run 遗留时不会错误继续激活”;当前自动安全恢复能力与更精确的继续条件以 ADR-0068 为准。
## 验证
1. SQLite 只返回 runtime-owned `dispatching/running`,排除 terminal、等待和 legacy Run,并正确统计 active Attempt。
2. 查询按 Run ID 稳定分页,超过 256 条显式 `truncated=true`;非法 limit 和 close 后访问 fail closed。
3. local-profile 与 adopted-profile 向上提供同一 `startupRecovery` authority,不新建数据库连接。
4. 真实 target SQLite 中存在一个不可判定候选时,local-application 在 stack recovery/lifecycle/admission 前拒绝并释放 source fence;可信回执或 exact 活进程按 ADR-0068 收敛。
5. 257 个候选时只观察 256 个并因截断拒绝,不能以部分扫描宣称安全。
6. package、导入闭包、production 产物与 RSS 门禁继续证明 edge/standalone 不携带 cluster、legacy ORM 或额外 SQLite native addon。