13 KiB
ADR-0141:Profile-neutral Approved Action 与 Package 准入边界
- 状态:Accepted(领域契约、
package.managePolicy、SQLite/PostgreSQL durable authority、immutable proposal、dispatch execution/start barrier、Package 原子 准入回执、产品 dispatcher/Package handler、准入强绑定、Profile 管理组合与 transport-neutral 认证后管理 facade 已实现;ADR-0149 已完成原子资源 generation identity/source,HTTP/CLI/UI 与资源语义 materializer 待完成) - 日期:2026-07-25
- 关联 RFC:QL-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
前置端口。若产品层把它实现成内存校验或一次性标记,会留下:
- 审批已标记消费但 queued installation 尚未耐久化;
- 调用进程崩溃后没有可发现的 dispatch 继续 exact replay;
- Package stage/activation 与通用 Approved Action start barrier 的责任不清;
- 单 owner 路由设备若强制“四眼审批”将永远无法安装 Package;
- 集群若默认允许自批,又无法表达 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 → consumed,version 固定为 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_console、multi_factor或hardwareassurance。该模式服务单 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 或资源激活。
完整产品链必须按以下顺序实现:
- 管理入口先持久化可由
actionRef解析的 immutable install proposal; - ApprovalRequest 绑定 proposal 的 action/preview digest;
- human decision 与消费生成 durable dispatch/execution;
- dispatcher 在外部副作用前提交 start barrier;
- 同一数据库 transaction 重新验证 dispatch execution fence、proposal、Policy fence,并原子写入完整 PackageLock、queued installation、head、mutation ledger、 Package admission receipt 和低敏 security audit;
- 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 与
audit,coordinator 只在获得 admission receipt 后进入 stage,并对相同 dispatch/lock
做 exact replay。底层 installation repository 仍保留给恢复和契约测试,但产品
coordinator 不得绕过 admission repository 直接 create()。
新增 qinglong/plugin-package-install-proposal@v1 与
qinglong/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 前 retry,start 后异常转
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、短
SERIALIZABLEtransaction 和现有 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-core、package-consumer等 workspace package:拒绝;当前独立 发布、运行和依赖生命周期均不存在,显式 subpath 已足够隔离。
影响
runtime-core多一个纯契约 subpath,workspace 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。
验证
当前门禁覆盖:
- exact request/action shape、digest 与 24 小时生命周期;
- pending/decision/consumption version 和状态 tuple;
- 三次 fence、强认证事实与 immutable dispatch;
human_confirmation同 User 强确认;separation_of_duty同主体拒绝、不同强认证 User 允许;- weak/service/expired principal 拒绝;
- decision/consumption exact replay 与 mutation drift 拒绝;
package.manageowner/admin/operator/viewer/agent Policy 矩阵;- Tool definition 权限解析与未知权限拒绝;
- 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;
- SQLite 0039–0044、capability v22、39 表、request/dispatch digest 防漂移、三次 原子 Audit、pending execution baseline、immutable proposal、Package admission receipt 与显式 subpath;
- 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 闭环;
- PostgreSQL
pg-0019/0020/0021、capability v20、35 表、admin-onlylock_approval_policy_fence、proposal/execution/Package admission receipt ACL、 runtime/worker 零权限与显式 subpath; - PostgreSQL 18.4 arm64 四角色真库 request/decision/consume/dispatch/admission exact replay,30 pass、1 个条件 skip、0 fail;
- cluster-postgres 130 pass/1 条件 skip、cluster-control 139 pass/2 条件 skip、 cluster-admin 63 pass/1 条件 skip,均 0 fail;
- edge/cluster import closure 与依赖审计
findings: []、compatible: true; - PostgreSQL 18 physical streaming、
remote_apply、timeline 1→2 promotion、pg_rewindrejoin 与 21 项 HA gate 全通过;最近一次 fail-closed 289.831 ms、 双 fresh activation 354.635 ms、rewind 1,480.675 ms,unexpected 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 产品闭环。