Files
qinglong/docs/adr/ADR-0134-content-addressed-package-lock-and-recoverable-install-state.md
T

177 lines
9.4 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-0134:内容寻址 PackageLock 与可恢复安装状态
- 状态:AcceptedOCI/offline 来源锁、digest-bound Approved Action、CAS 安装记录、
exact staging/activation receipt 和有界恢复判定已实现;ADR-0135 已补确定性签名
bundle 检查与 POSIX 私有 stagingADR-0136/0137 已补 SQLite/PostgreSQL durable
repositoryADR-0138 已补完整 lock 持久化、激活协调器与本地 pointer publisher
原子资源 generation 发布与生产 composition 仍未开放)
- 日期:2026-07-24
- 关联 RFCQL-RFC-0001 D-08、D-09、D-130、D-132
## 背景
ADR-0132 已能规范化 Package Manifest 并生成安装、升级和回滚预览,但预览本身不能
证明最终安装的仍是用户审批的字节。若后续安装器只保存 Package 名称和版本,会留下:
- OCI tag、URL 或本地路径在审批后改变;
- Manifest、解包内容和权限预览来自不同制品;
- upgrade 激活到一半时先撤销旧版本,重启后两代都不可用;
- `activating` 状态被盲重放,导致重复注册 Task、Tool 或 Trigger
- SQLite 与 PostgreSQL adapter 各自发明状态和恢复语义;
- 低配设备为了尚未启用的安装器常驻 watcher、timer 或下载进程。
PackageLock 和安装状态机必须先成为 profile-neutral 领域契约,再允许文件、OCI、
SQLite、PostgreSQL 或 Kubernetes adapter 消费。
## 决策
### 1. 继续留在 `@qinglong/runtime-core`
新增 `plugin-package-install` subpath,不新增 workspace package或第三方依赖。它与
Manifest、Tool Registry 同属核心契约,但不读取文件、网络、数据库或环境,不拥有
下载、解包、动态 import、注册或执行能力。
禁用插件时现有 edge、standalone、cluster-control 和 worker composition root 不加载
安装 adapter,也不增加 timer、socket、连接或后台进程。
### 2. 来源只能是不可变内容地址
首版来源锁只接受:
- 带 exact `@sha256:<64 hex>` 的 canonical `oci://` locator
- 不保存主机路径的 `offline:sha256:<64 hex>` bundle identity。
两者都必须同时声明 1 byte1 TiB 的 artifact 长度、artifact SHA-256 和解包后
content SHA-256locator 中的 digest 必须与 artifact digest 相同。拒绝 tag、
`latest`、任意 HTTP URL、query、fragment、可变文件路径和 digest 不一致。
`offline` 只表达审批和审计中的来源身份,不授权打开任意路径。未来 POSIX adapter
必须另外取得私有文件 capability,且读取后复验 artifact/content digest。
### 3. PackageLock 精确绑定预览与已消费审批
`PluginPackageLock` 固定:
- Project、Package name/canonical SemVer、install/reinstall/upgrade/rollback
- OCI/offline 来源、artifact/content digest 与字节数;
- normalized Manifest digest 和 compatible install plan digest
- planner 使用的 QingLong/runtime/资源环境摘要,且 target 架构/Profile 必须一致;
- architecture、deployment Profile、target generation
- upgrade/reinstall/rollback 的 previous lock digest
- Approval request/version、immutable dispatch、human approver、有效期和
Project/RoleBinding fence
- action、preview 与完整 lock digest。
action digest 覆盖所有可能改变安装结果的字段。Approval 的 action digest 必须与其
一致,preview digest 必须等于 install plan digestPackageLock 只能在审批有效期内
创建,且安装记录必须在同一已消费审批有效期内建立。所有 Package 安装仍要求
`user` subject 的人工审批,Agent、MCP Client、API App 或 system 不能充当 approver。
构造 Lock 时必须用 exact environment 和 previous Manifest 重新执行 ADR-0132
planner,结果与传入 plan 摘要一致;不能把低风险伪 plan 与另一份 Manifest
分别摘要后一起锁定。
新安装只能使用 generation 1 且没有 previous lockupgrade、reinstall 和 rollback
必须创建 generation 2+ 的新 lock,并绑定 previous lock。回滚不是把旧状态倒写,
而是以旧内容作为候选生成一个新的、可审计的 generation。
### 4. durable 状态机不拥有副作用
安装记录只有:
```text
queued -> staged -> activating -> active
| | |
+---------+-----------+-> failed
```
- `queued`PackageLock 与已消费审批已耐久保存;
- `staged`artifact、Manifest 和 content digest 全部与 lock 相同,并保存摘要保护的
staging receipt;该 receipt 同时绑定 adapter 可重新检查的外部 evidence digest
- `activating`:外部 adapter 可以开始原子发布,但 active pointer 仍指向旧 lock
- `active`:只有绑定 installation、generation、previous pointer、stage 双证据和
content 的 exact activation intent receipt 提交后,active pointer 才切换到候选 lock
- `failed`:保留失败阶段和受审 reasonactive pointer 仍指向旧 lock。
首装失败时 active pointer 保持 null;升级或回滚失败时旧版本继续 active。终态不能
原地重试,必须创建新 mutation 或新 PackageLock,避免覆盖历史证据。
### 5. CAS、幂等与恢复必须显式
记录带单调 version、last mutation ID/digest 和 record digest。纯领域转换对最后一次
完全相同的 mutation 返回 existing 语义,同 ID 不同内容失败关闭。repository port
接收 expected version、expected record digest、mutation ID/digest 和完整 next record
adapter 必须以事务 CAS 和 mutation ledger 实现 exact replay,不能只做 last-write-wins。
恢复扫描每页最多 64 条:
- `queued``resume_stage`
- `staged``resume_activation`
- `activating``inspect_activation`
- `active | failed``none`
`activating` 只能检查外部 activation fact,再决定提交 receipt 或失败;禁止直接重放
发布。这与进程启动、Approved Action 和 PostgreSQL COMMIT-response-loss 的既有原则
一致:不确定结果先 inspect,不把异常冒充 rollback。
### 6. 本地协议闭环已可执行,但 production 仍 unreachable
ADR-0135 已实现 runtime-core 纯流式确定性 USTAR/Ed25519 检查器与 local-admin 私有
文件 staging adapter,但两个入口都是显式 subpath,未接入生产 composition。
ADR-0136 已实现本地 SQLite migration/schema/readiness、head CAS、mutation ledger 和
current-head recovery scanADR-0137 又以同一可执行合同完成 PostgreSQL
migration/schema/admin-only repository parity。ADR-0138 进一步要求 queued record
与完整 PackageLock 原子保存,并实现了显式 fresh/recovery activation coordinator、
本地 POSIX pointer publisher 和注入 Approved Action consumer 的 SQLite 端到端组合。
这些能力仍只存在于按需 subpath。本 ADR 仍不实现:
- OCI client、publisher trust 获取/撤销和官方索引;
- 具体 Approved Action 产品 consumer、管理 API/CLI/UI 和 startup lifecycle
- Task/Workflow/Prompt/Tool 的原子注册与旧 generation 退役;
- Kubernetes resource 或数据库 active generation publisher
- production startup recovery coordinator 与 operator repair workflow
- Trigger pause/health check、Runtime Extension 或动态代码加载。
在 adapter、Approved Action consumer、原子发布、Audit 和启动恢复闭环前,生产
composition 不得暴露安装入口。
## 影响
- 审批对象从“名称与版本”提升为 exact artifact、内容、Manifest、计划和目标环境。
- edge 可使用无路径的离线 bundle identitycluster 可使用 exact OCI digest,二者共享
同一状态机。
- upgrade/rollback 不会在候选激活前移除旧 generation。
- `packages/` 仍为 21 个 importerADR-0135 也只在既有 runtime-core/local-admin
增加按需 subpath。
- 该 contract 不是零闪存成本:加入 ADR-0138 的显式 activation/installation subpath
后最大制品为 2,825,780 bytes/437 files,最大单次 RSS delta 12,615,680 bytes
loaded module 数仍为 72,低于 4 MiB/512 files/16 MiB 硬门禁。
- SQLite/PostgreSQL adapter 已共用同一 CAS/receipt/recovery 可执行合同,后续变更
不能自行缩窄任一端安全语义。
## 验证
单元测试必须覆盖:
1. immutable OCI PackageLock 与 plan/approval digest binding
2. 不保存主机路径的 offline bundle
3. mutable source 与 source digest mismatch 拒绝;
4. detached、过期和非人工 Approval 拒绝;
5. exact environment/previous Manifest planner 重算与伪 plan 拒绝;
6. upgrade/rollback previous lock 与 generation 约束;
7. queued record 保留 previous active pointer
8. exact staging receipt 与 lock mismatch 拒绝;
9. activating 阶段保持旧 activecommit 后才切换;
10. failure 保留旧 generation
11. last mutation exact replay 与 mutation conflict
12. 非法转换、时间倒退和 record tamper
13. exact CAS commit envelope 与 immutable identity
14. `activating -> inspect_activation` 及 64 条 recovery page 上限;
15. 根入口与 `plugin-package-install` subpath 导出一致。
ADR-0138 加入后当前针对性结果为 runtime-core 215/215、local-sqlite 68/68、
local-admin 50/50、cluster-postgres 非数据库全量 124 pass/1 skip,真实四角色
PostgreSQL 28 pass/1 skip。21 个 importer 的 clean build/聚合测试退出 0
dependency/source boundary 与 edge import 均通过且 `findings=[]`;六种 Profile
artifact 全部通过,PostgreSQL 18 HA 的 21 个具体 gate 与总 `passed` 全为 true。