Files
qinglong/docs/adr/ADR-0141-profile-neutral-approved-action-and-package-admission-boundary.md
T

13 KiB
Raw Blame History

ADR-0141Profile-neutral Approved Action 与 Package 准入边界

  • 状态:Accepted(领域契约、package.manage Policy、SQLite/PostgreSQL durable authority、immutable proposal、dispatch execution/start barrier、Package 原子 准入回执、产品 dispatcher/Package handler、准入强绑定、Profile 管理组合与 transport-neutral 认证后管理 facade 已实现;ADR-0149 已完成原子资源 generation identity/sourceHTTP/CLI/UI 与资源语义 materializer 待完成)
  • 日期:2026-07-25
  • 关联 RFCQL-RFC-0001 D-05、D-08、D-09、D-29 至 D-33、D-130 至 D-139

上下文

Plugin Package 的 Manifest、PackageLock、SQLite/PostgreSQL installation repository、OCI/offline staging、POSIX/Kubernetes pointer publisher 和启动恢复已经具备 可执行合同,Approved Action 到 queued installation 的原子 admission consumer、 immutable proposal repository、dispatch execution/start barrier、调用方驱动的产品 dispatcher/Package handler、两个方言的强绑定 admission transaction 与 Profile 管理 组合及 ADR-0142 的认证后管理 facade 也已完成;生产安装入口当前缺少的是真实 HTTP/CLI/UI authentication/transport adapter 与基于 ADR-0149 active generation 的 资源语义 materializer。

现有 2.x back/runtime ApprovalRequest 实现有 durable dispatch 和 start barrier 但它依赖 legacy Sequelize 表、旧 Project Policy 类型和旧进程 lifecycle。让 3.0 Package 安装直接适配该实现会违反 3.0 新领域不得继续写入旧架构的边界,也会让 cluster-control 反向依赖 SQLite/Sequelize。

早期的 PluginPackageApprovedActionConsumer.consume(lock) 只是安装 coordinator 前置端口。若产品层把它实现成内存校验或一次性标记,会留下:

  1. 审批已标记消费但 queued installation 尚未耐久化;
  2. 调用进程崩溃后没有可发现的 dispatch 继续 exact replay
  3. Package stage/activation 与通用 Approved Action start barrier 的责任不清;
  4. 单 owner 路由设备若强制“四眼审批”将永远无法安装 Package;
  5. 集群若默认允许自批,又无法表达 separation-of-duty。

因此必须先建立 3.0 profile-neutral Approval 契约,再实现两个方言的同语义 authority, 不能用一个“总是成功”的 consumer 打通产品入口。

决策

1. Approval 契约进入既有 runtime-core

新增 @qinglong/runtime-core/approved-action 显式 subpath,不新增 workspace package 或第三方依赖,也不从 edge 禁用路径装配 Repository、timer、socket 或数据库。

v1 契约固定:

  • qinglong/approval-request@v1
  • qinglong/approved-action-dispatch@v1
  • canonical permission/actionType/actionRef/actionDigest/previewDigest
  • pending → approved | rejected → consumedversion 固定为 1/2/3
  • request、decision、consumption 三次 Project/RoleBinding fence
  • 24 小时最大生命周期;
  • decision/consumption mutation identity 和 exact replay
  • immutable dispatch 同时携带审批人、强认证 ID/assurance、批准时间、过期时间和最终 consumption fence。

Action digest 或 preview digest 不是执行授权;只有 durable consumed request 产生的 immutable dispatch 才能进入后续执行。

2. Package 使用独立 Policy 权限

Project Policy 新增 package.manage

  • owner、admin 可直接发起 Package 管理;
  • operator、viewer 不可管理 Package
  • Agent 即使具备 admin binding,也只能得到 require_approval
  • Tool definition 可声明该权限,但仍必须通过统一 Policy/fence 规划。

不复用 project.manage,避免为了安装 Package 同时授予 Project 生命周期管理权限。

3. Edge 与 Cluster 共用记录,选择不同 ceremony

ApprovalRequest 显式记录 decision mode

  • human_confirmation:允许请求者本人在独立 decision mutation 中确认,但必须是 active User principal,并具备 local_consolemulti_factorhardware assurance。该模式服务单 owner 的 edge/standalone
  • separation_of_duty:除同样的强认证要求外,审批 User 必须与 requester 不同。 cluster 部署可由 Project/组织 Policy 强制选择该模式。

