no-mistakes 分支同步与推送安全机制深度解析:guarded sync、custody recovery 与 force-push 数据保全
2026/9/16 19:10:29 网站建设 项目流程

no-mistakes 分支同步与推送安全机制深度解析:guarded sync、custody recovery 与 force-push 数据保全

【免费下载链接】no-mistakesgit push no-mistakes项目地址: https://gitcode.com/GitHub_Trending/no/no-mistakes

本篇技术指南聚焦 no-mistakes 项目内部的branch-sync-and-push-safety技能契约(见 .agents/skills/branch-sync-and-push-safety/SKILL.md),系统讲解其在 AI 驱动 pipeline 场景下的三条安全主线:本地分支与远程的受保护同步、Review 之后头部连续性与推送绑定、以及 rebase 基线与 force-push 的数据丢失防护。读完你将理解no-mistakes axi sync一族命令背后的状态机、--recover的精确归还语义,以及resolveForcePushDecision如何从根上避免静默丢弃他人代码。

一、整体架构:一个服务、三种入口

syncaxi sync与 TUI 的u动作共享同一个internal/branchsync服务,其核心契约在 internal/branchsync/sync.go 中实现:

  • 唯一的普通 worktree 变更方式:一次"干净的、受保护的移动"——要么是 behind 分支上的严格快进(strict fast-forward),要么是"锚定后重置"(anchored reset)到内容等价的 diverged pipeline 头部(前提是本地独有提交已被 pipeline 头部完整承载)。
  • 被动检查永不联网InspectCached(由 CLI/TUI/axi status 渲染)只读取本地 Git、持久化 provenance 与只读的 gate 祖先证据,绝不 fetch、绝不改动 refs/index/worktree。
  • 阻塞状态永不破坏性操作:被阻塞的分支状态(如blocked_pipeline_ownedblocked_diverged)下,服务绝不执行 reset、stash、merge、rebase、force、switch、delete 或对外部 remote 的 update。

Service结构体(sync.go)同时暴露了两个可替换的 remote 操作 seam(lsRemote/fetchRemote),使测试可以在不污染全局状态的前提下注入受控 fake。

二、核心状态机:状态、关系与安全等级

同步契约由三组常量描述,它们是 CLI、AXI、TUI 三套展示层的共同语言:

分支状态(State,见 sync.go)

状态含义
pipeline_ownedpipeline 正在/曾持有分支,头部已移动但未成功推送,禁止本地后续提交
push_in_progresspush 步骤正在执行
behind本地落后于 pipeline 推送绑定
synchronized本地与推送绑定完全一致
local_ahead本地领先,建议走axi run而非同步
diverged双方分叉
dirtyworktree 不干净
remote_advanced/remote_rewritten远端包含绑定之外的提交 / 远端不再等于绑定
remote_missing远端分支不存在
merged_remote_retained/merged_remote_removedPR 已合并,分支保留 / 已被清理
closedPR 已关闭
offline网络操作失败,fail closed
target_changed推送目标配置被改动
ambiguous_context上下文无法唯一判定
legacy_unbound无精确推送绑定
custody_returned滞留 run 的 custody 已归还
user_ownedrun 终止于提交头未移动之前,分支本就属于操作者

关系(Relation,sync.go)equalbehindaheaddivergedunknown

安全等级(Safety,sync.go):仅两种等级允许Apply落地——safe_fast_forwardsafe_equivalent_advance(见CanApply)。

三、Refresh 与 Apply:只做"干净的受保护移动"

3.1 单操作网络超时预算:branch_sync_remote_timeout

Refresh中,每次网络 remote 操作都使用从调用方 ctx 派生的独立有界子上下文,而不是共享同一个 deadline(sync.go):一次"慢但成功"的git.LsRemote绝不能偷走随后git.FetchRemoteBranchToPrivateRef的预算,否则一个可达的远端会被误判为 offline。Apply的最终 live 检查也使用相同的单操作预算。

