agent-exec transport 0579081fa29ea1bd6d1d532a7a177e36/f55c4a162b49

This commit is contained in:
agent-exec-transport
2026-10-18 14:58:04 +00:00
parent 2fb7d2d30f
commit 5b5537aa0f
+139
View File
@@ -519,6 +519,145 @@ class TaskRecoveryTests(unittest.TestCase):
self.assertEqual(receipt.read_bytes(), original)
self.assertIn(receipt_sha, child_card.read_text())
def test_local_child_resume_rejects_reachable_missing_parent(self):
"""Child: configure the task card as child with
immediate_target=feature/parent, create that parent branch on the
bare remote AND locally at the recorded parent_head_sha, capture
a local receipt, then remove ONLY refs/heads/feature/parent from
the bare remote while keeping origin reachable (the bare remote
still serves refs/heads/main). `task resume` in local mode must
refuse with REMOTE_UNVERIFIED instead of silently accepting the
local parent reference: the registered immediate parent branch
is absent remotely, so the local-only reference cannot be
reconciled against the authoritative origin even though it
exists in the worktree.
Currently RED: resolve_parent_ref() does not distinguish
`ls-remote rc=2` (ref missing on a reachable origin) from the
unreachable branch. The function falls through the
`elif [ $rc -ne 2 ]` guard and lets `local_ref` satisfy the
resolver, so cmd_resume reaches IMPORT_RECEIPT and prints
`[resumed]` even though origin is fully reachable and just
missing the registered parent ref. Post-fix the local path must
print `[verdict] REMOTE_UNVERIFIED — origin is reachable but
feature/parent is missing; preserve local work, receipt not
imported` and refuse to import the receipt.
The fixture mirrors
test_local_child_resume_uses_local_parent_when_origin_is_unreachable
but adds the local refs/heads/feature/parent branch and only
deletes the bare remote ref — the worktree URL stays on the
disposable bare remote and main remains fetchable, so the only
authority gap is the absent remote parent ref."""
f = self.fixture
r0 = f.git(self.wt, "rev-parse", "origin/main").stdout.strip()
# (1) Create the parent initiative branch in the main repo and
# push it to the bare remote so the worktree can fetch it once
# at the start, then create the local refs/heads/feature/parent
# branch inside the worktree (the post-fix path must refuse
# even when this local branch exists), branch off the parent
# tip into feature/parent-child, then switch the worktree to
# the child branch.
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")
f.git(self.wt, "fetch", "origin",
"feature/parent:refs/remotes/origin/feature/parent")
# The shared refs/heads namespace means the worktree already
# sees refs/heads/feature/parent (it was created in f.repo via
# `checkout -b feature/parent`); the test must reject even
# when this local branch exists, so leave it intact.
self.assertEqual(
f.git(self.wt, "rev-parse", "--verify",
"refs/heads/feature/parent").stdout.strip(), parent_head)
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 BEFORE
# removing the bare remote ref, so the receipt reflects the
# state where the remote parent ref is still present.
receipt = self.capture()
original = receipt.read_bytes()
receipt_sha = json.loads(original)["receipt_sha256"]
head = f.git(self.wt, "rev-parse", "HEAD").stdout
child_card_before = child_card.read_bytes()
# (4) Remove ONLY refs/heads/feature/parent from the bare
# remote. The worktree's origin URL still points at the
# disposable bare remote and refs/heads/main stays intact, so
# the only gap in authority is the absent remote parent ref.
# The local refs/heads/feature/parent branch remains in the
# worktree (this is the "even if a local parent branch
# exists" half of the scenario).
self.assertEqual(
f.git(f.remote, "rev-parse", "--verify",
"refs/heads/feature/parent").stdout.strip(), parent_head)
f.git(f.remote, "update-ref", "-d", "refs/heads/feature/parent")
# Confirm the bare remote now refuses to serve the parent ref
# while still serving main (so origin is reachable, just
# missing the registered immediate parent ref).
self.assertNotEqual(
f.git(f.remote, "show-ref", "--verify", "--hash",
"refs/heads/feature/parent", check=False).returncode, 0)
self.assertEqual(
f.git(f.remote, "rev-parse", "--verify",
"refs/heads/main").stdout.strip(), r0)
# (5) Local resume must refuse with REMOTE_UNVERIFIED instead
# of silently accepting the local parent ref. No [resumed]
# line, the receipt is not imported (card bytes unchanged and
# receipt_sha256 not embedded), the receipt file is byte-for-
# byte unchanged, and HEAD is preserved.
result = self.resume(receipt, mode="local")
self.assertNotEqual(result.returncode, 0,
f"local child resume must reject when origin is reachable "
f"but the registered parent ref is missing; current code "
f"falls through resolve_parent_ref's rc=2 case and accepts "
f"the local parent, returning 0 and printing [resumed]; "
f"stdout={result.stdout}")
self.assertIn("[verdict] REMOTE_UNVERIFIED", result.stdout,
"the resume must surface REMOTE_UNVERIFIED for the reachable-but-"
"missing-parent scenario so the owner can reconcile the parent "
f"initiative instead of silently resuming on an unsynced tip; "
f"stdout={result.stdout}")
self.assertNotIn("[resumed]", result.stdout,
"the resume must NOT import the receipt under a reachable "
"origin whose registered parent ref is missing — accepting "
"the local-only parent would advertise delivery readiness "
f"for an initiative the worktree cannot reconcile; stdout={result.stdout}")
self.assertEqual(child_card.read_bytes(), child_card_before,
"the child card must not be updated with receipt_sha256 when "
"the resume refuses to import the receipt")
self.assertEqual(receipt.read_bytes(), original,
"the receipt file must be byte-for-byte unchanged when the "
"resume refuses to import it")
self.assertEqual(f.git(self.wt, "rev-parse", "HEAD").stdout, head,
"the child HEAD must not move when the resume refuses to "
"import the receipt")
self.assertNotIn(receipt_sha, child_card.read_text(),
"the child card must not embed the receipt sha256 when "
"the resume refuses to import the receipt")
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