agent-exec transport ba8f131d4f2b8d718c2fa2b84ec799b9/b503d085cc5b
This commit is contained in:
@@ -526,6 +526,36 @@ class EvidenceTests(unittest.TestCase):
|
||||
self.assertIn("local continuation validated", result.stdout)
|
||||
self.assertEqual(self.git("rev-parse", "HEAD", cwd=wt), task_head)
|
||||
|
||||
def test_local_continuation_accepts_when_target_ref_reappears(self):
|
||||
"""A resume-local receipt captured with no remote-tracking target ref
|
||||
validates after a forward target ref reappears, provided R0 stays locally
|
||||
reachable and the reappeared target is a forward descendant of R0.
|
||||
|
||||
The receipt records target_head=None; the rewind check is skipped because
|
||||
either side is missing, and the reappeared ref (a forward descendant of R0)
|
||||
must satisfy the ancestry guard that the registered task is still current."""
|
||||
wt = self.local_task()
|
||||
head_before = self.git("rev-parse", "HEAD", cwd=wt)
|
||||
# Drop refs/remotes/origin/main BEFORE capture so the receipt records
|
||||
# target_head=None. R0 (the recorded initial baseline) must remain
|
||||
# reachable as a local object.
|
||||
self.git("update-ref", "-d", "refs/remotes/origin/main")
|
||||
self.git("cat-file", "-e", self.head)
|
||||
self.capture("make precheck", cwd=wt)
|
||||
captured = json.loads(self.receipt.read_text())
|
||||
self.assertIsNone(captured["after"]["target_head"])
|
||||
# Recreate refs/remotes/origin/main as a forward descendant of R0.
|
||||
forward = self.git("commit-tree", "-p", self.head,
|
||||
"-m", "Forward target ref reappeared", self.head + "^{tree}")
|
||||
self.git("update-ref", "refs/remotes/origin/main", forward)
|
||||
# Validation must succeed: receipt has no target_head (rewind check is
|
||||
# skipped) and the reappeared target descends from R0 (ancestry guard
|
||||
# accepts a forward target ref).
|
||||
result = self.validate(wt)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertIn("local continuation validated", result.stdout)
|
||||
self.assertEqual(self.git("rev-parse", "HEAD", cwd=wt), head_before)
|
||||
|
||||
def test_local_precheck_captures_without_remote_tracking_ref(self):
|
||||
wt = self.local_task()
|
||||
head_before = self.git("rev-parse", "HEAD", cwd=wt)
|
||||
|
||||
@@ -144,6 +144,56 @@ class TaskRecoveryTests(unittest.TestCase):
|
||||
self.assertNotIn("[verdict] LOCAL_READY", delivery.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
|
||||
def test_local_precheck_rejects_closed_task(self):
|
||||
"""A closed registered task (已完成(合入前闭合)) must not advertise LOCAL_READY.
|
||||
|
||||
The current local precheck emits LOCAL_READY for any registered in-progress
|
||||
card text and never inspects the card status, so this test records the
|
||||
expected red result: the verdict is wrong and the exit code is wrong."""
|
||||
f = self.fixture
|
||||
self.card.write_text(self.card.read_text().replace("状态:进行中",
|
||||
"状态:已完成(合入前闭合)"))
|
||||
self.save_card_change()
|
||||
self.assertIn("已完成(合入前闭合)", self.card.read_text())
|
||||
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
local = self.precheck()
|
||||
self.assertNotEqual(local.returncode, 0, local.stdout)
|
||||
self.assertNotIn("[verdict] LOCAL_READY", local.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
|
||||
def test_local_precheck_rejects_reachable_target_rewind(self):
|
||||
"""Origin is reachable but its target was rewound after the task cached a
|
||||
newer descendant. Local precheck must not emit LOCAL_READY under that
|
||||
divergence — the cached refs/remotes/origin/main is no longer trustworthy.
|
||||
|
||||
The current implementation lets fetch_target() surface a non-FFREMOTE_UNVERIFIED
|
||||
line, then unconditionally prints LOCAL_READY in local mode. The exit code
|
||||
is wrong and the verdict is wrong; this test records both expected failures."""
|
||||
f = self.fixture
|
||||
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
# (1) Advance the remote and let local precheck refresh the cached origin/main.
|
||||
f.advance_remote()
|
||||
refresh = self.precheck()
|
||||
self.assertEqual(refresh.returncode, 0, refresh.stdout)
|
||||
forward = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
|
||||
self.assertNotEqual(forward, r0)
|
||||
# (2) Rewind the bare remote target back to R0; the worktree cache still
|
||||
# points at the forward descendant because we have not fetched since.
|
||||
f.git(f.remote, "update-ref", "refs/heads/main", r0, forward)
|
||||
self.assertEqual(f.git(f.remote, "rev-parse", "refs/heads/main").stdout.strip(), r0)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward)
|
||||
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
|
||||
# (3) Local precheck sees a reachable origin whose target is older than the
|
||||
# cache. fetch_target() refuses the non-fast-forward update; the precheck
|
||||
# must still refuse LOCAL_READY.
|
||||
local = self.precheck()
|
||||
self.assertNotEqual(local.returncode, 0, local.stdout)
|
||||
self.assertNotIn("[verdict] LOCAL_READY", local.stdout)
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head_before)
|
||||
# After the failed fetch the cache still points at the forward descendant
|
||||
# so a follow-up run can repeat the same check.
|
||||
self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward)
|
||||
|
||||
def test_unknown_work_mode_is_rejected(self):
|
||||
result = self.precheck(mode="anything")
|
||||
self.assertNotEqual(result.returncode, 0, result.stdout)
|
||||
|
||||
Reference in New Issue
Block a user