该预算即Service.RemoteTimeout(sync.go),来自操作者全局配置branch_sync_remote_timeout,默认值为config.DefaultBranchSyncRemoteTimeout(60s,见 internal/config/config.go)。关键设计点:RepoConfig故意没有对应字段——一个被推送进仓库的.no-mistakes.yaml不能通过修改配置来放宽或收紧服务等待网络多久,从而防止仓库内容操纵超时预算(相关回归测试见TestLoadRepo_BranchSyncRemoteTimeoutIsNotARepoSetting)。注意Recover的本地 gate fetch 属于本地仓库间操作,不在此网络 deadline 契约内。

3.2 Refresh:先验证再移动

Refresh的流程(sync.go)可概括为:本地检视 → 数据库绑定复检(run、generation、pushed head、target fingerprint/kind/ref 全部一致,否则blocked_binding_changed)→ 带独立超时的ls-remote→ 带独立超时的 fetch 到私有 refrefs/no-mistakes/sync/<run>→ 校验 fetched == live → 校验 live == 持久化的 pushed head → 再按 PR 生命周期(merged/closed)或关系分类落定。它从不更新普通的 remote-tracking ref,只把精确的推送 ref 验证进 no-mistakes 私有 ref。

3.3 Apply:apply 前的多重复检 + fail-closed 的两种移动

Apply(sync.go)在真正移动之前要连续通过多层防线:

  1. gate 执行上下文拒绝检查(gateContextRefusal,防止在 gate 自身的嵌套执行环境里误操作);
  2. Refresh重新出计划,且CanApply必须为真;
  3. 数据库 push binding 复检(run、pushed head、generation、ref、target fingerprint/kind);
  4. inspect复检本地分支/HEAD/cleanliness 未变;
  5. 带超时的最终 livels-remote,要求 live == pushed head;
  6. 最终前提复检(blocked_assumptions_changed);
  7. 关系证明复检:equivalent advance 重新执行equivalentDivergence,普通快进重新验证祖先关系。

移动本身分两种:

  • 严格快进git merge --ff-only --no-edit <pushed>,由 Git 自身保证不会覆盖未落地的本地工作;
  • 等价 diverged 推进:先把同步前本地 HEAD 锚定到refs/no-mistakes/sync-anchor/<run>并校验,再reset --hard到验证过的 pipeline 头部。

成功后还会检查 post-apply 的 worktree 是否仍干净(防止 hook 留下脏状态),最终才置为synchronized

成功推送的持久化绑定由 internal/pipeline/steps/push.go 写入:精确 SHA、免凭据的目标指纹(branchsync.TargetFingerprint对 URL 做规范化 sha256,见 sync.go)、ref 与 generation;历史遗留行的可空字段永远不允许从可变的head_sha反推 provenance。结构化的 PR 生命周期(merged/closed)会退役对应分支。

四、Terminal run 的 custody recovery:把"滞留"的分支精确归还

当一个 run 进入 TERMINAL 状态且存在未发布的 pipeline 提交(moved head)时,分支被 pipeline "托管"却再也不会被发布——必须有一个显式的受保护出口。这就是--recover

4.1 决策矩阵

Recover的完整决策矩阵与 fail-safe 规则写在 sync.go 的 doc comment 中(P 指 preserved pipeline head,即 terminal 验证过的 run head,由私有 recovery ref 固定):

relationworktree默认行为--keep-local
equalany本地锚定,归还 custody相同
aheadany本地锚定,归还 custody相同
behindclean严格快进到 P,归还 custodycustody 留在本地 head,gate 以 CAS 重置到它
behinddirty拒绝(先 commit/stash)同上
diverged 且 P 含全部本地工作clean锚定 pre-recovery 本地 head 后 fail-closed 移动到 P,归还 custody同上
diverged 且 P 含全部本地工作dirty拒绝(先 commit/stash)同上
diverged 且 P 不含全部本地工作any拒绝(锚定并人工协调 / 受保护 rerun)同上
P 缺失any拒绝同上

