"""门禁层:调用方只传事实,本层只出决定。 收敛对象是主线上已经存在的硬门禁,不是新发明的门禁 种类。主线(origin/main)上不存在 merge_gate 的 record/enforce 模式、分支保护门禁与全自动门禁——那些只 在未合并的分支里;本轨不从分支搬功能、不新增门禁种类、 不把硬门禁降级为“只记录不阻断”(那会削弱安全语义, 不属于收敛)。本层收敛的是三类已有判定: 1. 目录门(resolve_inside):worktree 内相对路径的安全 解析。服务端 _safe_worktree_path、cloud/tree.py 的 resolve_tree_path、cloud/marker_tools.py 的 safe_worktree_path(worker 经 marker_tools 调用)此前 各写了一份 realpath + commonpath,仅在 commonpath 的 ValueError 处理上不一致(服务端不捕,另两处捕)。 现统一为本函数:绝对路径、.. 逃逸、符号链接逃逸、 跨盘符 ValueError 一律拒绝;空串返回原 worktree。 2. 控制门(decide_control):marker_tools.check_control_gate 的纯函数判定(cancel / retry 的目标状态与派生关系), 现为本层 canonical 实现,marker_tools 只保留同名薄壳 (调用点与测试 import 的名字不变)。 3. 合并状态门(merge_state_decision):服务端 _merge_task 的状态判定——只有 completed 可标记 merged,其它状态 一律拒绝(含已 merged 的重复调用)。权限判定 (approve_merge)仍在服务端,不进本层:本层只看事实 (任务状态),不查权限。 不可覆盖规则(冻结): - 目录门的拒绝没有任何 confirm / override / bypass 参数。 本模块的函数签名里 deliberately 没有这类参数:显式 确认即使存在,也只能覆盖“保护类风险”,绝不能覆盖 目录违规——而主线上目录违规本来就没有确认通道, 任何调用方都不得以“用户已确认”为由绕过本层。 测试钉住:resolve_inside 与 decide_control 的签名中 不含 confirm / override / bypass 字样。 - 合并状态门同样没有覆盖参数:非 completed 任务不存在 “确认后合并”的路径,重复 merged 调用仍是拒绝。 返回形态沿用 codebase 的元组风格:(allowed, reason), reason 为空表示放行;目录门另有 resolve_inside 直接 返回路径或 None(旧调用点的形态不变)。 """ import os TERMINAL_STATUSES = ("completed", "failed", "interrupted", "merged", "merge-failed") RETRYABLE_STATUSES = ("failed", "interrupted", "merge-failed") def resolve_inside(base, rel): """worktree 内相对路径的安全解析:越界回 None。 空串返回 base 本身(旧三处行为一致);绝对路径、 .. 逃逸、符号链接逃逸、commonpath 跨盘符 ValueError 一律 None。目录内指回内部的符号链接放行到真实位置 (调用方仍只拿到 worktree 内的内容)。 """ if not rel: return base if os.path.isabs(rel): return None real_base = os.path.realpath(base) target = os.path.realpath(os.path.join(real_base, rel)) try: if os.path.commonpath([real_base, target]) != real_base: return None except ValueError: return None return target def is_goal_task(task): """goal 任务判定:verify_command 非空或 goal_mode 为真。 与 worker run_one 的 goal 分支同款;goal 字段本身是 goal_mode=1 时 prompt 的副本,不单独当判定条件。 """ if not task: return False return bool(task.get("verify_command") or task.get("goal_mode")) def decide_control(target, sender, relation, kind): """task-cancel / task-retry 的纯函数门禁,返回 (ok, reason)。 kind 只能是 "cancel" 或 "retry",其它值一律拒绝。 权限层不在这里:worker 侧另查 create_task / approve_merge;本函数只管目标状态与关系,单测不需 AuthStore。goal 任务不可取消或重试(goal 循环自有轮次)。 """ if kind not in ("cancel", "retry"): return False, "未知的控制操作" if target is None: return False, "目标任务不存在" if (target.get("project_id") is not None and sender.get("project_id") is not None and target.get("project_id") != sender.get("project_id")): return False, "跨项目任务不可操作" if target.get("id") == sender.get("id"): return False, "不能操作自己" if is_goal_task(target): return False, "goal 任务不可取消或重试(goal 循环自有轮次)" status = target.get("status") if kind == "cancel": if status in TERMINAL_STATUSES: return False, "目标已终态(%s),不可取消" % status else: if status not in RETRYABLE_STATUSES: return False, ("仅 failed / interrupted / merge-failed" " 可重试,当前状态为 %s" % status) if relation not in ("descendant", "same_creator"): return False, "目标不在发送方派生链上且创建人不同" return True, "" def merge_state_decision(status): """合并标记的状态判定:仅 completed 放行,返回 (ok, reason)。 含已 merged 的重复调用在内,其它状态一律拒绝;文案与 服务端旧 409 错误一致。权限判定不在本层。 """ if status == "completed": return True, "" return False, "only completed tasks can be marked merged"