打开文档导航

Workspace lineage 超小单文档审查

基线状态:Historical fixed-target dossier。 本文按 b6458b41... 审查 workspace owner;本科生 guide 当前 target 为 94fab78...,当前导航见 implementation/source-map.md

范围、基线与状态

  • 状态:Current
  • 审查基线:agent-python-runtime 精确 commit b6458b41f0fa32085fdd4cad0a528d98602569d2、tree 5e8154fc26527181c66e612584d79dd4e620649d
  • 范围:runtime/workspace;以及直接使用其 ManagerRefRootBranchAttemptLease 的 streaming、subagent、wazero runner、cmd/apyrun binding 与对应 tests。
  • 排除:runtime/capability/workspace.go 的内存 typed-tool workspace;semantic、COW、benchmark、native 全链路;本单不把它们的旁证升级为 lineage 结论。
  • 证据规则:以下为源码与测试断言的静态观察;本次没有运行测试或 campaign。

核心对象与身份边界

  • 状态:Observed
  • runtime/workspace/manager.go:34-35Ref 是 Host-owned opaque identity,格式为 ws- 加随机 16-byte hex,既不含路径也不等于内容 digest。
  • Managermanager.go:80-101)只管理其私有 0700 base 下的 root;不扫描、收养或复用任意既有子目录。
  • Createmanager.go:104-170)从 InitialFile value-copy 建立可变 root;CreateFromDirectoryingress.go:15-76)只在 provisioning 时复制 trusted Host directory。
  • CapsuleInfo.WorkspaceSHA256 是普通文件树的可移植内容/清单身份;本地 Ref 被有意排除于 capsule 与 identity。
  • Rootbranch_root.go:21-34)是不可变 lineage record;Root.IdentityDocument(142-148)只含 schema、workspace digest、parent identity、depth、delta 计数,不含 ref
  • Root.IdentitySHA256 绑定上述 document;Root.Ref() 仍只是当前 Manager 的本地 materialization handle。
  • PortableIdentitybranch_root.go:38-59)对 mutable base 产生 depth 0 的 parent identity;对 sealed root 返回其 lineage identity 与 depth。

状态机:基本 workspace、Ref 与 Lease

  • 状态:Framing
  • Create/CreateFromDirectory/ImportCapsuleAbsent -> Mutable(Ref, owner="");失败由 deferred RemoveAll 回滚,不登记 entry。
  • Acquire(ref, owner)manager.go:194-226):Mutable -> Leased;要求 Ref、owner 合法、树重新扫描通过、未 immutable、无其他 owner。
  • Lease.FS() 只给 rooted WASI adapter;Snapshot 返回 bounded metadata,不返回 body;BindMountSource 只对 exact active lease 暴露 runtime-owned root。
  • Lease.NewTemporarymanager.go:382-418):Leased -> Leased + Temporary;Temporary 没有 Ref、没有 continuation identity,目录位于 Manager base。
  • Temporary.Close(438-457):关闭 rooted adapter、删除 scratch tree、从 lease 的 temporary 集合移除;重复 close 幂等。
  • Lease.Release(460-484):只有无 live Temporary 才能执行;关闭 FS、核对 (ref, owner),再将 entry owner 清空,Lease 进入 released 状态。
  • Manager.Destroy(250-271):仅能删除 inactive entry;Manager.Close(274-301)在任一 lease active 时返回 ErrWorkspaceBusy,否则删除所有 roots 并关闭 Manager。
  • Host cleanup 的可重试性由 cmd/apyrun/workspace_binding.go:196-213 保持:busy close 不清空 binding;成功才移除 Manager base。

