fix(codex): resolve peer-review findings R1-R6 and T1-T2
- R1: use herdr send-keys C-c for graceful stop - R2: preserve JSONL fallback discovery when database is empty - R3: apply workspace-scoped SQLite query before bounding results - R4: tighten artifact validation to fail closed on missing/invalid metadata - R5: verify actual rollout presence before reporting resumable in status - R6: remove unconditional drift-B backend command fallback - T1: parameterize artifact metadata regression with verified finder fixture - T2: seed unmanaged and managed sessions in mock herdr for drift-B testing - Add final loop review reports (100% PASS)
This commit is contained in:
1 parent
76f10b2096
commit
47640e2fd7
10 files changed
+504
-34
No files matched your search
+1
-1
@@ -655,7 +655,7 @@ elif cmd1 == "agent":
|
||||
rollout_file = os.path.join(sessions_dir, f"rollout-001-{session_uuid}.jsonl")
|
||||
now_ts = int(time.time())
|
||||
with open(rollout_file, "w") as rf:
|
||||
rf.write(json.dumps({"payload": {"cwd": ws_abs, "id": session_uuid}}) + "\\\\n")
|
||||
rf.write(json.dumps({"payload": {"cwd": ws_abs, "id": session_uuid}}) + "\\n")
|
||||
conn.execute("INSERT OR REPLACE INTO threads (id, cwd, rollout_path, created_at, updated_at) VALUES (?, ?, ?, ?, ?)", (session_uuid, ws_abs, rollout_file, now_ts, now_ts))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -995,7 +995,172 @@ def test_codex_reconcile_full_block_integration(tmp_path):
|
||||
assert valid_candidates == ['u-target']
|
||||
|
||||
|
||||
def test_codex_verify_artifact_metadata_fail_closed(tmp_path, monkeypatch):
|
||||
"""R4 / T1: Verify verify_artifact fails closed on missing/invalid metadata, id mismatch, or db inconsistency."""
|
||||
import json, sqlite3
|
||||
from lib_py.agents.registry import get_adapter
|
||||
from lib_py.agents.base import DiscoveryContext
|
||||
ws = str(tmp_path / "ws")
|
||||
home = str(tmp_path / "home")
|
||||
os.makedirs(ws, exist_ok=True)
|
||||
os.makedirs(home, exist_ok=True)
|
||||
adapter = get_adapter('codex')
|
||||
ctx = DiscoveryContext(workspace=ws, agent_name='codex', home_dir=home)
|
||||
|
||||
uuid = "12345678-1234-1234-1234-123456789abc"
|
||||
rollout_file = tmp_path / "rollout.jsonl"
|
||||
rollout_file.write_text("{}\n")
|
||||
|
||||
# Install finder override using pytest monkeypatch before all cases
|
||||
monkeypatch.setattr(adapter, "_find_rollout", lambda u, c: str(rollout_file))
|
||||
|
||||
# Assert finder resolves the fixture before testing malformed contents
|
||||
resolved_path = adapter._find_rollout(uuid, ctx)
|
||||
assert resolved_path == str(rollout_file)
|
||||
assert os.path.exists(resolved_path)
|
||||
|
||||
# Parameterized invalid metadata cases
|
||||
invalid_payloads = [
|
||||
"{}", # Empty dict (R4 finding)
|
||||
json.dumps({"payload": {"cwd": ws}}), # Missing payload.id
|
||||
json.dumps({"payload": {"id": uuid}}), # Missing payload.cwd
|
||||
json.dumps({"payload": {"id": "other-id", "cwd": ws}}), # Mismatched session id
|
||||
json.dumps({"payload": {"id": uuid, "cwd": "/wrong/path"}}), # Mismatched workspace
|
||||
"not-valid-json", # Non-JSON content
|
||||
json.dumps([]), # Non-dict JSON
|
||||
json.dumps({"payload": "not-a-dict"}), # Non-dict payload
|
||||
]
|
||||
|
||||
for inv in invalid_payloads:
|
||||
rollout_file.write_text(inv + "\n")
|
||||
assert adapter.verify_artifact(uuid, ctx) is False, f"Expected False for payload: {inv}"
|
||||
|
||||
# Database / transcript metadata inconsistency check
|
||||
db_path = tmp_path / "state_5.sqlite"
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("CREATE TABLE threads (id TEXT PRIMARY KEY, cwd TEXT, rollout_path TEXT)")
|
||||
conn.execute("INSERT INTO threads VALUES (?, ?, ?)", (uuid, "/conflicting/db/cwd", str(rollout_file)))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
monkeypatch.setattr(adapter, "_state_db", lambda c: str(db_path))
|
||||
# Even if transcript has valid ws, DB cwd conflict must fail closed
|
||||
rollout_file.write_text(json.dumps({"payload": {"id": uuid, "cwd": ws}}) + "\n")
|
||||
assert adapter.verify_artifact(uuid, ctx) is False
|
||||
|
||||
# Valid control case (matching DB cwd and transcript cwd)
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("UPDATE threads SET cwd=? WHERE id=?", (ws, uuid))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
assert adapter.verify_artifact(uuid, ctx) is True
|
||||
|
||||
|
||||
def test_codex_discover_empty_stale_db_fallback(tmp_path):
|
||||
"""R2: Verify discover falls back to JSONL transcripts when DB is empty or has stale rows."""
|
||||
import json, sqlite3, time
|
||||
from lib_py.agents.registry import get_adapter
|
||||
from lib_py.agents.base import DiscoveryContext
|
||||
ws = str(tmp_path / "ws")
|
||||
home = str(tmp_path / "home")
|
||||
os.makedirs(ws, exist_ok=True)
|
||||
os.makedirs(home, exist_ok=True)
|
||||
codex_home = tmp_path / "home" / ".codex"
|
||||
os.makedirs(codex_home, exist_ok=True)
|
||||
|
||||
adapter = get_adapter('codex')
|
||||
ctx = DiscoveryContext(workspace=ws, agent_name='codex', home_dir=home)
|
||||
|
||||
# Create a valid date-nested rollout on disk
|
||||
uuid_valid = "11111111-1111-1111-1111-111111111111"
|
||||
sessions_dir = codex_home / "sessions" / "2026" / "09" / "05"
|
||||
sessions_dir.mkdir(parents=True, exist_ok=True)
|
||||
valid_file = sessions_dir / f"rollout-2026-09-05T00-00-00-{uuid_valid}.jsonl"
|
||||
valid_file.write_text(json.dumps({"payload": {"id": uuid_valid, "cwd": ws}}) + "\n")
|
||||
|
||||
# Empty DB: threads table exists with 0 rows
|
||||
db_path = codex_home / "state_5.sqlite"
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("CREATE TABLE threads (id TEXT PRIMARY KEY, cwd TEXT, rollout_path TEXT, updated_at INTEGER)")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
# Should fall through to JSONL file and discover uuid_valid
|
||||
found = adapter.discover(ctx)
|
||||
assert uuid_valid in found
|
||||
|
||||
# Stale DB: insert row pointing to nonexistent file
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("INSERT INTO threads (id, cwd, rollout_path, updated_at) VALUES (?, ?, ?, ?)",
|
||||
("stale-uuid", ws, str(codex_home / "nonexistent.jsonl"), int(time.time())))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
found_stale = adapter.discover(ctx)
|
||||
assert uuid_valid in found_stale
|
||||
|
||||
|
||||
def test_codex_discover_workspace_scoping_over_50_foreign_rows(tmp_path):
|
||||
"""R3: Verify global LIMIT does not hide target-workspace threads behind >50 foreign threads."""
|
||||
import json, sqlite3, time
|
||||
from pathlib import Path
|
||||
from lib_py.agents.registry import get_adapter
|
||||
from lib_py.agents.base import DiscoveryContext
|
||||
ws_target = str(tmp_path / "ws_target")
|
||||
ws_foreign = str(tmp_path / "ws_foreign")
|
||||
home = str(tmp_path / "home")
|
||||
os.makedirs(ws_target, exist_ok=True)
|
||||
os.makedirs(ws_foreign, exist_ok=True)
|
||||
os.makedirs(home, exist_ok=True)
|
||||
|
||||
adapter = get_adapter('codex')
|
||||
ctx_target = DiscoveryContext(workspace=ws_target, agent_name='codex', home_dir=home)
|
||||
|
||||
codex_home = Path(home) / ".codex"
|
||||
sessions_dir = codex_home / "sessions" / "2026" / "09" / "05"
|
||||
sessions_dir.mkdir(parents=True, exist_ok=True)
|
||||
db_path = codex_home / "state_5.sqlite"
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("CREATE TABLE IF NOT EXISTS threads (id TEXT PRIMARY KEY, cwd TEXT, rollout_path TEXT, updated_at INTEGER)")
|
||||
|
||||
now = int(time.time())
|
||||
# Seed 1 valid target thread
|
||||
target_uuid = "target-1234-1234-1234-123456789abc"
|
||||
target_file = sessions_dir / f"rollout-001-{target_uuid}.jsonl"
|
||||
target_file.write_text(json.dumps({"payload": {"id": target_uuid, "cwd": ws_target}}) + "\n")
|
||||
conn.execute("INSERT INTO threads (id, cwd, rollout_path, updated_at) VALUES (?, ?, ?, ?)",
|
||||
(target_uuid, ws_target, str(target_file), now - 100))
|
||||
|
||||
# Seed 60 newer foreign threads
|
||||
for i in range(60):
|
||||
f_uuid = f"foreign-{i:04d}-1234-1234-123456789abc"
|
||||
f_file = sessions_dir / f"rollout-001-{f_uuid}.jsonl"
|
||||
f_file.write_text(json.dumps({"payload": {"id": f_uuid, "cwd": ws_foreign}}) + "\n")
|
||||
conn.execute("INSERT INTO threads (id, cwd, rollout_path, updated_at) VALUES (?, ?, ?, ?)",
|
||||
(f_uuid, ws_foreign, str(f_file), now + i))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
candidates = adapter.discover(ctx_target)
|
||||
assert target_uuid in candidates
|
||||
|
||||
|
||||
def test_codex_discover_symlink_canonical_alias(tmp_path):
|
||||
"""R3: Verify discover resolves symlink workspace aliases correctly."""
|
||||
from lib_py.agents.registry import get_adapter
|
||||
from lib_py.agents.base import DiscoveryContext
|
||||
real_ws = tmp_path / "real_ws"
|
||||
real_ws.mkdir()
|
||||
symlink_ws = tmp_path / "symlink_ws"
|
||||
symlink_ws.symlink_to(real_ws, target_is_directory=True)
|
||||
home = str(tmp_path / "home")
|
||||
os.makedirs(home, exist_ok=True)
|
||||
|
||||
adapter = get_adapter('codex')
|
||||
ctx = DiscoveryContext(workspace=str(symlink_ws), agent_name='codex', home_dir=home)
|
||||
|
||||
uuid = "sym-1234-1234-1234-123456789abc"
|
||||
_seed_codex_session(home, uuid, str(real_ws), 2000.0)
|
||||
|
||||
candidates = adapter.discover(ctx)
|
||||
assert uuid in candidates
|
||||
@@ -1092,8 +1092,156 @@ def test_orc_onboard_codex_flag_tolerant_regex():
|
||||
assert res.stdout.strip() == "12345678-1234-1234-1234-123456789abc"
|
||||
|
||||
|
||||
def test_stop_session_codex_sends_c_c_chord_not_text_prompt(mam_sandbox, mock_herdr, mock_agents):
|
||||
"""R1: Verify graceful stop sends C-c key chord rather than text prompt for codex."""
|
||||
import subprocess, json
|
||||
# 1. Create a codex session
|
||||
create_script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh"
|
||||
res = subprocess.run([
|
||||
"bash", str(create_script),
|
||||
"--workspace", str(mam_sandbox),
|
||||
"--agent", "codex",
|
||||
"--role", "creator"
|
||||
], capture_output=True, text=True, cwd=str(mam_sandbox))
|
||||
assert res.returncode == 0, res.stderr
|
||||
|
||||
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
||||
import yaml
|
||||
with open(yaml_path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
session_name = data["herdr_sessions"][0]["name"]
|
||||
|
||||
# 2. Stop the session
|
||||
stop_script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-stop" / "scripts" / "stop_session.sh"
|
||||
res_stop = subprocess.run([
|
||||
"bash", str(stop_script),
|
||||
"--session", session_name,
|
||||
"--agent", "codex"
|
||||
], capture_output=True, text=True, cwd=str(mam_sandbox))
|
||||
assert res_stop.returncode == 0, res_stop.stderr
|
||||
|
||||
# 3. Verify mock_herdr recorded C-c send-keys call and did NOT record Ctrl+C prompt
|
||||
with open(mock_herdr) as f:
|
||||
state = json.load(f)
|
||||
calls = state.get("calls", [])
|
||||
pane_send_keys = [c for c in calls if "send-keys" in c and "C-c" in c]
|
||||
agent_prompts = [c for c in calls if "prompt" in c and any("Ctrl+C" in str(arg) for arg in c)]
|
||||
assert len(pane_send_keys) >= 1
|
||||
assert len(agent_prompts) == 0
|
||||
|
||||
|
||||
def test_status_resume_on_disk_codex_stale_and_valid(mam_sandbox):
|
||||
"""R5: Verify status.sh resume_on_disk reports MISSING for stale DB rows and yes for existing files."""
|
||||
import sqlite3, json, subprocess
|
||||
codex_home = mam_sandbox / ".codex"
|
||||
codex_home.mkdir(parents=True, exist_ok=True)
|
||||
db_path = codex_home / "state_5.sqlite"
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
conn.execute("CREATE TABLE threads (id TEXT PRIMARY KEY, cwd TEXT, rollout_path TEXT)")
|
||||
|
||||
# 1. Stale row: rollout_path does not exist
|
||||
stale_uuid = "stale-1234-1234-1234-123456789abc"
|
||||
conn.execute("INSERT INTO threads (id, cwd, rollout_path) VALUES (?, ?, ?)",
|
||||
(stale_uuid, str(mam_sandbox), str(codex_home / "nonexistent.jsonl")))
|
||||
|
||||
# 2. Valid row: rollout_path exists
|
||||
valid_uuid = "valid-1234-1234-1234-123456789abc"
|
||||
valid_file = codex_home / f"rollout-{valid_uuid}.jsonl"
|
||||
valid_file.write_text(json.dumps({"payload": {"id": valid_uuid, "cwd": str(mam_sandbox)}}) + "\n")
|
||||
conn.execute("INSERT INTO threads (id, cwd, rollout_path) VALUES (?, ?, ?)",
|
||||
(valid_uuid, str(mam_sandbox), str(valid_file)))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
||||
yaml_path.write_text(f"""herdr_sessions:
|
||||
- name: test-stale-codex
|
||||
status: running
|
||||
codex_session_id_own: {stale_uuid}
|
||||
pane:
|
||||
cwd: {mam_sandbox}
|
||||
- name: test-valid-codex
|
||||
status: running
|
||||
codex_session_id_own: {valid_uuid}
|
||||
pane:
|
||||
cwd: {mam_sandbox}
|
||||
""")
|
||||
script_path = mam_sandbox / "skills" / "multi-agent-mux-status" / "scripts" / "status.sh"
|
||||
env = {**os.environ, "CODEX_HOME": str(codex_home)}
|
||||
res = subprocess.run(["bash", str(script_path), "--json"], capture_output=True, text=True, env=env)
|
||||
assert res.returncode == 0
|
||||
data = json.loads(res.stdout)
|
||||
details = {d["name"]: d["resume_state"] for d in data["sessions_detail"]}
|
||||
assert details["test-stale-codex"] == "MISSING"
|
||||
assert details["test-valid-codex"] == "yes"
|
||||
|
||||
|
||||
def test_reconcile_drift_b_unmanaged_session_ignored(mam_sandbox, mock_herdr):
|
||||
"""R6 / T2: Verify drift-B ignores unmanaged in-workspace sessions while registering managed positive control."""
|
||||
import subprocess, json, yaml
|
||||
reconcile_script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
|
||||
assert reconcile_script.exists()
|
||||
|
||||
# Create dummy yaml with no sessions
|
||||
yaml_path = mam_sandbox / ".mam" / "agent-sessions.yaml"
|
||||
yaml_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
yaml_path.write_text("herdr_sessions: []\n")
|
||||
|
||||
unmanaged_name = "personal-session"
|
||||
managed_name = "test-proj-creator-codex"
|
||||
|
||||
# Seed mock_herdr with an unmanaged session and a managed control session running codex in workspace
|
||||
state = {
|
||||
"workspaces": [{"workspace_id": "w1", "label": "default", "cwd": str(mam_sandbox)}],
|
||||
"agents": {
|
||||
unmanaged_name: {
|
||||
"name": unmanaged_name,
|
||||
"status": "running",
|
||||
"cwd": str(mam_sandbox),
|
||||
"command": "codex",
|
||||
"pid": 12345
|
||||
},
|
||||
managed_name: {
|
||||
"name": managed_name,
|
||||
"status": "running",
|
||||
"cwd": str(mam_sandbox),
|
||||
"command": "codex",
|
||||
"pid": 54321
|
||||
}
|
||||
},
|
||||
"calls": []
|
||||
}
|
||||
with open(mock_herdr, "w") as f:
|
||||
json.dump(state, f)
|
||||
|
||||
# Assert candidate is visible to listing and pane metadata lookup before running reconcile
|
||||
r_ls = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"], capture_output=True, text=True)
|
||||
assert r_ls.returncode == 0
|
||||
assert unmanaged_name in r_ls.stdout
|
||||
assert managed_name in r_ls.stdout
|
||||
|
||||
r_pm = subprocess.run(["herdr", "list-panes", "-t", unmanaged_name, "-F", "#{pane_pid}|#{pane_current_path}|#{pane_current_command}"], capture_output=True, text=True)
|
||||
assert r_pm.returncode == 0
|
||||
assert f"12345|{str(mam_sandbox)}|codex" in r_pm.stdout
|
||||
|
||||
res = subprocess.run(["bash", str(reconcile_script), "--once", "--emit-diff"],
|
||||
capture_output=True, text=True, cwd=str(mam_sandbox),
|
||||
env={**os.environ, "WORKSPACE_ROOT": str(mam_sandbox)})
|
||||
assert res.returncode == 0, res.stderr
|
||||
|
||||
out = json.loads(res.stdout)
|
||||
drifts = out.get("drifts", [])
|
||||
|
||||
# Personal session must NOT be registered as drift B
|
||||
assert not any(d.get("name") == unmanaged_name for d in drifts)
|
||||
|
||||
# Managed positive control MUST be registered as drift B
|
||||
b_registered = [d.get("name") for d in drifts if d.get("class") == "B"]
|
||||
assert managed_name in b_registered
|
||||
|
||||
# Verify YAML updated with managed session but without unmanaged session
|
||||
with open(yaml_path) as f:
|
||||
data = yaml.safe_load(f)
|
||||
registered_names = [s.get("name") for s in data.get("herdr_sessions", [])]
|
||||
assert unmanaged_name not in registered_names
|
||||
assert managed_name in registered_names
|
||||
@@ -1252,6 +1252,8 @@ def test_comp_opencode_create_and_reconcile_and_stop(mam_sandbox, mock_herdr, mo
|
||||
# Run reconcile.sh to trigger drift-C materialization
|
||||
reconcile_script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
|
||||
res_rec = subprocess.run(["bash", str(reconcile_script), "--once", "--emit-diff"], capture_output=True, text=True, cwd=str(mam_sandbox), env={**os.environ, "WORKSPACE_ROOT": str(mam_sandbox)})
|
||||
print("RECONCILE STDOUT:\n", res_rec.stdout)
|
||||
print("RECONCILE STDERR:\n", res_rec.stderr)
|
||||
assert res_rec.returncode == 0, res_rec.stderr
|
||||
|
||||
with open(yaml_path) as f:
|
||||
|
||||
Reference in new issue
Block a user