Files
qinglong/docs/adr/ADR-0155-snapshot-bound-trusted-tool-handler-and-execution-admission.md
T

219 lines
10 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-0155Snapshot-bound Trusted Tool Handler 与执行准入
- 状态:Acceptedsnapshot-specific handler binding、分层 action/preview/plan
digest、Approval binding、执行前 Policy 复验和 StepRun/Trace/Audit evidence
admission 纯契约已实现;耐久 plan/preview、StepRun/Trace/Audit repository、真实
adapter 与生产 composition 尚未开放)
- 日期:2026-07-26
- 关联:ADR-0001、ADR-0031、ADR-0032、ADR-0133、ADR-0154、QL-RFC-0001
D-03/D-29/D-131/D-148/D-149
## 背景
ADR-0133 已把 Tool Definition、输入规范化和 Project Policy 计划收敛为不可变纯契约,
ADR-0154 又把 active Package vector 投影为 Project-scoped immutable snapshot。但这
两层都刻意不包含 handler。若 composition 只按 Tool name 找函数,或者让 Package JSON
声明 module、URL、MCP server,就会出现以下旁路:
- 同名同版本 Definition 在 Package generation 切换后继续命中旧 handler
- ADR-0133 的 invocation action digest 未绑定 snapshot、definition digest 或 handler
被误当成最终执行授权;
- preview 由 Agent 自报,审批后可以替换参数、adapter 或 Profile
- Approved Action dispatcher 只按通用 action type 找 handler,无法证明 Tool 对应的
StepRun、Trace 和 Audit 已经耐久化;
- edge 为了“动态插件”常驻目录 watcher/cachecluster 则让普通 Package 获得控制面
module/network authority。
因此 Tool execution 不能直接接到现有 dispatcher。必须先建立独立的可信 binding 和
执行前 admission contract。
## 决策
### 1. Binding 是 Project snapshot 的不可变受审描述符
`qinglong/trusted-tool-handler-binding@v1` 精确绑定:
- Project Tool snapshot digest
- exact Tool name/version 与 definition digest
- reviewed adapter identity 和 canonical SemVer
- execution class
- `edge | standalone | cluster-control | worker` Profile 集合;
- 最多 16 个显式外部 authority
- 只能收紧、不能放宽 Definition 的 timeout
- reviewed redaction contract 与 audit contract identity/version
- domain-separated binding digest。
首版 execution class 固定为:
- `builtin_in_process`
- `isolated_process`
- `remote_worker`
- `mcp_client`
- `http_connector`
authority 使用受限词表,例如 database/filesystem/artifact read/write、
network connect、process spawn、Secret、Model、MCP 和 Run control。词表只表达
composition 需要提供哪些 capability,不自动授予 capability。
一个 snapshot 最多 128 个 binding,同一 Tool identity 只能有一个。不存在于 current
snapshot、definition digest 不同、Profile 不匹配、timeout 扩大或 binding digest
漂移全部失败关闭。snapshot 中没有 binding 的 Definition 保持不可调用。
### 2. Binding registry 不保存 handler code
`TrustedToolHandlerBindingRegistry` 只保存冻结后的描述符,不提供 `register`,也不保存:
- function/`execute`
- module path 或 dynamic import
- command、URL、socket、credential
- filesystem/process/network/database service
- timer、watcher、LISTEN 或可变 current head。
真实 adapter 实例只能由受信产品 composition 按 binding identity 注入后续执行层;
Package materialized resource、Agent、MCP client 和 HTTP caller 均不能创建这个信任。
内置 Tool、MCP、HTTP 或未来隔离 Package runtime 也不能走特殊旁路:其 Definition 必须
先进入受审 Project snapshot,之后才能建立 binding。
### 3. 最终 Tool action 使用分层摘要
ADR-0133 的 `invocationActionDigest` 继续证明 canonical input 与 Policy-fenced
Definition 元数据,但不再被解释为执行授权。ADR-0155 生成:
1. `actionDigest`:绑定 invocation digest、action ref、Project/actor、Policy fence、
snapshot、definition、binding、Profile 和有效 timeout
2. `previewDigest`:绑定 action digest 与受限安全 preview
3. `planDigest`:绑定完整 normalized input、action、preview 和全部摘要。
任何 snapshot、Package generation、adapter、redaction/audit contract、Profile、
authority、timeout、input 或 preview 变化都会产生不同最终摘要。
plan 只由显式调用构造,edge 空闲时没有 cache/timer。当前纯对象仍含 normalized input
未来耐久 plan carrier 必须使用独立受授权 Artifact/plan repository,并根据 Secret
边界决定加密和 retention,不能把 input 写入 Approval/dispatch 行。
### 4. Preview 是有界安全投影,不是任意 JSON
preview 最多包含 16 个 field 和 8 个 warning code。field 只能为:
- `text`
- `identifier`
- `count`
- `redacted`
`redacted` 的 value 必须为 `null`,其余 value、title、summary 都有严格 UTF-8 byte
上限并拒绝控制字符。core 只能保证形状、大小和摘要绑定;具体哪些值必须被隐藏由
binding 中受审 redaction contract 决定。adapter 不得把 Prompt、token、Secret 或完整
credential 伪装成公开字段。
### 5. Approval 复用既有 durable state machine
只有 `approval_required` plan 可以生成 `ApprovedActionBinding`
- permission 为原始 `tool.call:{name}`
- action type 固定为 `tool.invoke`
- action ref、final action digest 和 preview digest 与 plan 精确一致。
随后复用 ADR-0031 的 ApprovalRequest、human decision、一次性 consumption 和 immutable
dispatch。执行 admission 只接受 exact consumed dispatchProject、requester、permission、
action type/ref/digest、preview digest 或时间顺序任一漂移都拒绝。
`ready` plan 不创建冗余 Approval。若执行前当前 Policy 已从 allow 变为
`require_approval`,旧 ready plan 必须停止并重新产生审批,不能带一个无关 dispatch
穿透。
### 6. 执行开始前重新验证当前 Policy
admission 以 active Principal 对 `tool.call:{name}` 和全部 required permission 再次
调用既有 `ToolPolicyAuthorizer`
- 任一 deny 立即拒绝;
- unavailable、畸形 decision、空 fence 或 mixed fence 失败关闭;
- Principal subject 必须等于 plan requester
- Profile 和 current binding digest 必须精确匹配。
审批完成不冻结未来权限;Role revoke、Project archive 或 binding/snapshot 切换都必须在
外部副作用开始前生效。
### 7. StepRun、Trace、Audit 是 admission 的强制前置证据
`qinglong/trusted-tool-execution-admission@v1` 必须绑定:
- 已耐久化 StepRun 的 id/version/digest
- 已耐久化 Trace 的 trace/span/digest
- 已耐久化 Audit event id/digest
- current Policy fence
- plan/action/binding/adapter/Profile/timeout
- Approved Action dispatch id/digest(需要审批时);
- admission time 与 domain-separated admission digest。
admission 不携带 input、handler 或 execute seam。当前 ADR 只冻结并验证 evidence receipt
形状;它不假装已有 StepRun/Trace/Audit repository。ADR-0001 的 `step_run_id` nullable
占位列和普通 RunEvent 不能冒充三种耐久事实。在这些 repository 与同事务 start barrier
完成前,生产 composition 不得构造 evidence,也不得调用 adapter。
### 8. Profile 与资源边界
- edge/standalone:按 Tool 调用构造 snapshot binding registry 和 plan;禁用或空闲时零
timer、watcher、socket、数据库连接;
- cluster-control:只读取已通过 current-vector proof 的 snapshotbinding 来自受审
control composition;不得取得 package-executor 的 snapshot 写权;
- worker:不建立 Project binding registry,只接受控制面已经绑定且由 Worker 能力再次
验证的未来 execution spec
- 任一 Profile 均不得通过 Package JSON、环境变量中的 module path 或运行期 register
绕过 binding ceremony。
## 被否决方案
1. **按 Tool name 直接 Map 到函数**:没有 snapshot/definition/version fence,拒绝。
2. **把 handler/module/URL 放进 Package Tool JSON**:把内容安装升级为控制面 authority
注入,拒绝。
3. **把 ADR-0133 action digest 直接写入 Approval**:没有绑定 snapshot 和 adapter
拒绝。
4. **保存任意 preview JSON**:无法证明大小、redaction 和 canonical identity,拒绝。
5. **审批后不复验 Policy**Role revoke 和 Project archive 无法在副作用前生效,拒绝。
6. **用 RunEvent payload 代替 StepRun/Trace/Audit**:缺少独立状态、版本和 start
transaction proof,拒绝。
7. **为了动态 registry 建 watcher/cache**:破坏 edge 空载预算且产生 stale head,拒绝。
8. **为 binding 新增 workspace package**:没有独立部署或发布生命周期,继续放在
runtime-core 显式 subpath。
## 实现与验证
当前切片已实现:
- `@qinglong/runtime-core/trusted-tool-invocation` root/subpath
- snapshot-specific immutable binding 与 exact registry
- execution class/Profile/authority/timeout/redaction/audit 规范化;
- 分层 action/preview/plan digest
- `tool.invoke` Approval binding 与 exact dispatch 复验;
- 执行前 current Policy 聚合与 mixed-fence fail-closed
- StepRun/Trace/Audit evidence-bound admission
- 无 filesystem/process/network/timer/execute authority 的源码门禁。
定向测试覆盖 12 项:
1. immutable binding 和无 executable code
2. unknown Tool、timeout 扩大、重复 binding、stale snapshot
3. Profile availability
4. canonical layered digest 与 adapter 漂移;
5. preview redaction 和 digest tamper
6. approval-only publisher
7. exact consumed dispatch
8. ready admission 与 fresh Policy
9. approval admission
10. deny、approval escalation、mixed fence、unavailable Policy
11. StepRun/Trace/Audit/Profile drift
12. root/subpath 与 authority import isolation。
## 后续门禁
1. ADR-0001 增量实现双方言 StepRun aggregate、状态机、索引和 Run/Attempt 外键;
2. 实现有界 Trace/Audit append-only repository 与 retention
3. 实现受授权的 encrypted/opaque Tool plan 与 preview Artifact repository
4. 把 plan/StepRun/Trace/Audit/Approved dispatch 在同一 start barrier 中提交或精确证明;
5. 为 built-in、isolated process、MCP、HTTP 和 Worker 分别建立 adapter composition 与
recovery/idempotency evidence
6. 接入本机/集群 application recovery/admission gate,并完成双方言、物理 edge、
PostgreSQL HA 与生产依赖闭包门禁;
7. 上述全部完成前,Tool execution 保持 production unreachable。