test(delegate): assert the unproven-state contract, not its prose

Review fold on the #88113 follow-up. The new guards asserted implementation
details that a strictly-better future change would break, and the second
producer of the payload schema had no coverage at all.

- The distinguishability test asserted the failure payload was byte-identical
  to the genuinely-clean one (`for key in commits/dirty/pruned: assertEqual`).
  That freezes the AMBIGUITY as a required property: emitting `commits: None`
  for "unknown" would improve exactly what #88113 is about and fail the test.
  Now asserts what the parent actually depends on -- both keep the worktree,
  and only the flag separates them.
- `assertNotIn("inspection_failed", ok_payload)` pinned key ABSENCE on the
  happy path, forbidding an always-present-but-False flag (a legitimately
  better JSON contract: stable key set for serializers). Now
  `assertFalse(...get("inspection_failed", False))` -- same coverage, tolerant
  of that refactor.
- `assertIn("UNKNOWN", note)` coupled tests to one word of English prose, and
  was not even a cross-producer contract: delegate_tool's note said "state
  unknown" (lowercase), so a copy-edit broke the implied convention. Tests now
  assert the note names the worktree AND branch -- the actionable part for a
  human -- and both producers' notes were aligned to read as one contract.
- The raises test never proved its patched seam ran (a future short-circuit
  before any git call would keep it green while proving nothing). Now checks
  `call_count` and mirrors the branch-survival + note-names-path legs its
  sibling had.
- NEW `WorktreePayloadSchemaTests`: commit 2's whole point is the schema the
  parent reads, but delegate_tool's fallback -- the second producer -- was
  verified only by reading. It now AST-parses the real fallback dict literal
  and compares against live `finalize_subagent_worktree()` output, so the two
  producers cannot drift and the pre-fix leak (repo_root/base_commit, missing
  commits/dirty/pruned) cannot come back.
- Docs/docstring drift: the flag has a second trigger (finalization itself
  raising, handled in delegate_tool), and the module docstring listed
  `inspection_failed` without `note`. Both corrected.
- Extracted the duplicated 5-line "corrupt the index" setup into
  `_break_git_index()` beside the file's other module-level helpers.

