Claudian Collab 私有云 Bootstrap:八阶段绑定状态机、持久化 Host 围栏与失败即恢复的设计解析
【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian
本文以 Claudian(一个把 Claude Code/Codex 嵌入 Obsidian 知识库的插件)Collab 协作子系统中的私有云 Bootstrap 模块为核心,完整解析 bootstrap 模块规约 定义的职责边界、八阶段绑定生命周期、持久化转换记录的存储与单调性保证、former-Host 停止围栏、双成员就绪报告以及激活后“只前进不回头”的恢复语义。读完后你可以理解该模块如何把一个局域网(LAN)权威项目安全切换到云权威:哪些状态必须落盘、哪些操作失败即封禁项目、恢复路径为什么保证不会回滚 LAN 权威。
模块定位:私有云 Bootstrap 只做什么、绝不做什么
bootstrap 模块的第一条规约划清了职责边界:该作用域只拥有以下能力——
- 保留的私有双客户端 readiness/report 固定夹具(fixture);
- former-Host(源主机)停止围栏(stop fence);
- 开发环境的激活请求与重放(development activation request/replay);
- 客户端本地的绑定转换(client-local binding transition)。
而生产权威转移、语义检查点捕获/导入、转移声明(claims)、源端放弃(source relinquishment)、目标代际(target generation)与终态响应器,全部属于 authority-transfer 模块,并且“绝不允许调用或包装本模块”。这是一个严格的单向依赖约束:生产路径永远不依赖 bootstrap 的私有能力,bootstrap 也不会反过来侵入生产权威转移。
从源码结构看,这个边界体现在模块内只有“development bootstrap”命名空间的实现:CloudBootstrapCoordinator.ts 面向的DevelopmentBootstrapCloudPort接口方法(activate/begin/cancel/get/report/upload)全部对应@claudian-collab/protocol协议包中的DevelopmentBootstrap*请求类型,每个响应都经过对应 operation 的 codec 解码(decodeStatus),防止服务端返回结构污染本地状态机。
转换记录:唯一的阶段权威
规约指出“一条转换记录是唯一的阶段权威”。在 CloudBootstrapTransitionRecord.ts 中,这个“唯一权威”被实现为一张严格解码的 JSON 记录,当前 schema 版本为CLOUD_BOOTSTRAP_TRANSITION_SCHEMA_VERSION = 2(第 31 行)。
八阶段生命周期
阶段集合是冻结常量(第 33–42 行),与规约逐字一致:
export const CLOUD_BOOTSTRAP_TRANSITION_PHASES = Object.freeze([ 'intent', 'readiness-confirmed', 'origin-rotated', 'cloud-verified', 'membership-replaced', 'index-repaired', 'lan-authority-retired', 'fence-terminal', ] as const);推进阶段只有一个入口advanceCloudBootstrapTransitionPhase(第 582–612 行),它强制三条规则:
- attempt 必须已激活:
current.attemptState !== 'activated'时直接抛错,即本地绑定阶段只属于“云端已激活”之后; - 严格单步前进:
nextIndex !== currentIndex + 1即拒绝,不存在跳阶段或回退; - 终态围栏前置条件:进入
fence-terminal要求 fence 处于host-stopped或not-applicable,否则抛错。
CloudBootstrapBindingFinalizer中的NEXT_PHASE映射表(CloudBootstrapBindingFinalizer.ts 第 32–40 行)与上述阶段序列一一对应,finalize主循环在每一轮先verifyActivation复核激活结果,再执行当前阶段对应的本地副作用,最后才推进阶段并持久化——这是规约中“激活后的恢复只把绑定阶段向前驱动”的具体实现。
记录的字段与不变量
CloudBootstrapTransitionRecord接口(第 70–105 行)携带:
attemptId/attemptState(pending|activated|cancelled)与activationResult(含activationOperationId、placementGeneration、activatedAt);manifest与manifestSha256(对 manifest 的规范化 JSON 做 SHA-256,见第 221–227 行);oldAuthority(旧 LAN 端点、Git URL、CA 指纹、源 Host 成员 ID)与newAuthority(云端serverUrl、gitRemoteUrl、绑定/线协议版本);repositoryIdentity(mainOid、personalRefOid、objectFormat为sha1/sha256);fence(Host 围栏,见下节)、terminalCleanupCompleted、ownerInstallationKey(v2 新增,标记恢复所有权)。
解码函数decodeCloudBootstrapTransitionRecord(第 291 行起)执行一组“不可能状态”校验,其中值得注意的有:
developmentActorId必须等于memberId——这是规约“actor-bound report”(报告必须绑定发起成员)在记录层的强制;manifestSha256必须与重新计算值一致,且oldAuthority的 CA 指纹、repositoryIdentity的各 OID 必须与 manifest 完全吻合;cancelled状态只允许 fence 为released-before-activation或not-applicable;fence-terminal阶段与fence.state === 'active'互斥;terminalCleanupCompleted为真时 attempt 不能仍处于pending。
持久化存储:目录 fsync、单调性检查与损坏即封禁
CloudBootstrapTransitionStore.ts 实现了规约中“转换目录及其父目录条目持久同步后才算返回”的全部细节:
- 路径与限额:活动记录位于
.claudian/collab/cloud-bootstrap-transitions/{projectId}.json,已取消的历史归档在.claudian/collab/cloud-bootstrap-transition-history/{projectId}/{attemptId}.json;单条记录序列化后不得超过MAX_TRANSITION_BYTES = 128 * 1024(第 25–27 行)。 - 独占创建 + 原子写:
create使用createCollabFileExclusively(文件模式0o600,目录0o700),save使用writeCollabFileAtomically,写后调用syncTransitionStorage()——它连续对转换目录与.claudian/collab父目录做syncCollabVaultDirectoryDurably(第 303–306 行)。任何一层目录 fsync 失败都会以operation-failed错误向上抛,即规约所说的“directory-sync failure fails closed”。 - 单调性守卫:
assertMonotonic(第 91–118 行)在每次save前校验:不可变身份字段(immutableIdentity,含 manifest、双端 authority、仓库身份等)不得变化;阶段索引不得倒退;attempt 状态只允许pending → activated/cancelled;fence 状态只允许active → host-stopped/released-before-activation与host-stopped → released-before-activation/terminal;updatedAt不得回退。 - 损坏即封禁:
list()(启动目录枚举)对超大或损坏的记录不抛错,而是把项目 ID 收进blockedProjectIds并置retryRequired;读失败同样封禁并触发重试。被blockedLifecycleProjectIds封禁的项目,inspectLifecycleOwner会返回nonterminal——即它作为“非终态生命周期所有者”阻止普通项目工作继续。这正是规约“不可读、超大或损坏的记录既挂起普通项目工作,也让 Host 启动以失败关闭”的落点。 - 取消归档:只有
cancelled且terminalCleanupCompleted的记录才能被archiveCancelled移入 history 目录,之后才允许用新的 pending 记录替换路径上的活动记录——对应规约“只有带该检查点的取消记录才可归档替换”。
Former-Host 停止围栏:失败关闭的双重挂起
规约要求“在源 Host 开始 bootstrap 之前,持久化 Host 围栏以及 token 挂起的项目准入和工作会话”。实现分布在两个文件:
CloudBootstrapLocalFence.ts 的closeAndDrain(第 61–76 行)按固定顺序执行:先suspendProjectAdmission挂起项目准入,再suspendProject挂起工作会话(挂起失败则回滚准入挂起),然后drainAdmittedOperations排空已准入操作,最后把项目标记为 quiesced。恢复路径resumeSuspendedProject(第 86–101 行)体现了规约中最微妙的一条补偿规则:先恢复工作会话,若随后准入恢复失败,则立即用一对新的挂起替换并保留,再抛出durable-progress-recovery-required(CLOUD_BOOTSTRAP_ADMISSION_RESUME_FAILED_REASON)——“重试永远不会使用陈旧 token”。
CloudBootstrapCoordinator.ts 的stopFormerHost(第 329–354 行)在closeAndDrain之后调用formerHost.stopAndDrain,并把返回证据逐项断言为autoStartDisabled === true && resourcesDrained === true && routeUnregistered === true,任何一项缺失都以cloud-bootstrap-host-stop-incomplete失败关闭;成功后markCloudBootstrapHostStopped把 fence 推进到host-stopped并持久化。
围栏对启动路径的约束是全局的。在组合根 ClaudianCollabService.ts(第 377 行附近)中,LanHostCoordinator的runWithProjectStartGuard被包进cloudBootstrapTransitions.runWithLanHostStartGuard。该守卫(Store 第 195–217 行)的语义是:只要存在 fence 为active/host-stopped/terminal且本安装是恢复所有者的记录,任何启动尝试(显式启动、恢复、项目创建、host-transfer 恢复)都会抛cloud-bootstrap-host-fence-active;而遗留的无主记录(ownerInstallationKey === undefined)则以cloud-bootstrap-legacy-owner-missing要求恢复。这就是规约“单一LanHostCoordinator.startProject权威把持久化转换守卫应用到每一个调用方”的实现证据。
另外两点与规约一致:工作会话挂起“自行完成 close 和 drain”,bootstrap 不得终态 drain 被挂起的项目;只有“持久化观察到的激活前取消”才允许恢复那对精确的挂起 token,且不会自动启动 LAN(见cancel后settleLocalTerminal中resumeAfterCancellation的调用链)。
双成员就绪报告:各自的 actor-bound 证据
规约规定“两个成员提交独立的 actor-bound 报告”,并给出上传黑名单:任何路径都不得上传 SQLite、凭据、CA 材料、项目目录归档、未发布文件、本地-only 提交或私有草稿。
Coordinator 侧的实现印证了这条黑名单的“正向白名单”设计:
collectReport(第 356–396 行)构造的DevelopmentBootstrapReport只包含clientReadiness(由CloudBootstrapReadinessCollector采集)、manifest 的comparison段、报告者成员 ID、观察到的个人 ref OID;源 Host 额外附加hostStopAttestation(fence ID、stoppedAt、manifestSha256 等停止证据),且只有 fence 为host-stopped时才允许签发——“源 Host 不能替另一个成员背书”。- 报告先经
decodeDevelopmentBootstrapReport解码,再以submitDevelopmentBootstrapReport提交;assertActorMatchesMember强制developmentActorId === memberId。 - 规约强调“quiesced 准入只证明停止了新的项目工作”,就绪检查仍必须巡检所有持久化生命周期所有者:
publication操作非空、Manager-responsibility 回执处于offered/acknowledged都视为非终态。Store 的inspectLifecycleOwner(第 180–193 行)正是这类巡检端口之一,把本地转换记录自身也归入“absent/nonterminal/terminal”三类判定。
源 Host 激活主流程:捕获、上传、激活与恢复
CloudBootstrapCoordinator.startFormerHost(第 171–200 行)串起完整链路:
- 捕获 manifest:
source.captureManifest(projectId),随后校验 manifest 的projectId/sourceHostMemberId与输入一致; - 创建转换记录:
createTransition先localIdentity.load加载本地 LAN 权威身份,交叉校验 CA 指纹、成员 ID、以及“fenceId 是否存在 ⇔ 是否拥有权威”的等价关系,再落盘记录。若创建失败,discardBundle(manifest)立即丢弃 bundle——对应规约“捕获中、无主、已取消或已激活的产物经唯一 intent 所有者丢弃”; - 停止 former Host(上节围栏);
- 驱动激活:
driveFormerHost(第 398–451 行)按序执行begin→ 若服务端状态未含本成员则report→ 若bundleState === 'missing'则以application/x-git-bundle流式upload(携带byteCount与sha256)→ 仅当state === 'ready'时调用activate。服务端返回recovery-required/rejected时抛出durable-progress-recovery-required。
参与方(非源 Host)走submitParticipant:先断言自己不是源 Host,创建记录,closeAndDrain后采集并直接提交报告。
recoverProject(第 222–263 行)是幂等的恢复入口:cancelled/activated状态走本地终态结算;fence 仍active先补做停止;随后拉取远端 attempt 状态,源 Host 在远端不存在记录时重新驱动begin,否则补交未含的参与者报告。规约中“步骤 5 可能在云端已激活、双端仍持有本地转换意图时完成;终态绑定等待步骤 6 的快照与 upload-pack”描述的正是这种“远端先行、本地收尾”的窗口,由settleLocalTerminal统一收口:丢弃 bundle、取消则恢复挂起 / 激活则执行binding.finalize并completeAfterActivation,最后markCloudBootstrapTerminalCleanupCompleted打上检查点。
绑定收尾:membership-replaced 与 lan-authority-retired 的原子性
CloudBootstrapBindingFinalizer.ts 的finalize主循环(第 49–81 行)把每一阶段的“效果执行 + 阶段推进 + 持久化”绑成一个不可分割的序列,并且:
- 每一轮先
verifyActivation复核;进入index-repaired和lan-authority-retired阶段前额外执行revalidateCloudBinding(verifyCloud→replaceMembership→repairIndex)——对应规约“在安静之后、第一个云操作之前重新验证完整捕获的权威与 Git-ref 快照”; membership-replaced阶段执行replaceMembership(从每个权威有效成员重建并安装严格带标签的云端成员身份文件),index-repaired执行repairIndex——对应规约“该阶段原子安装成员身份并是适配器选择边界;独立的项目索引随后重建,两个文件互不复制阶段、互不隐藏无关项目”;lan-authority-retired只移动 former Host 的精确无权威目录(效果由组合层注入的effects.retireLanAuthority完成),另一客户端记录为 no-op;保留数据是惰性诊断态,永不打开、永不自动启动、也绝不作为回滚权威。
fence-terminal时围栏状态被advanceCloudBootstrapTransitionPhase原子置为terminal,随后markCloudBootstrapTerminalCleanupCompleted要求“已激活且阶段为fence-terminal”才允许落检查点(CloudBootstrapTransitionRecord.ts 第 563–580 行),与规约“激活恢复把绑定阶段向前驱动、仅在云成员身份权威后重开被挂起项目、再检查点化终态清理”的次序完全一致。
启动目录、后台恢复与重试策略
CloudBootstrapService.ts 是模块的编排层:
- 本地恢复屏障:
prepareLocalRecovery先transitions.list()枚举全部有效转换记录,把所有“不确定”项目(blockedProjectIds+ 本安装拥有的非终态记录)逐一fenceUncertainProject;任何围栏失败整体以cloud-bootstrap-local-recovery-fence-failed失败关闭,并调度目录重试。规约的“启动在组合根发布 feature 服务之前,目录化每个有效的项目键转换文件并围栏所有损坏或终态不完整的项目”即此屏障;已终态完成的激活/取消记录不重复围栏(terminalCleanupCompleted直接跳过)。 - 后台工件恢复:屏障之后才调用
recoverLocalArtifacts(规约中“工件与远端转换恢复保持为该屏障之后的后台工作”)。 - 指数退避重试:目录级与项目级重试共用
delayMs = min(1000 * 2^(n-1), 30000)(第 238、311 行),任何pending结果或异常都会#requestRetry;close()时中断全部 AbortController 并取消挂起重试,保证插件退出干净。 - 恢复只前进:
recoverRecord中每条记录都先assertRecoveryOwner+#assertActorBinding;规约要求恢复时“重读精确的云 attempt,并在每个激活后阶段之前匹配其激活操作、放置代际、激活时间戳、项目、manifest 与 attempt 身份”,这些匹配由observeCloudBootstrapAttemptStatus(CloudBootstrapTransitionRecord.ts 第 635–682 行)的attemptId/projectId/manifestSha256一致性断言,以及activationPhase === 'completed'、fence 必须已host-stopped/not-applicable的激活完整性校验共同保证。激活后恢复“只向前或保持可见的 pending”,cancel对已激活记录直接抛cloud-bootstrap-already-activated,不存在恢复 LAN 权威或清除终态围栏的路径。
云端成员身份的字段白名单
规约最后一条对云端成员身份给出了精确的字段白名单,可从newAuthority的强制解码中逐条对照:
serverUrl:必须是https协议的规范 origin(canonicalCloudOrigin);gitRemoteUrl:必须严格等于cloudProjectGitRemoteUrl(serverUrl, projectId)推导出的地址(第 376 行),即“派生 Git URL”;bindingVersion/wireVersion:必须等于协议包的COLLAB_CLOUD_BINDING_VERSION/COLLAB_PROTOCOL_VERSION;- 记录中的
developmentActorId即开发 actor ID。
而oldAuthority(LAN 端点、CA 指纹等)只留在本地转换记录里,不进入云端成员身份——“不含活动 LAN 凭据、CA、Host 所有权或恢复阶段”由此在解码层被结构性保证。同时整个流程通过保留本地 bundle 与“只上传 bundle 流”的设计,全程保住了未发布文件、本地提交与私有草稿的可重建性。
小结
bootstrap 模块的工程质量集中体现为三件事:单一事实源(一条转换记录 + 八阶段冻结序列)、失败即关闭(目录 fsync、围栏守卫、损坏封禁、单调性断言全部 fail-closed)、恢复即前进(激活后无回滚、取消只补偿、重试带指数退避直至终态检查点)。如果你要继续深入,建议按以下顺序阅读:
- 规约与实现对照:AGENTS.md → CloudBootstrapTransitionRecord.ts → CloudBootstrapTransitionStore.ts;
- 协调与编排:CloudBootstrapCoordinator.ts → CloudBootstrapService.ts → CloudBootstrapBindingFinalizer.ts;
- 围栏与组合装配:CloudBootstrapLocalFence.ts、ClaudianCollabService.ts(
runWithLanHostStartGuard装配处)、集成测试 FormerHostFence.test.ts。
【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考