状态机:Branch / Root / Seal / Discard

  • 状态:Current
  • ForkBranch(base, expectedBaseSHA256)branch_root.go:205-263):验证 base inactive 且当前 WorkspaceSHA256 等于 expected,然后完整复制 ordinary tree,建立私有 mutable child RefBranch
  • Branch 保存 base Ref、expected base digest、parent lineage/depth、base snapshot;它不携带 Guest、capability 或 Temporary 状态。
  • Branch 的 child Ref 可经 Acquire/Release 写入;兄弟 branch 之间不共享可变目录,base 也不被原地修改。
  • Branch.Seal(expectedBaseSHA256)(280-330):要求 Branch 未 terminal、base 与 child 均 inactive,并再次检查 base digest;然后比较 snapshots,生成 Root(depth 加一、changed entries/bytes)。
  • Seal 成功:child entry 标记 immutable、保存 rootRecord,Branch 进入 terminal;base 保持原 identity,未选 branch 不会自动合并。
  • Branch.Discard(180-202):仅对未 sealed、inactive child 生效,删除 child root 并 terminalize;sealed Branch 返回 ErrAttemptTerminal
  • Root 的本地 materialization 可由 Manager.Destroy 删除;Root value 与其 digest 仍可作为 portable record 留在调用者手中。
  • BindImportedRootbranch_root.go:61-100)先验证 Root identity document digest,再核对 imported tree digest;成功后把新本地 Ref 标为 immutable。
  • SelectRoot(103-139)只接受同一 expected parent 的已绑定候选,按 selected identity 选择一个,不 merge、不 mutation。

状态机:Attempt / Publish / Discard

  • 状态:Observed
  • ForkAttempt(base)attempt.go:22-70)也先要求 base inactive 并完整复制;返回独立 mutable child Ref 与 Attempt。
  • Attempt 写入通过同一个 exclusive Lease;base 的内容不随 attempt 写入改变。
  • Attempt.Publish()attempt.go:86-104)只在 child inactive 且重扫通过后将 Attempt handle 标为 terminal,并返回该 child Ref;它不写 Root、不设置 entry immutable、不修改 base。
  • 因而 Publish != Seal:Publish 是一次 caller-level terminal disposition,返回的 Ref 不是自动冻结的 portable Root;后续 Ref 生命周期仍由 Host/caller 管理。
  • Attempt.Discard()(106-128)要求未 terminal、inactive,删除 child root;发布或丢弃后再次操作返回 ErrAttemptTerminal
  • runtime/streaming/run.go:54-76 把 run error、close error、malformed/non-ok response 都导向 Attempt.Discard;仅 Guest response status=ok 且 runner 已关闭后调用 Publish。
  • runtime/subagent/orchestrator.go:289-344 则对每个 child Branch.Seal,再保留 selected Root、对未选 sealed roots 调 Manager.Destroy;错误路径 cancel 后 discard。

End-to-end 调用与数据流

  • 状态:Observed
  • cmd/apyrun/prepareMountedWorkspaceworkspace_binding.go:34-88):校验 config,创建私有 Manager,从 Host directory、capsule 或 empty tree 得到 Ref,再保存 initial CapsuleInfo
  • main.go:269-279 把 Manager/Ref/runIdentity 绑定到 wazero Factory;Factory 要求三者 all-or-none。
  • runtime/engine/wazero/engine.go:295-309 只在 request admission 通过后 Acquire,同一 Engine 只保留一个 workspace Lease;workspaceRun 串行化 mounted-workspace run。
  • moduleConfig(606-625)将 leased workspace 挂到 Guest workspace,将每次 NewTemporary 挂到 Guest tmp;Temporary 在 run/module 结束时关闭。
  • Runner close 先释放 Lease(engine.go:570-588);随后 main.go:328-347 Inspect final workspace、按 disposition 决定 discard 或 stage export。
  • stageExportworkspace_binding.go:135-168)写 0600 temporary capsule、hash、sync、close;publish(171-185)才 rename 到目标并尝试 sync parent directory。
  • main.go:396-410 在响应和 receipt 再验证成功后才 publish staged capsule;任一前置失败由 discard 删除临时文件,避免半成品目标。
  • imported capsule 首先得到新 mutable Ref;只有显式 BindImportedRoot 才获得 immutable Root binding,避免把 capsule receipt 当作 lineage authority。

