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

10 KiB
Raw Blame History

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. 审批后不复验 PolicyRole 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。