跳到主要内容
← 返回全部文章

工程博客

我们怎样构建交付证据链

AI Agent 十分钟就能写出一个应用,证明真正上线的究竟是哪一份代码却难得多。这篇拆开 Appaloft 的交付证据链,也说明它刻意不证明什么。

Appaloft
部署控制Runtime 验证AIAgent

AI Agent 十分钟就能写出一个应用,证明真正上线的究竟是哪一份代码却难得多。这篇拆开 Appaloft 的交付证据链,也说明它刻意不证明什么。

用 coding agent 做项目的人,迟早会撞上同一堵墙:Agent 交出一份在本机能跑的结果;接着通常是你把文件复制到服务器、重启进程、瞄一眼日志,然后宣布部署成功。两周后,production 在凌晨两点跑着错误版本,没人答得出软件交付里最朴素的问题:现在运行的到底是哪一份代码,又是谁批准的?

这不是 AI 才有的问题,但 Agent 把它放大了:

  1. 吞吐量。 人通常一次写一个可部署变更,也大致记得里面有什么;Agent 一下午就能产出二十个 candidate,人的部署记忆跟不上。
  2. 信任。 如果直接把 SSH key、Docker socket 或 cloud credential 交给 Agent,就等于把 production 权限交给一个语气笃定、结果却有随机性的进程。

Appaloft 是 Apache-2.0 的开源交付平台。我们坚持一条原则:Promotion 必须显式发生,Delivery 必须留下证据。 不是“Agent 说已经部署”,而是把获批 artifact 与实际承载流量的 workload 连起来,并在每个环节记录可验证观察。

我们把它称为 Delivery Evidence Chain,交付证据链。下面讲的是它怎样运转,以及我们刻意不做哪些承诺。

证据链能证明什么

先讲免责声明,因为这是设计原则,不是法律附注:证据链不是形式化验证,不是正确性证明,也不是安全认证。它是一条 chain of custody,回答三个问题:

  • 哪一组精确 bytes 被冻结成 candidate?
  • 谁、以哪一种 principal 身份、在什么时候批准了 promotion?
  • 部署以后,观察到的现实——运行容器、配置、承载流量的 route——是否与批准内容一致?

其中任何一环断掉,系统都要给出明确 reason code。缺失或不可用的 evidence 不能静默通过检查;fail closed 正是这套设计的重点。

证据链有四环:freeze → preview → promote → verify

第一环:把 Workspace 冻结为内容寻址 Artifact

Coding agent 在隔离 Sandbox 中工作。结果准备好以后,Workspace 被冻结为 Source Artifact。

最直觉的实现是“把目录打成 tar,再算 tarball hash”。我们没有这样做。Artifact digest 是一份 canonical JSON manifest 的 SHA-256;manifest 是按路径排序、去重后的 {path, sha256-of-file-bytes, sizeBytes} 列表。无压缩 ZIP 只是传输格式,identity 属于 manifest。

Manifest-of-hashes 带来几项实际好处:

  • 有意义的去重。 findArtifactByDigest 能在 Agent 输出与历史版本逐 byte 相同时返回已有 artifact。两个 freeze 并发时,输掉竞争的一方删除自己的副本,调用者仍拿到赢家的 descriptor。
  • 发现中途修改。 Capture 读取文件后会重新检查大小;Workspace 在 freeze 期间变化,capture 立即失败,并报告 Source Artifact changed during capture
  • 路径安全。 每个 entry 都要相对 source root 验证;symlink 与 .. escape 直接拒绝。

还有两个决定值得单独说明。

Freeze 要求 Workspace 静止。 Sandbox 中仍有 Agent Run 活跃时不能 capture。把规则放进状态机,而不只写进文档,才算真正执行了这条边界。

Secret scanning 在 freeze 时 fail closed。 内容变成 immutable 之前,系统会检查 filename denylist(例如除文档模板外的 .env*id_rsa*credentials.json)和内容 pattern(PEM private key、AWS/GitHub/OpenAI token 形态、password = … 一类赋值)。疑似 secret 会让 freeze 失败,因为后续 Preview、Promotion 与 Deployment 都会按 identity 信任这份 artifact。

第二环:精确 Preview 被冻结的内容

Frozen artifact 可以产生 Candidate Preview:一个临时、会过期、只服务该 build 的 URL。Preview response 带有 x-appaloft-artifact-digest;control plane 还会确认 gateway 返回的 previewIdartifactDigest 和预期一致。无法说明自己在服务哪一份 artifact 的 preview 属于 unverified evidence,并阻止 promotion。

Preview token 使用 HMAC hash、timingSafeEqual 比较、可 revoke,而且只允许 GET/HEAD。我们刻意压小 TCB:preview gateway 是职责单一的服务,不是一套大型 framework。

第三环:把 Promotion 做成状态机

不少工具把 approval 当成 Dashboard 上的一个按钮。Appaloft 的 approval gate 是 domain layer 中的 principal check

Promotion aggregate 使用显式状态机:

planned → accepted → creating-resource → deploying → verifying → completed
   │                                                            │
   ├── expired (30-min TTL)                        failed ──→ retry ──→ deploying
   └── (no approval, nothing happens — ever)

这样建模会自然得到几条约束:

Plan 30 分钟后过期。 Plan 绑定 digest 与 verified preview;过期后返回 sandbox_promotion_plan_expired。Approval 应该依据新鲜 evidence,而不是昨天的 candidate。

