mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
90 lines
6.3 KiB
Markdown
90 lines
6.3 KiB
Markdown
# ADR-0067:SQLite 事实驱动的本机 Run 启动恢复预检
|
||
|
||
- 状态:Superseded(由 ADR-0068 细化)
|
||
- 日期:2026-07-20
|
||
- 关联 RFC:QL-RFC-0001 D-02、D-17、D-37、D-42、D-52、D-55、D-62、D-65、D-66
|
||
- 关联 ADR:ADR-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 recovery;ADR-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。
|