8.4 KiB
ADR-0162:首个 Trusted Built-in Run Read Tool Adapter
- 状态:Accepted
- 日期:2026-07-26
- 关联:ADR-0133、ADR-0154 至 ADR-0161;RFC D-131/D-149/D-150/D-151
背景
ADR-0158 已要求 Tool adapter 只能在 durable start barrier 提交后启动;ADR-0159 至 ADR-0161 又把 input/preview 变成不可变 Artifact,并把每个 start 与 exact Artifact pair 以双方言关系事实绑定。但此前仍没有任何可执行 adapter:
- binding registry 只保存受审元数据,没有函数或
executeseam; - Artifact 可以被领域函数解封,却没有一个 start-first 的产品无关执行门;
- current Project snapshot、binding、Artifact、key 和 output schema 还可能由调用方以 多套对象分别注入;
- response-loss 后没有 adapter-specific 证据说明能否安全重试。
首个实现必须验证整条安全链,同时不能为了一个内置只读能力增加 workspace package、 数据库表、连接、timer 或低配设备常驻成本。
决策
1. 执行 authority 只从两个现有 package subpath 暴露
在 @qinglong/runtime-core 内新增:
/trusted-tool-execution:durable-start 后的通用执行门和 executable adapter registry;/builtin-run-read-tool:首个qinglong.run.get@1.0.0adapter。
它们不从 @qinglong/runtime-core root 聚合导出,不新增 workspace package、第三方
依赖、bin、进程、socket、连接池、timer、watcher 或缓存。Package 资源、Agent 输入、
HTTP/MCP caller 和 plan 均不能运行期注册 executable adapter。
TrustedToolExecutionAdapterRegistry 由受信 Profile composition 一次性构造。每个
adapter 必须携带 exact Project/snapshot-specific binding 和 Profile;registry 在构造
时复验 current binding、Definition effect、builtin_in_process execution class,并
冻结 binding/Profile/recovery mode 与已绑定的 execute seam。执行函数不再额外接受一套
Definition/binding registry,避免两个信任源。
2. 顺序固定为 barrier → current binding → Artifact → key → execute
executeTrustedToolAfterStart(startId, dependencies) 固定执行:
- 从
ToolExecutionStartBarrierRepository.findByStartId读取并规范化 durable barrier;缺失、损坏或 identity 漂移失败关闭; - 以 barrier 的
bindingDigest在 executable registry 持有的 current Project binding registry 中反查 exact Tool,再复验 Project、snapshot、Definition、 adapter/redaction/audit contract、Profile、execution class 与 timeout; - 读取 exact input Artifact,复验 reference、Project、action、requester、Tool、 seal time 和由当前 Definition 重新计算的 invocation action digest;
- 按 Artifact
keyId解析一次 owned 32-byte key,AES-256-GCM 解封,并再次通过同一 current Tool Definition Registry; - 调用 frozen adapter seam,并使用同一 Registry 对 output 做 schema 与 256 KiB 总上限规范化;
- 返回 domain-separated output/result digest 绑定的低敏内存结果。
key provider 返回的 owned key 在成功和失败路径都会覆零;Artifact 解封函数继续覆零 内部 key、nonce、ciphertext、auth tag、AAD 和 plaintext byte buffer。JavaScript 已经解析出的字符串/对象无法提供物理内存擦除保证,因此 adapter 不得不必要地保留 input 引用,后续 Secret-bearing adapter 仍需独立内存与进程隔离评审。
deadline 在解封前检查,并用一次只在当前调用存续期间存在的 timer race 约束 caller; adapter 返回后再复验时钟。race 超时不被宣传为已取消底层 I/O;首个 adapter 只有一次只读点查且无副作用,迟到结果会被丢弃。未来 process/MCP/HTTP/write adapter 必须提供真正的 cancellation/fencing contract,不能照搬本实现。
3. qinglong.run.get 是严格只读、Project-scoped 的首个 adapter
受审 Definition 固定:
- Tool:
qinglong.run.get@1.0.0; - effect/risk:
read/low; - required permission:
run.read; - input:仅
{runId},最长 128; - timeout:5 秒;
- adapter:
builtin.qinglong.run-get@1.0.0; - authority:仅
database.read; - Profile:由 binding 显式选择,首版可用于 edge、standalone 和 cluster-control。
该 Definition 仍必须作为 qinglong Package namespace 的普通 Definition 进入 current
Project snapshot;平台代码不会把它秘密插入 registry。binding factory 只接受与受审
Definition 完全一致的 snapshot entry。
adapter 只取得 RunRepositoryReader.findRunById,不取得 transaction、mutation、
filesystem、network、process、Secret 或 Artifact authority。返回值只包含:
- found;
- Run/Task revision identity;
- status/version/event sequence/priority;
- execution origin/owner;
- create/queue/start/finish 时间。
request ID、trigger identity、input/output ref、executor handle、PID、lease、error 和
其他敏感或可扩权字段不得进入 output。missing 与 cross-Project Run 都返回相同的
{found:false}。
4. 首个 post-start recovery 只证明“只读可安全重试”
inspectTrustedToolExecutionRecovery 只读取 durable barrier 并复验 exact executable
binding。对 retry_safe_read adapter 返回
qinglong/trusted-tool-execution-recovery-evidence@v1,其中 disposition 为
retry_safe、reason 为 read_only_no_side_effects,并绑定 barrier/adapter digest
与 inspection time。
inspection 不读取 input Artifact、不解析 key、不解密、不调用 adapter,也不声称已有 exact output。重试可能观察到更新后的 Run 状态,但不会重复外部副作用。结果 Artifact/receipt 的持久化、exact response replay 和 StepRun completion 同事务仍是后续 门禁。
低配与集群影响
- workspace importer 仍为 21,不新增依赖;
- Edge/Standalone 空闲时零新增资源;每次调用只多一次 barrier 点查、一次 Artifact 点查、一次 key resolve、一次 Run 点查、一个调用期 deadline timer 和有界 AES/JSON 工作;
- Cluster 复用现有 PostgreSQL repositories,不增加表、role、Pool、队列或服务;
- adapter registry 是请求/组合根显式构造的最多 128 项小集合,不是跨 Project 全局 cache,也不建立 watcher;
- recovery inspection 不触碰密文和 Run repository。
被否决方案
- 在 plan/admission 中保存函数或 module path:绕过受信 composition 和供应链 审查。
- 执行函数同时接受 binding registry 与 executable registry:形成可漂移的双信任 源。
- 没有 barrier 时直接读取 Artifact 执行:允许越过同事务 start gate。
- 只比 Artifact ID:不能证明 digest、Project、action、requester、Tool 和当前 Definition。
- 把内置 Definition 隐式插入每个 Project registry:破坏 Project snapshot 的 完整 source vector。
- 为首个 adapter 新建 package:没有独立部署、依赖或权限生命周期。
- 用 Promise race 宣称可取消任意 adapter:底层 I/O 仍可能继续,不能作为副作用 fence。
- 把只读 recovery evidence 当完成 receipt:无法重放 exact output。
验证
- runtime-core type/build gate 通过;
- runtime-core 全量 319/319,新增 6 项覆盖:
- durable start 后才解封并执行真实 Run repository 点读;
- missing barrier 和错误 key 在 adapter 前失败关闭,owned key 覆零;
- stalled read 对 caller 有 deadline 上限且 owned key 仍覆零;
- missing/cross-Project 输出不可区分且不泄露低敏投影外字段;
- recovery inspection 不读取 Artifact/key、不调用 adapter;
- Definition/binding 漂移拒绝,root 不导出 execution authority;
- source gate 证明通用执行门不导入 filesystem、process、network、worker thread;
- package 数和依赖树不变。
后续门禁
- 在 edge/standalone 与 cluster-control 产品 composition 中请求驱动装配
qinglongbuilt-in Definition、binding、repository、key provider 和 start→execute 调用链; - 新增 immutable result Artifact/receipt,并与 StepRun terminal transition、 RunEvent、Trace/Audit completion 在双方言中原子绑定;
- 为 key lost、Artifact/关系损坏和 result ambiguity 提供 inspect/manual recovery;
- 增加 crash/response-loss 的 SQLite、PostgreSQL 真库和 physical HA 故障注入;
- 完成物理 Edge idle/fault/scale 证据后,才允许产品 execution admission 开放。