Accept 必须带上精确 digest。 acceptPromotion 接收 expectedArtifactDigest。查看以后 candidate 若发生 drift,调用会得到 sandbox_promotion_artifact_mismatch。批准的是“这组 bytes”,不是“最新版本”。

Gate 拒绝 machine principal。 requireExternalApprovalActor 会拒绝 deploy-token identity,并返回 sandbox_agent_external_approval_required。Agent 或 MCP session 持有的 sandbox-scoped identity 在结构上不能 accept promotion。Agent 可以准备 gate 前的全部工作,但必须由人或持有人类委托 credential 的外部系统跨过它。

Accept 与 retry 都具备幂等性。 同一 idempotency key 再次 accept 只会重新 enqueue,不会重复 promotion。Retry 也有意保持狭窄:复用同一 resource 和 artifact,只清除 deployment ID 后重新尝试。已经绑定 resource 的 promotion 不能被 recordResource 悄悄改接到另一个 resource。

每个中间状态都会持久化。 reconcilePromotion 由 database-backed durable work queue 驱动,并在 resource created、deployment created、proof read 等阶段后分别保存状态。进程可以在任何位置崩溃,再从最后一份诚实状态继续。

第四环:Proof 比 exit code 更有用

exit 0 只能证明 deploy script 结束了;绿色 health check 只能证明某个 container 健康。两者都不能证明承载流量的就是获批版本。因此最后一环是 read-back verification engine,对外暴露为 deployments.proof

Proof 在读取时计算:把 admission 时冻结的 planned state——artifact identity、configuration fingerprint、expected effects——与目标环境的 observed evidence 对比:

  • Workload identity。 通过 SSH 在目标机上 docker inspect 带有当前 deployment ID label 的 container,再比较 image sha256: digest 与 generation。
  • Configuration fingerprint。 比较 label 与 environment key set;发生 drift 的 env var 是 mismatch,不是脚注。
  • Health。 同时观察 container running state 与 Docker health status。
  • Route identity。 正确 label 的健康 container 并不能证明 reverse proxy 真在服务它。Managed edge 因此会在 response 上盖 deployment identity header;verification 读取 public URL,并确认 identity 与 planned deployment ID 一致。Managed public route served deployment X instead of Y 是一等失败,而不是模糊告警。

Verdict vocabulary 有意保持诚实:verified | partially-verified | unverified | stale | failed。不可用 evidence 会显式出现,不能满足 required gate;health success 或 access success 单独都不能产生 verified。Mismatch 带 machine-readable reason code,例如 artifact_identity_mismatchconfiguration_fingerprint_mismatchaccess_route_workload_mismatch,并附上建议 remediation operation。

只有 verified proof 才能让 promotion 进入 completed。Promotion descriptor 的 proofVerdict 直接从该状态推导,不存在 unverified deployment 被读成成功 promotion 的路径。

底下那本账:Audit Event 与 Operator Work

证据链下面是一套通用 audit sink。Project、Deployment、Sandbox、Credential 等 audited domain 的 mutation 会记录 operation key(例如 sandboxes.promotions.accept)、涉及的 aggregate 与经过 redaction 的 payload。命中 secret-like key pattern 的内容会在持久化前剥离。Retention、prune、带独立 content digest 的 immutable archive 与 legal hold 是另外几套朴素、独立的子系统。

appaloft audit-event listappaloft work list 是 operator-facing read path。凌晨两点出问题时,“发生了什么、顺序如何、由谁触发”应该是一条 query,而不是考古项目。

当前能力边界

这部分按 2026-08-02 的公开 capability maturity 更新:

  • Deploy & Verify 已可用。 Folder、Git repository、ZIP、image、Compose bundle、static artifact、health、logs、rollback 与 proof readback 属于当前交付路径。
  • Agent Workspace 是 Public Alpha。 托管 Execution Sandbox、Pi Runtime / Run、Source Artifact、Candidate Preview 与 Sandbox Promotion 仍处于 Private Preview;dynamic application runtime preview 仍是 Planned。
  • 廉价 VPS 不一定能承载 Sandbox。 gVisor isolation 需要 Docker 注册 runsc;provider probe 缺失时必须 fail closed。
  • 证据链不是正确性论证。 它不判断 Agent 写的代码好不好,只说明获批代码是否按可观察 evidence 运行在目标位置。

为什么坚持这条路

“一键 AI deploy”可以更早做出来,常见实现也很相似:Agent 拿着过多 credential,deploy script 承担过多信任,success message 却留下太少 evidence。

我们更愿意押注另一件事:Agent 时代需要的是更少的隐式信任。Agent 应通过受治理的 operation 工作——与人使用同一份 operation catalog,通过 CLI、MCP、SDK、OpenAPI 或 GitHub Action 进入;capability boundary 由 principal type 执行,artifact identity 由 content digest 执行,成功则由 observed reality 是否匹配 approval 决定。

如果你认同这条路,可以查看 Apache-2.0 的 Appaloft repository交付证据文档。如果你认为 approval gate 最终会在 Agent autonomy 下消失,或者 content-addressed promotion 只有仪式感,也欢迎直接在 issue 里讨论。

Appaloft 是开源 AI Application Delivery Platform:让 Agent 在隔离 Workspace 中工作,冻结精确 candidate,显式 promotion,把应用部署到你控制的基础设施,并通过 evidence 验证结果。