mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-20 16:07:11 +08:00
148 lines
7.3 KiB
Markdown
148 lines
7.3 KiB
Markdown
# ADR-0136:SQLite Plugin Package 安装仓库与 Head CAS
|
||
|
||
- 状态:Accepted(首建/替换 head、完整 lock、mutation ledger、版本与 record digest
|
||
CAS、bounded recovery、migration/schema/readiness lockstep 已实现;ADR-0137 已补
|
||
PostgreSQL parity,ADR-0138 已补本地 activation publisher/组合;具体产品 consumer、
|
||
startup lifecycle 与生产管理入口仍未开放)
|
||
- 日期:2026-07-24
|
||
- 关联 RFC:QL-RFC-0001 D-08、D-09、D-130、D-132、D-133、D-134
|
||
- 关联 ADR:ADR-0063、ADR-0076、ADR-0132、ADR-0134、ADR-0135
|
||
|
||
## 背景
|
||
|
||
ADR-0134 定义了 profile-neutral PackageLock、安装状态机、CAS commit 和恢复动作,
|
||
ADR-0135 又证明离线 bundle 可被确定性检查并私有 staging。但没有 durable adapter
|
||
时,`queued`、staging receipt、activation receipt、上一代 active pointer 和
|
||
mutation replay 都只存在于内存,进程重启后无法安全裁决。
|
||
|
||
本地 edge/standalone 必须先有低资源、单 SQLite authority 的实现;同时不能为这一
|
||
能力新增只有一个文件的 workspace package,也不能把安装器接入默认启动路径。
|
||
|
||
## 决策
|
||
|
||
### 1. Repository 留在既有 `@qinglong/local-sqlite`
|
||
|
||
新增显式 `@qinglong/local-sqlite/plugin-package-install` subpath,不从 package 根入口
|
||
或 runtime 入口导出,不新增 workspace package和第三方依赖。Repository 复用既有
|
||
`LocalSqliteOperationAuthority` 的单连接有界队列和 Node SQLite transaction authority。
|
||
|
||
禁用插件安装时,edge/standalone 默认 composition 不加载该 subpath,不增加 timer、
|
||
watcher、socket、后台进程或第二个数据库连接。
|
||
|
||
### 2. 三张表分别拥有历史、当前指针和重放事实
|
||
|
||
`0037-plugin-package-installs` 新增:
|
||
|
||
- `QingLong3PluginPackageInstalls`:原子保存每个 installation 的不可变 `lock_json`
|
||
与当前 durable record,同时保留所有 generation 历史;
|
||
- `QingLong3PluginPackageInstallHeads`:每个 Project/Package 只有一个当前
|
||
installation head;
|
||
- `QingLong3PluginPackageInstallMutations`:保存 mutation ID、命令摘要、结果 version
|
||
与 record digest,作为 exact replay ledger。
|
||
|
||
表级 CHECK、FK 和索引将 lock/record JSON 的 installation、Project、Package、
|
||
operation、state、generation、version 与 digest 同索引列绑定。Repository 的
|
||
`findLock` 每次读取重新规范化并重算 lock digest。typed Drizzle schema、真实 SQLite
|
||
catalog、migration manifest 和 readiness contract 必须保持 lockstep。
|
||
|
||
`0038-capability-v19` 只允许从 exact local-control-core v18 前驱推进到 v19,并发布
|
||
`"plugin_package_install":1`。runtime readiness 要求 34 张表、全部受审索引、无
|
||
owned-table trigger、完整 migration history 和 exact capability JSON。
|
||
|
||
### 3. 首建也是需要 head fence 的提交
|
||
|
||
领域层新增 `PluginPackageInstallCreate`,固定:
|
||
|
||
- normalized initial queued record;
|
||
- create mutation ID;
|
||
- expected head,首装必须为 null,替换必须精确绑定 installation ID、version 和
|
||
record digest;
|
||
- domain-separated create command digest。
|
||
|
||
create command digest 同时绑定 candidate record digest 和 expected head,不能复用
|
||
record 自己的 initial mutation digest。这样同一 queued record 不能在审批后从
|
||
“首装”静默改成“替换另一 head”,也不能改变被替换 generation 后继续命中 replay。
|
||
|
||
替换只接受同一 Project/Package 的不同 installation;上一 head 必须为
|
||
`active | failed` 终态,且候选 record 的 previous-active pointer 必须与其一致。
|
||
首装只接受 generation 1、null previous active 和空 head。
|
||
|
||
### 4. 所有写入使用短 `BEGIN IMMEDIATE` 与 exact CAS
|
||
|
||
create 在同一 transaction 内:
|
||
|
||
1. 复验 Project 存在且未 archived;
|
||
2. 读取并比较 expected head;
|
||
3. 插入 immutable installation、mutation ledger;
|
||
4. 首建或替换 head;
|
||
5. COMMIT 后返回重新规范化的 durable record。
|
||
|
||
后续 commit 同时比较 installation ID、expected version、expected record digest、
|
||
mutation ID/digest 和完整 next record。任一漂移返回稳定 conflict;SQLite、约束或
|
||
corrupt record 统一映射为 unavailable,不泄漏 driver/schema 细节。
|
||
|
||
同 mutation exact replay 返回当前已前进的 record,不把历史结果倒写成旧状态。
|
||
同 ID 不同 command digest 永远冲突。record JSON 每次读取都重新走 runtime-core
|
||
normalizer,不能因为索引列看似正确而接受被手工篡改的内容。
|
||
|
||
### 5. 恢复只扫描当前 head
|
||
|
||
恢复查询只返回当前 head 中 `queued | staged | activating` 的 installation,按
|
||
Package name、installation ID 稳定排序,cursor exact continuation,每页最多 64 条。
|
||
旧 generation 即使历史上仍是非终态,也不允许绕过 head authority 被重新执行。
|
||
|
||
动作仍由 ADR-0134 决定:
|
||
|
||
- `queued` → `resume_stage`;
|
||
- `staged` → `resume_activation`;
|
||
- `activating` → `inspect_activation`。
|
||
|
||
Repository 不读取 bundle、调用 staging、发布资源、注册 Tool/Task/Trigger 或创建
|
||
recovery timer。
|
||
|
||
### 6. 可注入组合不等于生产入口开放
|
||
|
||
本 ADR 不实现:
|
||
|
||
- PostgreSQL parity 已由 ADR-0137 完成,但 production composition 仍不可达;
|
||
- 具体 Approved Action 产品 consumer、Policy/Audit 入口;
|
||
- Task、Workflow、Prompt、Tool 或 Trigger 的原子 generation publisher;
|
||
- production startup recovery lifecycle、operator repair 和旧代 GC;
|
||
- OCI client、publisher revoke/index、管理 API/CLI/UI 或自动更新;
|
||
- Runtime Extension 或动态代码加载。
|
||
|
||
因此 migration 和 Repository 的存在不能被解释为 Plugin Package 已可在生产安装或
|
||
执行。任何产品入口必须在上述闭环及独立审计完成前保持不可达。
|
||
|
||
## 影响
|
||
|
||
- 本地 Profile 获得 crash-safe 安装历史、单 head、exact replay 和恢复候选 authority。
|
||
- SQLite 只在显式安装/恢复操作中付费,不增加常驻资源;低配路由器继续使用单连接、
|
||
bounded page 和短 transaction。
|
||
- `packages/` importer 数不增加;能力留在 runtime-core/local-sqlite 现有包的显式
|
||
subpath。
|
||
- 集群 adapter 已由 ADR-0137 实现,并与 SQLite 共用同一可执行语义合同;后续修改
|
||
不能把 JSON/schema 或锁策略提升为第二套领域语义。
|
||
- bundle staging 成功仍不能直接激活;它只能形成下一次 CAS 所需的 evidence。
|
||
|
||
## 验证
|
||
|
||
单元和架构门禁覆盖:
|
||
|
||
1. initial queued record 与 head-bound create command;
|
||
2. 首装空 head、replacement exact head 和 previous-active fence;
|
||
3. durable install/head/mutation 原子 create;
|
||
4. exact create replay 与 command/head drift conflict;
|
||
5. version + record digest + mutation digest CAS;
|
||
6. record 前进后的旧 mutation replay不回退状态;
|
||
7. terminal-only head replacement 和历史保留;
|
||
8. current-head-only 稳定 recovery pagination;
|
||
9. archived Project 与 corrupt record fail-closed;
|
||
10. `0037`/`0038` manifest checksum、v19 capability 和 typed schema/catalog lockstep;
|
||
11. explicit subpath-only export;
|
||
12. runtime-core、local-sqlite、Profile artifact、edge import 与 dependency audit。
|
||
|
||
ADR-0138 加入共享 lock 合同与本地端到端组合后,当前结果为 runtime-core 215/215、
|
||
local-sqlite 68/68、local-admin 50/50,均 0 fail。生产产品入口、真实断电恢复与
|
||
operator 级安装恢复仍是后续 Gate。
|