agent-exec transport 2cd24625d2b60b7c6db7aa1bc628a9c0/af7e0b3bb4d6
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user