4.2 三种基础场景

  • 取消且 head 未移动(user_owned:TERMINAL run 若从未改变提交头(head_sha == submitted_head_sha,无 push、无 custody 戳),选择逻辑会保留它可见,避免误报为blocked_wrong_branch,并分类为user_owned——无next_action、非阻塞退出、永不表述为可恢复的 custody,--recover在此是幂等 no-op(不修改任何文件、ref 或数据库行),新的axi run或单独授权的直接 push 永不被阻塞(见 sync.go)。
  • 本地可达(equal/ahead):本地锚定即可,不需要 gate 访问;但若 gate 可用,其既有 recovery ref 必须与记录 head 一致。
  • 本地不可达(behind/diverged):从 run 专属的 gate recovery ref 验证并 fetch preserved head;只有干净的 behind worktree 才会被快进。

terminalization 固化:pipeline 在移除托管 worktree 之前,会把每个已验证的未发布 head 固定到refs/no-mistakes/recover/<run>(见 internal/custody/refs.go)。恢复时读取该 run 专属 ref 而非要求 gate 分支匹配,因此 abort、rebase、pre-push 失败都保持可恢复,同时独立移动的 gate 分支不受影响。历史遗留的记录 head 若仍以 dangling gate 对象存在,会在恢复时被锚定。

4.3preservedContainsLocalWork:内容包含性证明,而非 patch-id

被取消的验证经常留下一个本地分支的 rebase作为 preserved head——同样的逻辑提交换了新 SHA,equality 与 ancestry 只能读出"分叉"。此时干净的 diverged worktree 只有在preservedContainsLocalWork证明"P 已包含本地全部变更"时才会被采纳(sync.go):

  • 证明方式是可执行的git merge-tree三方合并:以merge-base为唯一可靠锚点(绝不使用runs.base_sha——那是上一个 gate head,对重新推送过的分支会携带本地从未有过的 pipeline 提交),将本地分支合并进 preserved head,要求结果树恰好等于preserved head 的树;
  • 刻意不使用 patch identity:patch ID 丢弃 hunk 位置与空白,无法区分"真正的重放"与"对另一个相同代码块的同形状编辑",基于它的包含性断言不构成证明;
  • 一切无法判定的情况都升级处理,包括 rebase 的修复轮同时改写了操作者的行——此时没有任何东西能区分"有意的 pipeline 修复"与"被丢弃的改动",由操作者决定(fail-safe 优先于便利)。

4.4recoverAdoptPreserved:自带守卫的原子移动

采纳(adopt)路径先把 pre-recovery 本地 head 锚定到refs/no-mistakes/recover-local/<run>,然后用 Git 操作自带的失败语义移动分支(sync.go):

  • git update-ref <branch> <preserved> <observed>原子 CAS,并发提交移动了分支则整体拒绝、零副作用;
  • git read-tree -m -u <observed> <preserved>:拒绝覆盖被修改或未跟踪的工作树文件;若拒绝,分支通过反向 CAS 回滚。

绝不是"先观察再reset --hard"的 check-then-act——后者会摧毁观察与执行之间的空隙里落地的一切。doc comment 还明确记录了一个已知的有界 Git 根本限制(两条命令之间的并发 checkout 窗口),但它不会造成数据丢失:包含性证明在移动前已完成、pre-recovery head 保持锚定、custody 永不盖章,操作 fail-closed 为报告失败而非虚假成功。

4.5--keep-local:显式丢弃未发布提交的唯一出口

当操作者选择保留 behind/diverged 的本地 head 而不是采纳 preserved head 时,--keep-local(sync.go):

  • 绝不触碰 worktree
  • 以原子 CAS 把 gate 分支移动到保留的本地 head(并发 gate push 获胜则恢复拒绝);
  • 通过gate 侧的 fetch暂存对象(refs/no-mistakes/custody-return/<run>)——绝不是 push,因为 push 会触发 gate 的 receive hook 并启动一个新的 pipeline run;
  • 独立移动过的 gate head 先被固定到refs/no-mistakes/recover-gate/<run>,CAS 永不丢弃它。

对于"已验证的 recorded head 同时不存在于 worktree 与可访问 gate"的场景,--recover --keep-local是操作者显式丢弃未发布 pipeline 提交的唯一路径;不可验证的 head、不可访问的 gate、冲突的 ref 一律保留人工协调,普通--recover仍拒绝。

4.6recovery_archives:追加式归档的窄路径

一个 diverged 的较新 head 还可以通过恰好一条追加式recovery_archives数据库绑定来证明被保存,但前提是它的 repository、run、branch、required/preserved heads、原始 archive ref 与 gate recovery ref全部重新验证通过(见 sync.go)。recoverySourceAvailable是唯一的发现/分类 owner,且只提供recover_custody+--recover --keep-local——归档 head永远不会被选中作为工作结果。任何类似的未绑定 ref、stale、moved、symbolic、malformed、跨库、歧义记录都 fail closed。

五、评审后头部连续性与推送绑定

5.1assertPipelineHeadContinuity:每个后评审步骤的入口守卫

固定 pipeline 顺序(Test、Document、Lint、Push、PR、CI)中 Review 之后的每一个步骤都在入口调用assertPipelineHeadContinuity(internal/pipeline/steps/common_fix.go),它是该语义的唯一 owner

  • 比较的是记录的 headsctx.Run.HeadSHA,由 daemon 进程内 Run 结构持有、仅由 no-mistakes 的 commit 代码推进)与实时的 worktree HEADgit.HeadSHA),绝不从可变 worktree 反推锚点(否则就是循环且可被击败);
  • equal 或 descendant 的 live head 继续;backward、sibling(旁支)与不可验证的 head 在步骤做任何工作之前失败——例如并发的git reset把共享 worktree 拨到丢失已评审提交的旁支,文档步骤就不会在 clobber 之上再提交并发布。