single_factor、service principal、过期 principal 和非 User decision 全部失败关闭。 同一底层记录格式不按 Profile 分叉;差异只存在于受信管理 composition 选择的 mode。

4. Durable dispatch 只授权原子准入

Package Approved Action 的业务结果定义为“把一份 digest-bound PackageLock 与 queued installation 原子准入”,而不是在 Approval transaction 内完成 OCI 下载、解包、 Kubernetes publish 或资源激活。

完整产品链必须按以下顺序实现:

  1. 管理入口先持久化可由 actionRef 解析的 immutable install proposal
  2. ApprovalRequest 绑定 proposal 的 action/preview digest
  3. human decision 与消费生成 durable dispatch/execution
  4. dispatcher 在外部副作用前提交 start barrier
  5. 同一数据库 transaction 重新验证 dispatch execution fence、proposal、Policy fence,并原子写入完整 PackageLock、queued installation、head、mutation ledger、 Package admission receipt 和低敏 security audit
  6. dispatcher 以 receipt 完成;Plugin Package recovery 再从 queued 状态有界收敛 staging/activation。

这样,start 后响应丢失只能由 Package admission receipt 和 installation durable fact 裁决;不会再次执行审批,也不会把长时间网络/Kubernetes I/O 放进数据库 transaction。

现有 PluginPackageInstallationCoordinator 已删除注入式 consumer,改为调用 PluginPackageAdmissionRepository.admit()SQLite 与 PostgreSQL adapter 都能在单一 事务内提交 PackageLock、queued installation、head、mutation、admission receipt 与 auditcoordinator 只在获得 admission receipt 后进入 stage,并对相同 dispatch/lock 做 exact replay。底层 installation repository 仍保留给恢复和契约测试,但产品 coordinator 不得绕过 admission repository 直接 create()

新增 qinglong/plugin-package-install-proposal@v1qinglong/approved-action-execution@v1 纯契约及两个方言的显式 repository subpath。 consume 现在把 immutable dispatch 与 pending execution baseline 在同一事务提交; 只有 pre-start lease 可以释放或接管,executing 过期必须进入 recovery_required,不得盲重放。proposal 持久化同时重新验证 Project/RoleBinding Policy fence,并与 plugin_package.propose audit 原子提交。

@qinglong/runtime-core/approved-action-dispatcher 现在提供无 timer、调用方驱动、 每次最多 64 条的通用 dispatcher。它固定执行 list → claim → inspect → durable start barrier → handler → complete;只允许 start 前 retrystart 后异常转 indeterminate/blocked,且 start/complete response loss 都先读取 durable execution 收敛,不会重做 handler 副作用。plugin-package-approved-action handler 重新读取 proposal、确定性派生 installation/mutation/audit identity,并只调用原子 admission admission COMMIT 响应丢失时以 receipt + installation exact replay 收敛。

SQLite BEGIN IMMEDIATE 与 PostgreSQL SERIALIZABLE admission transaction 都会重新 读取 durable proposal、当前 executing execution 和 Project Policy fence。除请求内 exact execution snapshot 外,adapter 还必须用事务内数据库时钟确认 lease 在观察时 仍有效,不能信任调用者提供的历史时间;已有 receipt 的 exact replay 不再要求旧 execution 仍处于 executing,因为 replay 本身不产生新副作用。

这仍不等于公开产品入口已经开放:dispatcher 和 ADR-0142 管理 facade 只从 @qinglong/local-admin/package-approved-action@qinglong/cluster-admin/plugin-package-approved-action 及对应 plugin-package-management 显式管理子入口装配,不从 package root、常驻 runtime、 cluster-control 或 worker 导出。真实 HTTP/CLI/UI 在 credential/local-console/MFA、 rate limit、transport idempotency 与资源语义 materializer 完整装配前必须继续 失败关闭。

5. Profile 资源边界

  • edge/standalone:复用唯一 Node 24 SQLite authority、调用方驱动、单连接、短 BEGIN IMMEDIATE;禁用 Package 时不加载 Approval/Package storage subpath,不增加 timer/watcher;默认单次批量分别为 1/4;
  • cluster:使用 admin-only PostgreSQL role、短 SERIALIZABLE transaction 和现有 COMMIT outcome-unknown 规则;cluster-control/runtime/worker 不获得 Approval decision 或 Package admission 写权限;短生命周期管理调用默认单次批量 16;
  • 两者共用 exact record、transition、receipt 和 repository contract,不复制领域逻辑;
  • 不为 Approval、Package consumer、receipt 或每个 Profile 新建单文件 workspace package。