Copy、identity 与 Host/Guest 边界

  • 状态:Framing
  • Host-owned:Manager、物理 root、Ref、owner token、limits、Root record、capsule 文件路径、stage/publish/discard 决策。
  • Guest-owned:通过 rooted FS 可见的普通文件/目录与 Guest 内部对其的读写结果;Guest 不接触 Host path、device、inode、symlink 或 hard-link identity。
  • Shared-but-bounded:Snapshot/CapsuleInfo/WorkspaceReceipt/Root.IdentityDocument 只跨边界传递 digest、路径名、类型、大小、权限与 lineage counters。
  • Private:LeaserootedFS、Temporary、runner、interpreter/capability state;capsule 明确不含 Lease、Temporary、interpreter 或 capability。
  • cleanGuestPathscanOrdinaryTreecopySourceDirectoryrootedFS 共同拒绝 traversal、absolute path、反斜杠、.git、symlink、special entry、cross-device tree、hard-linked file。
  • rooted FS mask stat identity(fs.go:211-216),以 synthetic inode 代替 Host inode;quota 在 open/write/truncate/rename/unlink/rmdir 边界重新计账。
  • CaptureFilecapture.go:11-60)只读已 sealed immutable root 的 bounded regular file,并返回 defensive byte copy;它不是任意 Host 文件读取接口。

测试与机器可读证据

  • 状态:Observed
  • workspace 单测覆盖:manager_test.go 的 lease continuation、Attempt publish/discard、path/link/quota、Temporary cleanup、directory ingress 与 snapshot;branch_root_test.go 覆盖 seal recursion、parent conflict、portable identity、root selection。
  • capsule 单测覆盖:capsule_test.go 的 deterministic bytes、round-trip、tamper/trailing/manifest/quota rejection;capture_test.go 覆盖 immutable bounded capture;ingress_unix_test.go 覆盖 FIFO atomic rejection。
  • 直接 caller 单测覆盖:streaming/run_test.go 只发布成功 attempt;subagent/orchestrator_test.go 覆盖 parallel children、selected root、sibling discard、abort cascade;cmd/apyrun/workspace_binding_test.go 覆盖 stage-before-publish、policy、invalid capsule、busy close retry。
  • 机器可读字段锚点:Root.IdentityDocumentCapsuleInfo{WorkspaceSHA256,TreeSHA256,EntryCount,TotalBytes}WorkspaceReceipt{InitialWorkspaceSHA256,FinalWorkspaceSHA256,CapsuleSHA256,Disposition}JoinResult{SelectedRoot,DiscardedRefs}
  • 状态:Deferred;上述 tests 是可审计的源码证据,不在本只读单中宣称已执行、通过或覆盖任意 Python/Host 环境。

Trade-offs 与不主张

  • 状态:Framing
  • Trade-off 1:完整物理 copy 换取 branch/attempt 的简单隔离、可重扫与 deterministic capsule;代价是 provisioning 与 materialization 成本随树大小增长,未采用 COW 作为本单依据。
  • Trade-off 2:opaque local Ref 与 digest-only Root 分离本地路径和可移植 lineage;代价是跨 Manager 必须 Import 后 Bind,并由 SelectRoot 显式选择,不能靠 Ref 相等判断同一 lineage。
  • Trade-off 3:exclusive Lease 加 per-instance Temporary 换取 owner/cleanup 清晰与 scratch 不回流;代价是同一 mounted workspace 的写入被串行化,Temporary 未关闭会阻断 Release/Manager.Close。
  • Trade-off 4:stage-then-rename 换取目标文件不被半成品覆盖;代价是需要 caller 处理 temporary cleanup、directory sync 与 publish error。
  • 状态:Unsupported;Attempt.Publish 不建立 immutable Root、不证明后续 Ref 不再写入,也不把 child 内容 merge 回 base。
  • 状态:Unsupported;Snapshot、CapsuleInfo、receipt、digest 是 bounded evidence/identity,不是外部世界真相、权限授权或完整 interpreter state。
  • 状态:Unsupported;本单不推导 COW、semantic pre-dispatch、native backend、benchmark latency 或任意 Guest Python 支持范围。

答辩一句话

  • 状态:Current
  • Pysolate 以 Host-owned opaque Ref 管理可变物理副本,以 exclusive Lease 限定唯一写者,以 Seal/Root 或显式 Attempt Publish 区分可移植 lineage 与 caller disposition,并在失败、未达成或未选路径上 fail-closed、discard 或 orphan,而不把 Host 路径和后续 authority 带入 Guest。