代际与 fail-closed

定义

代际(generation)是 Box 与 A3S OCI Runtime 共用的生命周期单位:同一次产品对象(一个 box、一个 sandbox)的每一次创建或重启,对应恰好一代运行时状态。一代拥有不可变的创建身份、附件摘要、端点与路由。崩溃恢复、日志投影、exec 句柄与清理都绑定到这一代,而不是绑定到「名字叫 web 的那个东西」。

fail-closed(失败即关闭)是决策规则:当实现无法证明「继续是安全且符合已记录意图的」时,必须返回错误并停止副作用。禁止的行为包括:改走另一后端、合成退出码、把 tmpfs 密钥降级到磁盘、在摘要漂移后复用套接字。

问题背景

本地运行时要处理的典型故障是:进程被 SIGKILL、主机重启、套接字残留、镜像标签被移动、半写入的目录。若恢复逻辑采用「尽量猜」,则可能启动第二个实例、删除别人的根目录,或把已停止的一代标成仍在运行。代际把「这是哪一次启动」变成显式数据;fail-closed 把「猜」从实现策略中排除。

机制说明

代际约束了哪些动作

  • 路由:创建时写入 ManagedRuntimeRoute,恢复时不得按新的 A3S_BOX_OCI_MIGRATION 改道。
  • 所有者复用:PID、端点、路径、SHA-256 必须同时匹配,否则启动新的所有者,并把旧代际标为已停止。
  • 操作身份:文件写入、资源更新、快照 thaw 使用可重放的操作 ID。已完成的操作在崩溃后不得再执行一次。
  • 日志与 attach:投影 worker 在 OCI init 之前就绪,按有序游标写入;删除运行时代际前必须排空。attach 不回退到旧的 runtime socket。
  • 退出状态:无法取得真实退出码时,拒绝伪造。对账可以将一代标为已停止,但不能发明一个 0 或 137。

Native Live 观察门证明:在保留 Box manager 的前提下,宿主所有者被 SIGKILL 后,流式 exec 句柄与文件系统连续性仍可维持。这与「仅停止对账」是两种都成立、但不可混淆的性质。主机报告保持 b2_process_session_recovery_closed=false,表示该门尚未被宣称为关闭。

fail-closed 的具体例子

情境 关闭行为 被禁止的开放行为
Sandbox 所有者未就绪且策略为显式 sandbox 预检失败 改走 MicroVM
OCI 路由上的 cp / attach 失败 向调用方报错 重试 Box 拥有的 socket
密钥 tmpfs 不可用 启动前失败 把密钥写到磁盘
客户机文件名无法在 macOS 目录传输中无损表示 失败 改名后继续
记录缺少托管元数据 Internal 错误 按当前默认隔离猜测

开放(fail-open)在界面上更「好用」,但会破坏取证与多租户假设。Box 的文档反复出现「从不回退」,指的就是这一规则,而不是指程序不得重试同一操作身份。

在 Box 中的位置

  • 记录与路由:src/runtime/src/box_record.rslocal_execution/router.rsrecovery.rs
  • 操作身份与资源更新:local_execution/resources.rs,以及 core 中的 OperationId
  • 日志投影:local_execution/oci_log_projection.rsshim/src/managed_oci_log_worker.rs
  • 证据语言:README 的 Sandbox GA 小节、docs/sandbox-ga-evidence.md

验证命令

无单一命令能演示全部规则。较接近的观察是:启动一个 Sandbox box,记录其运行时身份,然后仅杀死 OCI 所有者进程,再执行 inspect 或下一次普通操作。预期是新的所有者与新的代际,而不是把旧套接字上的残留当作同一代继续使用。

相关与易混

  • 相关:ExecutionManager、Backend 与 RouterSandbox(共享内核)
  • 易混:代际不是镜像标签,也不是 Kubernetes 的 ReplicaSet generation。它标识一次本地执行。fail-closed 也不是「禁止重试」:对同一操作 ID 的显式重试是允许的,前提是实现能证明该操作至多生效一次。