agent-exec transport dfbdcf8f9879479cec6791f6db4182b1/9e213bfbef61
This commit is contained in:
+13
-6
@@ -35,13 +35,20 @@ origin_transport_unavailable() { # $1=失败的远端命令 stderr → 仅明确
|
||||
esac
|
||||
[ ! -e "$path" ]
|
||||
}
|
||||
target_history() { # $1=可选基线(无缓存目标时代替比较,如任务 R0)→ 输出 ok(远端传输不可达/通告提交为基线后代)、rewound(回退或分叉)或 uncomparable(认证/授权/未知错误、目标 ref 缺失、无基线、通告提交本地缺失或无法比较)
|
||||
local cached listing advertised rc=0
|
||||
cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || cached=${1:-}
|
||||
target_history() { # $1=可选基线(如任务 R0;无缓存目标时代替比较)→ 输出 ok(通告提交为基线后代;传输不可达时缓存目标为基线后代或无缓存目标)、rewound(回退或分叉)或 uncomparable(认证/授权/未知错误、目标 ref 缺失、无基线、提交本地缺失或无法比较)
|
||||
local base=${1:-} cached listing advertised rc=0
|
||||
cached=$(git rev-parse -q --verify "refs/remotes/origin/${TARGET_BRANCH}^{commit}" 2>/dev/null) || cached=
|
||||
if ! listing=$(git ls-remote origin "refs/heads/${TARGET_BRANCH}" 2>&1); then
|
||||
if origin_transport_unavailable "$listing"; then echo ok; else echo uncomparable; fi
|
||||
if ! origin_transport_unavailable "$listing"; then echo uncomparable
|
||||
elif [ -z "$cached" ]; then echo ok
|
||||
elif [ -z "$base" ]; then echo uncomparable
|
||||
else
|
||||
git merge-base --is-ancestor "$base" "$cached" 2>/dev/null || rc=$?
|
||||
case "$rc" in 0) echo ok ;; 1) echo rewound ;; *) echo uncomparable ;; esac
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
[ -n "$cached" ] || cached=$base
|
||||
advertised=$(printf '%s\n' "$listing" \
|
||||
| awk -v ref="refs/heads/${TARGET_BRANCH}" '$2 == ref && !seen { print $1; seen = 1 }')
|
||||
[ -n "$advertised" ] && [ -n "$cached" ] || { echo uncomparable; return 0; }
|
||||
@@ -174,9 +181,8 @@ cmd_precheck() {
|
||||
exit 3
|
||||
fi
|
||||
if [ "$mode" = local ]; then
|
||||
python3 "${SCRIPT_DIR}/evidence.py" --local-context --active || return 2
|
||||
if [ "$remote_fresh" -eq 0 ]; then
|
||||
# 无缓存目标时只以活动任务卡登记的 R0 作比较基线,不把它当作远端最新值。
|
||||
# 以活动任务卡登记的 R0 为比较基线(不把它当作远端最新值);先于任务上下文核验,使缓存目标与 R0 矛盾时给出明确判定。
|
||||
case "$(target_history "$(card_r0 "$(branch_card "$branch")")")" in
|
||||
rewound)
|
||||
echo "[verdict] REMOTE_UNVERIFIED — origin/${TARGET_BRANCH} rewound or diverged from the known target; preserve local work and reconcile the target history"
|
||||
@@ -186,6 +192,7 @@ cmd_precheck() {
|
||||
return 6 ;;
|
||||
esac
|
||||
fi
|
||||
python3 "${SCRIPT_DIR}/evidence.py" --local-context --active || return 2
|
||||
[ "$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"
|
||||
return 0
|
||||
|
||||
@@ -1290,6 +1290,37 @@ class TaskRecoveryTests(unittest.TestCase):
|
||||
"the task HEAD must not move when the precheck refuses to "
|
||||
"advertise LOCAL_READY")
|
||||
|
||||
def test_local_outage_rejects_cached_target_diverged_from_r0(self):
|
||||
"""Origin transport is unavailable and the cached origin/main does not
|
||||
descend from the registered R0. Local precheck must surface
|
||||
REMOTE_UNVERIFIED and local resume must not import the receipt, instead
|
||||
of continuing on a cache that already contradicts the task baseline."""
|
||||
f = self.fixture
|
||||
receipt = self.capture()
|
||||
original = receipt.read_bytes()
|
||||
card_before = self.card.read_bytes()
|
||||
r0 = self.local_authorization_scope()["r0"]
|
||||
divergent = f.git(self.wt, "commit-tree", r0 + "^{tree}", "-m",
|
||||
"Unrelated cached target").stdout.strip()
|
||||
f.git(self.wt, "update-ref", "refs/remotes/origin/main", divergent)
|
||||
self.assertEqual(f.git(self.wt, "merge-base", "--is-ancestor", r0, divergent,
|
||||
check=False).returncode, 1)
|
||||
f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git"))
|
||||
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
|
||||
local = self.precheck()
|
||||
self.assertNotEqual(local.returncode, 0, local.stdout)
|
||||
self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout)
|
||||
self.assertIn("rewound or diverged", local.stdout)
|
||||
self.assertNotIn("[verdict] LOCAL_READY", local.stdout)
|
||||
|
||||
resumed = self.resume(receipt)
|
||||
self.assertNotEqual(resumed.returncode, 0, resumed.stdout)
|
||||
self.assertNotIn("[resumed]", resumed.stdout)
|
||||
self.assertEqual(self.card.read_bytes(), card_before)
|
||||
self.assertEqual(receipt.read_bytes(), original)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user