拒绝的方案

  • 直接复用 2.x Sequelize Approval 表:拒绝;会把兼容实现变成 3.0 永久事实源,并 破坏 PostgreSQL/edge 对等合同。
  • 内存 consumer、总是成功 consumer 或只校验 Lock 字段:拒绝;无法证明 durable decision/dispatch,也无法恢复消费与 queued create 之间的崩溃。
  • 所有 Profile 强制不同审批人:拒绝;单 owner 路由设备无法使用。
  • 所有 Profile 默认允许自批:拒绝;集群无法表达职责分离。
  • Approval transaction 内执行 OCI/Kubernetes/POSIX I/O:拒绝;长事务、响应丢失和 外部副作用会污染一致性边界。
  • 新建 approval-corepackage-consumer 等 workspace package:拒绝;当前独立 发布、运行和依赖生命周期均不存在,显式 subpath 已足够隔离。

影响

  • runtime-core 多一个纯契约 subpathworkspace package 数与第三方依赖数不变。
  • package.manage 是共享 Policy 词表的加法式安全边界;Tool Registry 与角色矩阵必须 持续回归。
  • edge 用户获得可用的强认证自确认路径,cluster 用户获得可强制的四眼路径。
  • 当前 production Package 安装入口仍保持关闭;durable dispatcher/Package handler、 强绑定 admission、Profile 管理组合和认证后 use-case facade 完成,不等于真实 HTTP/CLI/UI authentication/transport 或资源 generation consumer 已完成。
  • 下一阶段 transport 必须调用 ADR-0142 facade,不能绕过 proposal/Approval/dispatcher 直接调用 installation repository。

验证

当前门禁覆盖:

  1. exact request/action shape、digest 与 24 小时生命周期;
  2. pending/decision/consumption version 和状态 tuple
  3. 三次 fence、强认证事实与 immutable dispatch
  4. human_confirmation 同 User 强确认;
  5. separation_of_duty 同主体拒绝、不同强认证 User 允许;
  6. weak/service/expired principal 拒绝;
  7. decision/consumption exact replay 与 mutation drift 拒绝;
  8. package.manage owner/admin/operator/viewer/agent Policy 矩阵;
  9. Tool definition 权限解析与未知权限拒绝;
  10. runtime-core 249/249,覆盖 proposal/dispatch digest 绑定、pre-start retry、 start barrier、post-start 禁止 takeover、dispatcher start/complete response-loss 收敛、确定性 Package handler/admission response-loss 与 result digest
  11. SQLite 00390044、capability v22、39 表、request/dispatch digest 防漂移、三次 原子 Audit、pending execution baseline、immutable proposal、Package admission receipt 与显式 subpath
  12. SQLite admission 成功/exact replay、数据库时钟拒绝过期 execution lease,以及 Policy revoke 后 install、mutation、audit、receipt 全事务回滚;local-sqlite 78/78、local-admin 54/54;后者包含认证后 facade 的 propose→self-confirm→consume→dispatch→queued admission 真 SQLite 闭环;
  13. PostgreSQL pg-0019/0020/0021、capability v20、35 表、admin-only lock_approval_policy_fence、proposal/execution/Package admission receipt ACL、 runtime/worker 零权限与显式 subpath
  14. PostgreSQL 18.4 arm64 四角色真库 request/decision/consume/dispatch/admission exact replay30 pass、1 个条件 skip、0 fail
  15. cluster-postgres 130 pass/1 条件 skip、cluster-control 139 pass/2 条件 skip、 cluster-admin 63 pass/1 条件 skip,均 0 fail
  16. edge/cluster import closure 与依赖审计 findings: []compatible: true
  17. PostgreSQL 18 physical streaming、remote_apply、timeline 1→2 promotion、 pg_rewind rejoin 与 21 项 HA gate 全通过;最近一次 fail-closed 289.831 ms、 双 fresh activation 354.635 ms、rewind 1,480.675 msunexpected domain side effect 为 0。

后续 product Gate 还必须覆盖 PostgreSQL admission 过期租约真库拒绝、两个方言的 并发 decision/consume、Approved Action/Package 端到端 COMMIT-response-loss、真实 credential/local-console/MFA transport、HTTP/CLI/UI、资源 generation consumer 和 完整 edge/cluster 产品闭环。