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

9.4 KiB
Raw Blame History

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 状态机不拥有副作用

安装记录只有:

queued -> staged -> activating -> active
   |         |           |
   +---------+-----------+-> failed
  • queuedPackageLock 与已消费审批已耐久保存;
  • stagedartifact、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 条:

  • queuedresume_stage
  • stagedresume_activation
  • activatinginspect_activation
  • active | failednone

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。