agent-exec transport 15c16d890ff16327f11293fe6ecbfdde/f7c42d8ea545

This commit is contained in:
agent-exec-transport
2026-10-18 13:00:18 +00:00
parent f2c24015b0
commit 2e0da0dadb
2 changed files with 83 additions and 0 deletions
+28
View File
@@ -659,6 +659,34 @@ class EvidenceTests(unittest.TestCase):
self.assertNotEqual(result.returncode, 0)
self.assertIn(message, result.stderr)
def test_local_continuation_rejects_receipt_target_branch_mismatch(self):
"""Resume-local must refuse a receipt whose before.target_branch or
after.target_branch points at a different branch than the registered
task target. Re-sign the fixture receipt with the existing helper so
the digest still validates, and assert the existing production
rejection that flags the receipt as belonging to a different task
branch (or, when the after snapshot already disagrees, the
authoritative-target-changed guard that fires first)."""
wt = self.local_task()
original = self.capture("make precheck", cwd=wt)
cases = (
("before.target_branch",
"Precheck belongs to a different task branch",
lambda d: d["before"].update(target_branch="feature/other")),
("after.target_branch",
"Precheck authoritative target has changed",
lambda d: d["after"].update(target_branch="feature/other")),
)
for name, message, change in cases:
with self.subTest(case=name):
data = json.loads(json.dumps(original))
change(data)
data["output_sha256"] = hashlib.sha256(data["output"].encode()).hexdigest()
self.resign(data)
result = self.validate(wt)
self.assertNotEqual(result.returncode, 0)
self.assertIn(message, result.stderr)
if __name__ == "__main__":
unittest.main()
+55
View File
@@ -194,6 +194,61 @@ class TaskRecoveryTests(unittest.TestCase):
# 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_local_precheck_rejects_reachable_target_divergence(self):
"""Origin is reachable but its target was replaced by a divergent commit
that shares R0 with the cached forward target without descending from it.
Local precheck must refuse LOCAL_READY and surface the divergence so the
owner can reconcile the target history instead of trusting the cache.
Both the cached forward target and the divergent commit are locally
available so target_rewound() can compare them via merge-base."""
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) Build a divergent commit on top of R0 in the main checkout; it shares
# R0 with the cached forward target but does not descend from it.
f.git(f.repo, "switch", "--detach", r0)
(f.repo / "divergent-main").write_text("different target history\n")
f.git(f.repo, "add", "divergent-main")
f.git(f.repo, "commit", "-m", "Create divergent fixture history")
divergent = f.git(f.repo, "rev-parse", "HEAD").stdout.strip()
# Both targets must share R0 (otherwise they would just be unrelated refs).
self.assertEqual(f.git(f.repo, "merge-base", forward, divergent).stdout.strip(), r0)
# The cached forward target must NOT be an ancestor of the divergent commit.
self.assertEqual(f.git(f.repo, "merge-base", "--is-ancestor", forward,
divergent, check=False).returncode, 1)
# Both commits must be locally available so target_rewound() can inspect them.
self.assertEqual(f.git(self.wt, "cat-file", "-e", forward + "^{commit}",
check=False).returncode, 0)
self.assertEqual(f.git(self.wt, "cat-file", "-e", divergent + "^{commit}",
check=False).returncode, 0)
# (3) Push the divergent commit so the bare remote's object database sees it,
# then replace refs/heads/main on the bare remote with the divergent commit.
f.git(f.repo, "push", "origin", "HEAD:refs/heads/divergent")
f.git(f.remote, "update-ref", "refs/heads/main", divergent, forward)
self.assertEqual(f.git(f.remote, "rev-parse", "refs/heads/main").stdout.strip(), divergent)
# The worktree cache still points at the forward descendant because we have
# not fetched since.
self.assertEqual(f.git(self.wt, "rev-parse", "origin/main").stdout.strip(), forward)
head_before = f.git(self.wt, "rev-parse", "HEAD").stdout
# (4) Local precheck: fetch_target() refuses the non-fast-forward update to
# the divergent commit, target_rewound() then surfaces the divergence, and
# the precheck must not advertise LOCAL_READY.
local = self.precheck()
self.assertNotEqual(local.returncode, 0, local.stdout)
self.assertNotIn("[verdict] LOCAL_READY", local.stdout)
self.assertIn("[verdict] REMOTE_UNVERIFIED", local.stdout)
self.assertIn("rewound or diverged", 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)