From 8d8585afec6671c82ddf8c9d80063e4499ae7f2f Mon Sep 17 00:00:00 2001 From: agent-exec-transport Date: Sun, 18 Oct 2026 14:17:22 +0000 Subject: [PATCH] agent-exec transport 2cd24625d2b60b7c6db7aa1bc628a9c0/af7e0b3bb4d6 --- tests/test_task_recovery.py | 128 ++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/tests/test_task_recovery.py b/tests/test_task_recovery.py index b24bf541..7e5856ac 100644 --- a/tests/test_task_recovery.py +++ b/tests/test_task_recovery.py @@ -391,6 +391,134 @@ class TaskRecoveryTests(unittest.TestCase): "VALIDATE_RECEIPT_PURPOSE": "ignore"}) self.assertNotEqual(result.returncode, 0, result.stdout) + def test_local_resume_continues_when_origin_is_unreachable(self): + """Standalone: capture a valid TASK_WORK_MODE=local precheck receipt, + point the disposable worktree's origin URL at a nonexistent path, + then assert (a) `task resume` in local mode imports the receipt and + leaves HEAD unchanged and (b) the same receipt under delivery mode + rejects with REMOTE_UNVERIFIED and does not import. + + Currently RED on (a): cmd_resume calls fetch_target unconditionally, + so the unreachable origin aborts the resume before the receipt can + be imported (set -e trips, returncode != 0, no [resumed], receipt + bytes unchanged, card untouched). Post-fix the local path must + skip or survive the unreachable fetch and import the receipt on + the registered in-progress task context. + + Delivery must remain strict under the same offline origin — the + RED-capture only records what (a) requires; (b) stays correct.""" + f = self.fixture + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + # Break the disposable worktree's origin URL so the offline + # scenario is real (the fetch in cmd_resume loses its target). + # The bare-remote fixture and the registered HEAD remain + # identifiable on the worktree fs; only the authoritative URL + # is changed for the duration of this test. + f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git")) + # (1) Local mode must continue authorized work by importing the + # captured receipt under the registered in-progress task context. + local = self.resume(receipt, mode="local") + self.assertEqual(local.returncode, 0, + f"local resume should import the captured receipt under the " + f"registered task (RED: fetch_target aborts the resume before " + f"IMPORT_RECEIPT; returncode={local.returncode}); stdout={local.stdout}") + self.assertIn("[resumed]", local.stdout) + self.assertIn("work_mode=local", local.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head) + self.assertEqual(receipt.read_bytes(), original) + self.assertIn(receipt_sha, self.card.read_text()) + # (2) Same receipt under delivery mode must stay strict under + # the same offline origin: reject with REMOTE_UNVERIFIED, no + # `[resumed]` line, the card is not updated to claim delivery + # authorization, and HEAD remains untouched. + card_before_delivery = self.card.read_bytes() + delivery = self.resume(receipt, mode="delivery") + self.assertNotEqual(delivery.returncode, 0, delivery.stdout) + self.assertIn("[verdict] REMOTE_UNVERIFIED", delivery.stdout) + self.assertNotIn("[resumed]", delivery.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head) + self.assertEqual(self.card.read_bytes(), card_before_delivery) + + def test_local_child_resume_uses_local_parent_when_origin_is_unreachable(self): + """Child: configure the task card as child with + immediate_target=feature/parent, create that parent branch locally + at the recorded parent_head_sha, capture a local receipt, make + origin unreachable, and assert that `task resume` in local mode + imports the receipt using the authorized local parent reference + (the local refs/heads/feature/parent tip = parent_head_sha). + + Currently RED: cmd_resume reaches fetch_target before resolving + the parent reference, so the unreachable origin aborts the + resume before the receipt can be imported (returncode != 0, no + `[resumed]`, receipt bytes unchanged, child card untouched). + Post-fix the local path must skip or survive the unreachable + fetch AND let resolve_parent_ref fall back to the authorized + local parent branch when origin is unqueriable.""" + f = self.fixture + r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip() + # (1) Create the parent initiative branch in the main repo so + # the parent has a deterministic tip commit, push it to the + # bare remote so the worktree can fetch it once at the start, + # then create the child branch on top of the parent inside the + # worktree and switch the worktree to it. + f.git(f.repo, "checkout", "-b", "feature/parent") + (f.repo / "parent-tip").write_text("authorized parent initiative tip\n") + f.git(f.repo, "add", "parent-tip") + f.git(f.repo, "commit", "-m", "Parent initiative tip commit") + parent_head = f.git(f.repo, "rev-parse", "HEAD").stdout.strip() + f.git(f.repo, "push", "origin", "feature/parent") + # Bring the parent branch into the worktree, branch off the + # parent at the recorded tip, then check the worktree out on + # the child branch. + f.git(self.wt, "fetch", "origin", + "feature/parent:refs/remotes/origin/feature/parent") + f.git(self.wt, "branch", "feature/parent-child", parent_head) + f.git(self.wt, "checkout", "feature/parent-child") + # (2) Write the child card on the child branch so branch_card + # finds it (the file is named after the branch slug) and the + # resume's immediate_target + parent_head_sha fields resolve + # to feature/parent and the recorded parent tip. + child_card = self.wt / ".task" / "feature-parent-child.md" + self.assertFalse(child_card.exists()) + child_card.parent.mkdir(exist_ok=True) + child_card.write_text( + "# feature/parent-child\n" + "任务主记录:fixture;状态:进行中;owner:Lifecycle Test\n" + f"- 权威仓:fixture;目标分支:main;R0(initial_base_sha)= {r0}\n" + f"- 工作区:{self.wt}(worktree);分支:feature/parent-child\n" + "- 分支类型:child;initiative_id:feature-parent;root_target:main;" + "immediate_target:feature/parent;accepting_requirements:false;" + "parent_branch:feature/parent;" + f"parent_head_sha:{parent_head};" + "parent_pr:无;parent_source:local(非正式准入)\n" + ) + f.git(self.wt, "add", str(child_card.relative_to(self.wt))) + f.git(self.wt, "commit", "-m", "Register child fixture task") + # (3) Capture a local receipt on the child branch. + receipt = self.capture() + original = receipt.read_bytes() + receipt_sha = json.loads(original)["receipt_sha256"] + head = f.git(self.wt, "rev-parse", "HEAD").stdout + # (4) Break the origin URL so the offline scenario is real. + f.git(self.wt, "remote", "set-url", "origin", str(f.root / "missing.git")) + # (5) Local resume must import the receipt using the + # authorized local parent reference and leave HEAD unchanged. + result = self.resume(receipt, mode="local") + self.assertEqual(result.returncode, 0, + f"local child resume should import the captured receipt via the " + f"local parent ref (RED: fetch_target aborts the resume before " + f"resolve_parent_ref / IMPORT_RECEIPT; returncode={result.returncode}); " + f"stdout={result.stdout}") + self.assertIn("[resumed]", result.stdout) + self.assertIn("work_mode=local", result.stdout) + self.assertIn(f"observed_target={parent_head}", result.stdout) + self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head) + self.assertEqual(receipt.read_bytes(), original) + self.assertIn(receipt_sha, child_card.read_text()) + def test_local_precheck_rejects_reachable_uncomparable_target(self): """Origin advertises a target SHA but the advertised object is unavailable locally, so ancestry cannot be compared against the