Validation: 19/19 tests/tools/test_subagent_worktree.py; ruff clean. New
schema guard mutation-checked -- reverting delegate_tool's fallback to the
pre-fix `dict(_worktree_info)` shape fails it. Restores checksum-verified.
This commit is contained in:
kshitij
2026-08-17 18:27:07 +05:30
parent 38ea711fd0
commit 97c4f9eeec
4 changed files with 112 additions and 25 deletions
+102 -18
View File
@@ -4,10 +4,13 @@ Inspired by Muse Code's --subagent-worktree-isolation (clean-room
implementation from documented behavior).
"""
import ast
import inspect
import os
import subprocess
import sys
import tempfile
import textwrap
import shutil
import unittest
from pathlib import Path
@@ -36,6 +39,14 @@ def _make_repo(root: Path) -> Path:
return repo
def _break_git_index(wt: Path) -> None:
"""Corrupt a worktree's index so the real git probes exit non-zero."""
git_dir = Path(_git(["rev-parse", "--git-dir"], wt).stdout.strip())
if not git_dir.is_absolute():
git_dir = (wt / git_dir).resolve()
(git_dir / "index").write_bytes(b"not-a-valid-git-index\n")
class SubagentWorktreeTests(unittest.TestCase):
def setUp(self):
self.tmp = Path(tempfile.mkdtemp(prefix="hermes-sw-test-"))
@@ -166,19 +177,22 @@ class SubagentWorktreeTests(unittest.TestCase):
# The parent agent only ever sees this payload (it cannot read logs),
# so the uncertainty must travel in the dict — otherwise "0 commits,
# clean" reads as "the child produced nothing" and the work we just
# preserved never gets looked at.
# preserved never gets looked at. Assert the actionable invariants
# (flag set; note names the worktree + branch), not the prose.
self.assertTrue(payload["inspection_failed"])
self.assertIn("UNKNOWN", payload["note"])
self.assertIn(info["path"], payload["note"])
self.assertIn(info["branch"], payload["note"])
def test_finalize_flags_unproven_state_distinguishably(self):
"""#88113 follow-up: a failed inspection must not look like "no work".
Without an explicit flag, "inspection failed, uncommitted work
preserved" and "inspected fine, child left nothing" serialize to the
byte-identical dict {commits: 0, dirty: False, pruned: False} — so the
parent agent's rational reading of the failure case is the exact wrong
conclusion.
preserved" and "inspected fine, child left nothing" are
indistinguishable to the parent agent — so its rational reading of the
failure case is the exact wrong conclusion. The contract asserted here
is *distinguishability*, not the specific field values (a future
change emitting ``commits: None`` for "unknown" would be strictly
better and must not break this test).
"""
repo = _make_repo(self.tmp)
@@ -192,19 +206,22 @@ class SubagentWorktreeTests(unittest.TestCase):
assert bad_info is not None
bad_wt = Path(bad_info["path"])
(bad_wt / "WIP.txt").write_text("real work\n", encoding="utf-8")
git_dir = Path(_git(["rev-parse", "--git-dir"], bad_wt).stdout.strip())
if not git_dir.is_absolute():
git_dir = (bad_wt / git_dir).resolve()
(git_dir / "index").write_bytes(b"not-a-valid-git-index\n")
_break_git_index(bad_wt)
bad_payload = sw.finalize_subagent_worktree(bad_info)
# The three state fields are identical — that is exactly the ambiguity.
for key in ("commits", "dirty", "pruned"):
self.assertEqual(ok_payload[key], bad_payload[key])
# Only the flag separates them.
self.assertNotIn("inspection_failed", ok_payload)
self.assertNotIn("note", ok_payload)
# Both keep the worktree, so "pruned" alone cannot separate them...
self.assertFalse(ok_payload["pruned"])
self.assertFalse(bad_payload["pruned"])
self.assertTrue(os.path.isdir(bad_info["path"]))
self.assertTrue((bad_wt / "WIP.txt").exists())
# ...the flag must. Tolerant of an always-present-but-False refactor.
self.assertFalse(ok_payload.get("inspection_failed", False))
self.assertTrue(bad_payload["inspection_failed"])
self.assertNotEqual(
bool(ok_payload.get("inspection_failed")),
bool(bad_payload.get("inspection_failed")),
)
self.assertFalse((ok_payload.get("note") or "").strip())
def test_finalize_flags_unproven_state_when_inspection_raises(self):
"""A raising probe is the same unknown state as a non-zero exit."""
@@ -215,13 +232,18 @@ class SubagentWorktreeTests(unittest.TestCase):
def _boom(*_a, **_k):
raise subprocess.TimeoutExpired(cmd="git", timeout=30)
with mock.patch.object(sw, "_run_git", side_effect=_boom):
with mock.patch.object(sw, "_run_git", side_effect=_boom) as m:
payload = sw.finalize_subagent_worktree(info)
# Prove the patched seam was actually exercised.
self.assertGreaterEqual(m.call_count, 1)
self.assertFalse(payload["pruned"])
self.assertTrue(payload["inspection_failed"])
self.assertIn("UNKNOWN", payload["note"])
self.assertIn(info["path"], payload["note"])
self.assertIn(info["branch"], payload["note"])
self.assertTrue(os.path.isdir(info["path"]))
branches = _git(["branch", "--list", info["branch"]], repo).stdout
self.assertNotEqual(branches.strip(), "")
def test_finalize_missing_path_reports_pruned(self):
payload = sw.finalize_subagent_worktree(
@@ -257,6 +279,68 @@ class SubagentWorktreeTests(unittest.TestCase):
self.assertIn("WORKTREE ISOLATION", note)
class WorktreePayloadSchemaTests(unittest.TestCase):
"""The parent agent reads ONE schema under ``entry["worktree"]``.
Two producers write it: ``finalize_subagent_worktree`` and
``delegate_tool``'s fallback for when finalize itself raises. Compare the
fallback against the REAL finalize output (not a hardcoded key list) so
the two can never drift apart.
"""
def setUp(self):
self.tmp = Path(tempfile.mkdtemp(prefix="hermes-sw-schema-"))
self.addCleanup(shutil.rmtree, self.tmp, True)
def test_fallback_matches_finalize_schema(self):
from tools import delegate_tool
repo = _make_repo(self.tmp)
info = sw.create_subagent_worktree(str(repo), "schema")
assert info is not None
happy = sw.finalize_subagent_worktree(info, prune=False)
# Read the fallback's ACTUAL keys out of the source (not a hardcoded
# copy), so drift in delegate_tool is what fails this test.
tree = ast.parse(
textwrap.dedent(inspect.getsource(delegate_tool._run_single_child))
)
fallback_keys = None
for node in ast.walk(tree):
if not isinstance(node, ast.Assign) or not isinstance(
node.value, ast.Dict
):
continue
target = node.targets[0]
if (
isinstance(target, ast.Subscript)
and isinstance(target.slice, ast.Constant)
and target.slice.value == "worktree"
):
fallback_keys = {
k.value
for k in node.value.keys
if isinstance(k, ast.Constant)
}
break
self.assertIsNotNone(
fallback_keys,
"delegate_tool no longer assigns a dict literal to "
'entry_dict["worktree"] — update this schema guard',
)
assert fallback_keys is not None # narrow for type checkers
# Fallback must be a superset: every happy-path key plus the two
# unproven-state keys, and nothing invented. The pre-fix version
# emitted repo_root/base_commit and omitted commits/dirty/pruned.
self.assertTrue(set(happy).issubset(fallback_keys))
self.assertEqual(
fallback_keys - set(happy), {"inspection_failed", "note"}
)
for leaked in ("repo_root", "base_commit"):
self.assertNotIn(leaked, fallback_keys)
class DelegationConfigGateTests(unittest.TestCase):
def test_worktree_isolation_default_off(self):
from tools import delegate_tool
+4 -2
View File
@@ -2520,8 +2520,10 @@ def _run_single_child(
"pruned": False,
"inspection_failed": True,
"note": (
"worktree finalize raised; state unknown — inspect "
f"{_worktree_info.get('path', '')} manually before "
"worktree finalize raised: 'commits' and 'dirty' are "
"UNKNOWN, not zero/clean. The worktree and branch were "
f"preserved — inspect {_worktree_info.get('path', '')} "
f"(branch {_worktree_info.get('branch', '')}) before "
"assuming no work."
),
}
+1 -1
View File
@@ -28,7 +28,7 @@ Contract (mirrors Muse Code's documented semantics):
clean tree is removed automatically after the child finishes; anything
holding work is kept and reported. Pruning requires affirmative proof:
if a git inspection probe fails the state is unknown, so the worktree is
kept and the result entry is flagged ``inspection_failed`` (#88113).
kept and the result entry carries ``inspection_failed`` + ``note`` (#88113).
Only the local terminal backend is supported: on docker/ssh/modal/etc. the
worktree created on the host would not be visible inside the sandbox, so
@@ -412,10 +412,11 @@ With isolation on:
reviews or merges each branch (`git log <branch>`, `git merge <branch>`).
- A worktree left with **no commits and a clean tree is pruned automatically**
(`pruned: true`); anything holding work is kept.
- Pruning requires proof. If a git inspection probe fails, the worktree and
branch are kept and the entry carries `inspection_failed: true` plus a
`note` — `commits`/`dirty` are then defaults, not measurements, so inspect
the worktree rather than assuming the child produced nothing.
- Pruning requires proof. If a git inspection probe fails — or finalization
itself errors — the worktree and branch are kept and the entry carries
`inspection_failed: true` plus a `note` — `commits`/`dirty` are then
defaults, not measurements, so inspect the worktree rather than assuming
the child produced nothing.
Scope: opt-in, git-only, and local-terminal-backend-only. In a non-git
directory, on docker/ssh/modal backends, or if worktree creation fails, the