5.2review_approved_head_sha:可验证的不可变推送绑定

一次成功完成的完整评审会原子地记录runs.review_approved_head_sha;parked、failed、skipped 与历史遗留的评审不携带任何推断的授权。Push 步骤读取这份持久绑定,只允许推送"恰好该提交或其后代",并推送已验证的不可变 SHA而非可变的HEAD(internal/pipeline/steps/push.go)。任何情况下都不得从runs.head_sha、worktree、gate ref 或远端分支推断评审授权。若评审批准 head 与待推 head 之间失去连续性(非 equal/非 descendant),assertReviewApprovedPushHead直接拒绝发布——CI 修复路径的处理方式是重新验证,而不是绕过发布。

六、rebase 基线与 force-push 安全(数据丢失防护)

这个工具存在的全部意义就是不弄丢用户的代码:宁可拒绝推送并暴露 finding,也不用任何"聪明的恢复"来冒险(完整论证见 internal/pipeline/steps/forcepush.go 的注释)。

6.1 rebase 基线只来自新鲜权威 ref

rebase 基线来自刚 fetch 的权威远端 ref,绝不使用本地或陈旧状态。同时,构建在未推送的本地默认分支提交之上的分支会以NeedsApproval+AutoFixable=false暂停(detectBundledLocalDefaultCommits,#283),而不是静默扩大 PR 范围。

6.2resolveForcePushDecision:唯一的 force-push 路由

每一次 force-push 都经过resolveForcePushDecision(forcepush.go),它重新读取live 远端 head,仅在以下情况放行:

  1. 远端分支不存在(newBranch,普通 push);
  2. 远端已等于要推的 head(upToDate,无需推送);
  3. 远端未变,仍是lastSeenSHA
  4. 远端当前所有提交都已被(按 patch-id 内容等价)并入新 head,或属于 run 已知重写的历史(^baseSHA)。

任何其他情况一律拒绝;ls-remote/fetch失败也 fail closed——绝不在没有显式锚点的情况下退化为裸--force/--force-with-leaseremoteCommitsNotIncorporated(forcepush.go)使用rev-list --cherry-pick:干净的 rebase 会重写 SHA 但保留 patch-id,因此 pipeline 真正重放过的提交会被识别为"已并入",而只在远端存在过的提交会被报告为"即将被丢弃"。

6.3lastSeenSHA:必须是被观察过的 head,而不是 live 远端 tip

lastSeenSHA必须保持为 run最后观察/产生的 head——来自当前 run 或先前 run 的推送 provenance,或 remote-tracking ref——而绝不是live 远端 tip:

  • rebase 步骤只在普通push 时刷新origin/<branch>不在force push 时刷新;
  • CI 修复在本地提交并回到 Review 重新验证;后续 Push 步骤负责它们的远端更新与 force-push 安全。

两个必须永不回归的历史 bug:

  • #281:把 lease 锚定到"推送前立刻读取"的 SHA——它总是通过,等于没有任何保护;
  • 在 force push 时总是 fetch 该分支——这等于重现 #281。

6.4 回归测试矩阵

相关回归测试的分布与意图(可在对应文件与 e2e 中检索验证):

  • 分支同步网络预算TestRefreshSlowSuccessfulLsRemoteDoesNotStealFetchBudgetTestRefreshSlowButSuccessfulLsRemoteAloneExceedsItsOwnBudgetReportsOfflineTestRefreshRaisedRemoteTimeoutAcceptsTheSameLegitimateSlowLsRemoteTestRefreshParentCancellationStopsFetchAfterLsRemoteSucceedsTestServiceRemoteTimeoutDefaultsToConfigDefaultTestLoadGlobal_InvalidBranchSyncRemoteTimeout(internal/branchsync/sync_test.go);
  • 评审后连续性TestPostReviewStepsRefuseHeadClobberAtEntryTestPushStep_RefusesPostReviewClobberWithoutLaterPipelineCommitTestPushStep_BindsRemoteAndDatabaseToVerifiedCommitWhenHEADMovesDuringPushTestExecutor_FullRereviewReplacesApprovalWithoutAuthorizingParkedRound
  • force-push 安全TestPushStep_RefusesToClobberAdvancedUpstreamBranch(#305)、TestForcePushRun_RefusesToClobberOutOfBandBranchCommitTestRebaseStep_DetectsUnpushedLocalDefaultBranchCommits(#283)、TestResolveForcePushDecision_*TestExecutor_CIRestartRevalidatesBeforePushTestPushStep_AllowsForcePushAfterMidRunRebaseOverPriorPushedGeneration(#837)、TestPushStep_AllowsForcePushOnRerunOverPriorRunPushedGeneration(#837);
  • 端到端旅程TestAxiBranchSyncJourneyTestAxiCustodyRecoveryJourneyTestAxiCustodyRecoveryAfterRebaseJourneyTestAxiPrePushAbortUnmovedHeadCustodyJourney

公共操作指引由 internal/skill/skill.go 与 live AXI 字符串持有(见 internal/cli/axi_guidance.go),并通过make skill重新生成。

七、操作命令速查与适用边界

场景命令说明
查看分支同步状态(被动,不联网)no-mistakes axi status/no-mistakes sync --check只读本地证据与缓存分类
执行受保护同步no-mistakes axi sync严格快进或等价 diverged 锚定推进
归还滞留 run 的分支 custodyno-mistakes axi sync --recover采纳 gate 保存的 preserved head
保留本地 head、显式丢弃未发布提交no-mistakes axi sync --recover --keep-local不触碰 worktree,CAS 移动 gate 分支
绑定归档为 keep-local 恢复证据no-mistakes sync --bind-archive-ref <ref>仅限refs/heads/archive/*的精确提交,不修改任何 Git ref
提交意图开始新一轮验证no-mistakes axi run --intent "<what the user set out to accomplish>"user_owned/custody_returned之后的标准下一步

约束与边界:--check--recover互斥;--keep-local必须与--recover同用;branch_sync_remote_timeout是全局唯一配置项(默认 60s),仓库级.no-mistakes.yaml无法覆盖;服务只同步发起调用的 worktree,并要求检出的是精确分支(detached HEAD 直接拒绝);Sync/Recover都会先做 gate 嵌套执行上下文拒绝检查。所有路径的共同底色是同一句话——不做任何可能丢失代码的"聪明"操作,无法判定时把决定权交还给操作者

【免费下载链接】no-mistakesgit push no-mistakes项目地址: https://gitcode.com/GitHub_Trending/no/no-mistakes

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询