fix(issue-10,13): check every agy artifact for holders and purge orphaned leftovers

Round-3 review of f5a6575 (planner-reviewer-codex-01, R1/R2).

R1 - the adapter's in-use check handed only the conversation DB to lsof, so a
holder that keeps the presence lock (or a sidecar) open while naming the uuid
on no command line and in no session variable was invisible, and the held
lock was unlinked from under it. AgyAgentAdapter.conversation_in_use now
passes every path of the conversation (DB, -wal/-shm/-journal, brain,
annotation, presence lock).

R2 - both resolver stages required the DB, so once an interrupted or pre-#10
purge had removed it, the WAL/annotation/lock left behind were "no artifact"
(exit 5) forever. BaseAgentAdapter.has_artifacts (default: a uuid-bearing
artifact path that exists) replaces the resolver's path fallback, and agy's
override accepts any of its pieces.

Tests: a flock holder that names the uuid nowhere is refused and nothing is
removed; orphaned leftovers resolve to agy and are purged. VERSIONS.md
v4.4.3 items 1-2 and orc-onboard SKILL.md updated.

Refs #10 #13

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-09-13 10:30:02 +09:00
1 parent f5a6575bf2
commit fae70c13d9
6 files changed
+90 -9

No files matched your search

+52
View File
@@ -297,6 +297,58 @@ def test_purge_cli_unresolved_uuid(tmp_path, other):
assert json.loads(res.stdout)["agent"] is None
def _lock_holder(lock: Path) -> subprocess.Popen:
"""A process that holds the presence lock open (flock) but names the
conversation nowhere - not on its command line (the path travels through
the environment) and not in a session variable."""
code = ("import fcntl, os, time; f = open(os.environ['MAM_TEST_LOCK'], 'a'); "
"fcntl.flock(f, fcntl.LOCK_EX); time.sleep(60)")
proc = subprocess.Popen([sys.executable, "-c", code], env=dict(os.environ, MAM_TEST_LOCK=str(lock)))
time.sleep(0.5)
return proc
def test_purge_cli_refuses_when_only_the_presence_lock_is_held_open(tmp_path, conv):
"""codex R1: the in-use check must also ask lsof about the presence lock
and the sidecars, not just the DB - a holder can keep the lock open with
the uuid on no command line and in no session variable."""
paths = _agy_tree(tmp_path, conv)
adapter = get_adapter("agy")
ctx = DiscoveryContext(workspace=str(tmp_path), agent_name="agy", home_dir=str(tmp_path))
holder = _lock_holder(paths["lock"])
try:
assert adapter.conversation_in_use(conv, ctx) is True
res = _purge_cli("agy", conv, tmp_path)
finally:
_stop(holder)
assert res.returncode == 4, res.stdout + res.stderr
assert json.loads(res.stdout)["in_use"] is True
assert all(p.exists() for p in paths.values()), "a refused purge must remove nothing"
assert adapter.conversation_in_use(conv, ctx) is False
def test_purge_cli_resolves_agy_from_orphaned_leftovers(tmp_path, conv):
"""codex R2: after the DB is gone (an interrupted or pre-#10 purge) the
WAL, annotation and lock must still resolve to agy and be purged, instead
of 'no artifact' leaving them behind forever."""
root = tmp_path / ".gemini" / "antigravity-cli"
leftovers = [root / "conversations" / f"{conv}.db-wal",
root / "annotations" / f"{conv}.pbtxt",
root / "presence" / f"{conv}.lock"]
for p in leftovers:
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("x")
res = _purge_cli("auto", conv, tmp_path)
assert res.returncode == 0, res.stdout + res.stderr
out = json.loads(res.stdout)
assert out["agent"] == "agy"
assert sorted(out["purged"]) == sorted(str(p) for p in leftovers)
assert out["residual"] == []
assert not any(p.exists() for p in leftovers)
# ---------------------------------------------------------------------------
# #13 — orc_onboard.sh --remove --purge-conversation
# ---------------------------------------------------------------------------