diff --git a/policy/governance.md b/policy/governance.md index a869f17e..93bbe34b 100644 --- a/policy/governance.md +++ b/policy/governance.md @@ -59,7 +59,9 @@ LOCAL_READY 只用于局部恢复;新任务入口始终校验严格 READY, 一致、工作区干净且组件状态未变;收据 `root_target/immediate_target` 等于卡片范围;目标只允许快进落后,回退或分叉拒绝。 远端跟踪目标 ref 缺失时,local 模式的 precheck 与 `resume-local` 只在当前登记任务身份、本地 R0 对象、R0 为本地 HEAD 祖先、 工作区干净、分支/worktree、组件与范围全部核验通过时继续。缺失或过期的远端 ref 从不证明新鲜度;只要有任一目标历史可用, -其祖先关系须一致。正式 Review 与合入仍须先完成新鲜的权威同步。 +其祖先关系须一致。local precheck 刷新失败时,远端不可查询或目标 ref 缺失按上述本地核验继续;远端可查询且已有缓存目标时, +通告提交须本地存在且为缓存目标后代才继续,回退/分叉或通告提交本地缺失无法比较均判 REMOTE_UNVERIFIED。 +正式 Review 与合入仍须先完成新鲜的权威同步。 `resume-delivery` 仍要求 READY 且收据目标等于当前权威目标。LOCAL_READY 始终是 SELF 本地工作记录, 不能用于建任务、正式交付、Review、合入、CI、GATE 或 REPLAY。 旧收据允许作为历史证据原样导入,不自动成为当前恢复或交付准入;格式继续为 schema 1。 diff --git a/scripts/task.sh b/scripts/task.sh index 02a0607d..16a3827e 100755 --- a/scripts/task.sh +++ b/scripts/task.sh @@ -18,14 +18,15 @@ fetch_target() { git rev-parse --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" >/dev/null \ || { echo "[verdict] REMOTE_UNVERIFIED — target commit missing" >&2; return 6; } } -target_rewound() { # 0 仅当 origin 通告的目标提交本地可用且不是缓存 origin/ 的后代;不可达或无法比较返回 1 +target_history() { # 输出 ok(远端不可查询/目标 ref 缺失/无缓存目标/通告提交为缓存后代)、rewound(回退或分叉)或 uncomparable(通告提交本地缺失或无法比较) local cached advertised rc=0 - cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || return 1 + cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || { echo ok; return 0; } advertised=$(git ls-remote origin "refs/heads/${TARGET_BRANCH}" 2>/dev/null \ - | awk -v ref="refs/heads/${TARGET_BRANCH}" '$2 == ref && !seen { print $1; seen = 1 }') || return 1 - [ -n "$advertised" ] && git cat-file -e "${advertised}^{commit}" 2>/dev/null || return 1 + | awk -v ref="refs/heads/${TARGET_BRANCH}" '$2 == ref && !seen { print $1; seen = 1 }') || { echo ok; return 0; } + [ -n "$advertised" ] || { echo ok; return 0; } + git cat-file -e "${advertised}^{commit}" 2>/dev/null || { echo uncomparable; return 0; } git merge-base --is-ancestor "$cached" "$advertised" 2>/dev/null || rc=$? - [ "$rc" -eq 1 ] + case "$rc" in 0) echo ok ;; 1) echo rewound ;; *) echo uncomparable ;; esac } check_worktree() { python3 "${SCRIPT_DIR}/check-worktree.py" "$1"; } main_worktree_root() { git worktree list --porcelain -z | python3 -c 'import os,sys; print(os.fsdecode(sys.stdin.buffer.read().split(bytes([0]),1)[0][9:]))'; } @@ -150,9 +151,15 @@ cmd_precheck() { fi if [ "$mode" = local ]; then python3 "${SCRIPT_DIR}/evidence.py" --local-context --active || return 2 - if [ "$remote_fresh" -eq 0 ] && target_rewound; then - echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} rewound or diverged from the cached target; preserve local work and reconcile the target history" - return 6 + if [ "$remote_fresh" -eq 0 ]; then + case "$(target_history)" in + rewound) + echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} rewound or diverged from the cached target; preserve local work and reconcile the target history" + return 6 ;; + uncomparable) + echo "[verdict] REMOTE_UNVERIFIED — advertised origin/${TARGET_BRANCH} commit is missing locally and cannot be compared with the cached target; preserve local work and retry the refresh" + return 6 ;; + esac fi [ "$remote_fresh" -eq 1 ] || echo "[remote-freshness] unavailable — LOCAL_READY rests on local task context only" echo "[verdict] LOCAL_READY — existing task may continue authorized local work; final delivery still requires latest-target sync and checks"