test(issue-57): the derivable audit-log root is recorded, not forwarded
New: prefix()/CLI forward three keys for a sibling binding and four for an explicit root; create records the sibling on the row, omits it from the pane's --env and still pins it in the injected publish commands; the issue's scene (a pane with WORKSPACE_ROOT and MAM_JOBS_DIR only) moves its audit log with --registry-dir and leaves the control root alone; worktree_prepare's pane --env carries no DELEGATE_JOB_LOGS_DIR. The three existing four-key forwarding assertions (user-approved) compare FORWARDED_ENV = CP_ENV[:3]; CP_ENV and the negative assertions are kept. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
c57e7667aa
commit
63d816468e
3 files changed
+101
-6
No files matched your search
@@ -21,7 +21,8 @@ import pytest
|
|||||||
from test_issue21_loop_b import LOCAL_ROWS, _drive, _reviews, _sidecars, _state
|
from test_issue21_loop_b import LOCAL_ROWS, _drive, _reviews, _sidecars, _state
|
||||||
from test_issue21_phase2_loop_a import (REM, _calls, _prepare, _resume, _rows, _run, _skill, _wt, # noqa: F401
|
from test_issue21_phase2_loop_a import (REM, _calls, _prepare, _resume, _rows, _run, _skill, _wt, # noqa: F401
|
||||||
ctl)
|
ctl)
|
||||||
from test_issue21_worktree_prereqs import (CP_ENV, WRAPPER, _assert_no_env_in_agent_args, _binding, _dry_submit,
|
from test_issue21_worktree_prereqs import (CP_ENV, FORWARDED_ENV, WRAPPER, _assert_no_env_in_agent_args, _binding, # noqa: F401
|
||||||
|
_dry_submit,
|
||||||
_env, _env_flags, _injected, _kill_timer, _only_job_id)
|
_env, _env_flags, _injected, _kill_timer, _only_job_id)
|
||||||
from test_issue21_worktree_prereqs import _git as _g
|
from test_issue21_worktree_prereqs import _git as _g
|
||||||
from test_issue21_worktree_prereqs import wt_repo # noqa: F401
|
from test_issue21_worktree_prereqs import wt_repo # noqa: F401
|
||||||
@@ -70,7 +71,7 @@ def test_a2_pane_env_carries_the_binding_not_the_agent_args(ctl, mock_herdr, moc
|
|||||||
main = ctl
|
main = ctl
|
||||||
wt, since, _ = _sibling(main, mock_herdr)
|
wt, since, _ = _sibling(main, mock_herdr)
|
||||||
flags = _env_flags(mock_herdr, since)
|
flags = _env_flags(mock_herdr, since)
|
||||||
for k, v in zip(CP_ENV, _binding(main).values()):
|
for k, v in zip(FORWARDED_ENV, list(_binding(main).values())[:3]):
|
||||||
assert f"{k}={v}" in flags, flags
|
assert f"{k}={v}" in flags, flags
|
||||||
spawns = [c for c in _calls(mock_herdr) if c[:2] in (["pane", "split"], ["workspace", "create"])]
|
spawns = [c for c in _calls(mock_herdr) if c[:2] in (["pane", "split"], ["workspace", "create"])]
|
||||||
assert spawns and any("--env" in c for c in spawns), "the binding rides on the pane-creating call"
|
assert spawns and any("--env" in c for c in spawns), "the binding rides on the pane-creating call"
|
||||||
|
|||||||
@@ -493,6 +493,8 @@ def test_t2_14_undeterminable_change_set_is_refused(wt_repo, monkeypatch, tmp_pa
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
CP_ENV = ("WORKSPACE_ROOT", "AGENT_SESSIONS_YAML", "MAM_JOBS_DIR", "DELEGATE_JOB_LOGS_DIR")
|
CP_ENV = ("WORKSPACE_ROOT", "AGENT_SESSIONS_YAML", "MAM_JOBS_DIR", "DELEGATE_JOB_LOGS_DIR")
|
||||||
|
# Issue #57: the derivable audit-log root is recorded on the row, not forwarded
|
||||||
|
FORWARDED_ENV = CP_ENV[:3]
|
||||||
|
|
||||||
|
|
||||||
def _binding(root: Path) -> dict:
|
def _binding(root: Path) -> dict:
|
||||||
@@ -576,7 +578,7 @@ def test_t3_1_create_persists_and_forwards_a_foreign_binding(mam_sandbox, mock_h
|
|||||||
(row,) = _rows(tmp)
|
(row,) = _rows(tmp)
|
||||||
assert row["control_plane"] == _binding(tmp)
|
assert row["control_plane"] == _binding(tmp)
|
||||||
flags = _env_flags(mock_herdr)
|
flags = _env_flags(mock_herdr)
|
||||||
for k, v in zip(CP_ENV, _binding(tmp).values()):
|
for k, v in zip(FORWARDED_ENV, list(_binding(tmp).values())[:3]):
|
||||||
assert f"{k}={v}" in flags, flags
|
assert f"{k}={v}" in flags, flags
|
||||||
assert "WORKSPACE_ROOT=" not in row["pane"]["cmd_full"]
|
assert "WORKSPACE_ROOT=" not in row["pane"]["cmd_full"]
|
||||||
assert "WORKSPACE_ROOT=" not in row["start_command"]
|
assert "WORKSPACE_ROOT=" not in row["start_command"]
|
||||||
@@ -625,7 +627,7 @@ def test_t3_3_4_stop_keeps_the_binding_and_resume_reforwards_it(mam_sandbox, moc
|
|||||||
# tokens to the agent as arguments, review R1): resume takes a new pane,
|
# tokens to the agent as arguments, review R1): resume takes a new pane,
|
||||||
# whose creation call carries the binding as --env.
|
# whose creation call carries the binding as --env.
|
||||||
flags = _env_flags(mock_herdr, since)
|
flags = _env_flags(mock_herdr, since)
|
||||||
for k, v in zip(CP_ENV, _binding(tmp).values()):
|
for k, v in zip(FORWARDED_ENV, list(_binding(tmp).values())[:3]):
|
||||||
assert f"{k}={v}" in flags, (res.stderr[-1500:], flags)
|
assert f"{k}={v}" in flags, (res.stderr[-1500:], flags)
|
||||||
_assert_no_env_in_agent_args(mock_herdr, since)
|
_assert_no_env_in_agent_args(mock_herdr, since)
|
||||||
(row,) = _rows(tmp)
|
(row,) = _rows(tmp)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from lib_py import control_plane # noqa: E402
|
|||||||
from lib_py.delegation import engine, job_subscriber, mqtt_common, registry # noqa: E402
|
from lib_py.delegation import engine, job_subscriber, mqtt_common, registry # noqa: E402
|
||||||
from test_issue21_phase2_loop_a import _mutate, _prepare, _rows, _skill, _wt # noqa: E402
|
from test_issue21_phase2_loop_a import _mutate, _prepare, _rows, _skill, _wt # noqa: E402
|
||||||
from test_issue21_phase2_loop_a import ctl # noqa: E402,F401
|
from test_issue21_phase2_loop_a import ctl # noqa: E402,F401
|
||||||
|
from test_issue21_worktree_prereqs import _env_flags, _sent_texts # noqa: E402
|
||||||
|
|
||||||
UNREACHABLE = {"MQTT_BROKER": "127.0.0.1", "MQTT_PORT": "9", "MQTT_TLS": "0"}
|
UNREACHABLE = {"MQTT_BROKER": "127.0.0.1", "MQTT_PORT": "9", "MQTT_TLS": "0"}
|
||||||
DROP = ("MAM_JOBS_DIR", "DELEGATE_JOB_LOGS_DIR")
|
DROP = ("MAM_JOBS_DIR", "DELEGATE_JOB_LOGS_DIR")
|
||||||
@@ -153,9 +154,9 @@ def test_registry_cli_writes_and_reads_next_to_the_registry(moved):
|
|||||||
# L3 — the wrapper's last line names the directory it wrote
|
# L3 — the wrapper's last line names the directory it wrote
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _wrapper(ws: Path, *args, **extra) -> subprocess.CompletedProcess:
|
def _wrapper(ws: Path, *args, cwd: Path = None, **extra) -> subprocess.CompletedProcess:
|
||||||
return subprocess.run(["bash", str(WRAPPER), *args], capture_output=True, text=True,
|
return subprocess.run(["bash", str(WRAPPER), *args], capture_output=True, text=True,
|
||||||
env=_env(ws, **extra), cwd=str(ws), timeout=120)
|
env=_env(ws, **extra), cwd=str(cwd or ws), timeout=120)
|
||||||
|
|
||||||
|
|
||||||
def _only_job_id(reg: Path) -> str:
|
def _only_job_id(reg: Path) -> str:
|
||||||
@@ -280,6 +281,10 @@ def test_worktree_prepare_keeps_the_logs_next_to_the_control_root_registry(ctl,
|
|||||||
jid = row["delegate_job_id"]
|
jid = row["delegate_job_id"]
|
||||||
assert (main / "elsewhere" / "delegate_job_logs" / jid / "status.json").is_file()
|
assert (main / "elsewhere" / "delegate_job_logs" / jid / "status.json").is_file()
|
||||||
assert not (main / ".mam" / "delegate_job_logs").exists()
|
assert not (main / ".mam" / "delegate_job_logs").exists()
|
||||||
|
# the pane carries the registry, not the audit-log root derived from it
|
||||||
|
flags = _env_flags(mock_herdr)
|
||||||
|
assert any(f == f"MAM_JOBS_DIR={os.path.realpath(reg)}" for f in flags), flags
|
||||||
|
assert not [f for f in flags if f.startswith("DELEGATE_JOB_LOGS_DIR=")], flags
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -380,3 +385,90 @@ def test_old_audit_terminal_does_not_complete_the_redispatched_attempt(moved, mo
|
|||||||
pending, terminal = {jid}, {}
|
pending, terminal = {jid}, {}
|
||||||
job_subscriber._check_disk_fallback(pending, str(reg), terminal, {jid: rec["dispatch_token"]}, {jid: 2})
|
job_subscriber._check_disk_fallback(pending, str(reg), terminal, {jid: rec["dispatch_token"]}, {jid: 2})
|
||||||
assert pending == set() and terminal == {jid: "completed"}
|
assert pending == set() and terminal == {jid: "completed"}
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# plan §2-B (user decision): the derivable audit-log root is recorded, not forwarded
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _cp_cli(ws: Path, *args, **extra) -> subprocess.CompletedProcess:
|
||||||
|
return subprocess.run([sys.executable, "-m", "lib_py.control_plane", *args], capture_output=True, text=True,
|
||||||
|
env=_env(ws, **extra), cwd=str(ws), timeout=60)
|
||||||
|
|
||||||
|
|
||||||
|
def test_prefix_forwards_only_what_cannot_be_derived(moved, monkeypatch, tmp_path):
|
||||||
|
ws, reg, logs = moved
|
||||||
|
monkeypatch.setenv("MAM_JOBS_DIR", str(reg))
|
||||||
|
d = control_plane.describe(str(ws / "wt"))
|
||||||
|
assert d["forward"] is True
|
||||||
|
assert d["delegate_job_logs_dir"] == os.path.realpath(logs), "the row still records the sibling"
|
||||||
|
p = control_plane.prefix(d)
|
||||||
|
assert "WORKSPACE_ROOT=" in p and "AGENT_SESSIONS_YAML=" in p and f"MAM_JOBS_DIR={os.path.realpath(reg)} " in p, p
|
||||||
|
assert "DELEGATE_JOB_LOGS_DIR=" not in p, p
|
||||||
|
# an audit-log root that cannot be derived is still forwarded
|
||||||
|
custom = tmp_path / "custom-logs"
|
||||||
|
monkeypatch.setenv("DELEGATE_JOB_LOGS_DIR", str(custom))
|
||||||
|
d2 = control_plane.describe(str(ws / "wt"))
|
||||||
|
assert d2["delegate_job_logs_dir"] == os.path.realpath(custom)
|
||||||
|
assert f"DELEGATE_JOB_LOGS_DIR={os.path.realpath(custom)} " in control_plane.prefix(d2)
|
||||||
|
# the CLI lib.sh calls agrees on both
|
||||||
|
monkeypatch.delenv("DELEGATE_JOB_LOGS_DIR")
|
||||||
|
res = _cp_cli(ws, "json", str(ws / "wt"), MAM_JOBS_DIR=str(reg))
|
||||||
|
assert res.returncode == 0, res.stderr
|
||||||
|
cli = _cp_cli(ws, "prefix", res.stdout.strip(), MAM_JOBS_DIR=str(reg))
|
||||||
|
assert cli.returncode == 0 and "MAM_JOBS_DIR=" in cli.stdout and "DELEGATE_JOB_LOGS_DIR=" not in cli.stdout, cli.stdout
|
||||||
|
res = _cp_cli(ws, "json", str(ws / "wt"), MAM_JOBS_DIR=str(reg), DELEGATE_JOB_LOGS_DIR=str(custom))
|
||||||
|
cli = _cp_cli(ws, "prefix", res.stdout.strip(), MAM_JOBS_DIR=str(reg), DELEGATE_JOB_LOGS_DIR=str(custom))
|
||||||
|
assert f"DELEGATE_JOB_LOGS_DIR={os.path.realpath(custom)} " in cli.stdout, cli.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_binds_the_sibling_and_does_not_forward_it(mam_sandbox, mock_herdr, mock_agents, monkeypatch):
|
||||||
|
tmp = mam_sandbox
|
||||||
|
reg, sibling = tmp / "elsewhere" / "jobs", tmp / "elsewhere" / "delegate_job_logs"
|
||||||
|
monkeypatch.setenv("MAM_JOBS_DIR", str(reg))
|
||||||
|
monkeypatch.delenv("DELEGATE_JOB_LOGS_DIR", raising=False)
|
||||||
|
wt = tmp / "wt"
|
||||||
|
wt.mkdir()
|
||||||
|
res = subprocess.run(["bash", _skill(tmp, "multi-agent-mux-create", "scripts", "create_session.sh"),
|
||||||
|
"--workspace", str(wt), "--agent", "claude", "--role", "creator", "--onboard"],
|
||||||
|
capture_output=True, text=True, cwd=str(tmp), timeout=180)
|
||||||
|
assert res.returncode == 0, res.stdout + res.stderr
|
||||||
|
(row,) = _rows(tmp)
|
||||||
|
assert row["control_plane"]["mam_jobs_dir"] == os.path.realpath(reg)
|
||||||
|
assert row["control_plane"]["delegate_job_logs_dir"] == os.path.realpath(sibling)
|
||||||
|
flags = _env_flags(mock_herdr)
|
||||||
|
assert any(f == f"MAM_JOBS_DIR={os.path.realpath(reg)}" for f in flags), flags
|
||||||
|
assert not [f for f in flags if f.startswith("DELEGATE_JOB_LOGS_DIR=")], flags
|
||||||
|
# the injected publish commands still pin the root per command
|
||||||
|
texts = [t for t in _sent_texts(json.loads(Path(mock_herdr).read_text())) if "publish_event.py" in t]
|
||||||
|
assert texts and f"DELEGATE_JOB_LOGS_DIR={os.path.realpath(sibling)} " in "".join(texts), texts
|
||||||
|
jid = row["delegate_job_id"]
|
||||||
|
assert (sibling / jid / "status.json").is_file()
|
||||||
|
assert not (tmp / ".mam" / "delegate_job_logs").exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_forwarded_pane_moves_its_logs_with_the_registry(tmp_path, monkeypatch):
|
||||||
|
"""The issue's scene: a worktree creator pane after §2-B carries the control
|
||||||
|
root's WORKSPACE_ROOT and MAM_JOBS_DIR but no DELEGATE_JOB_LOGS_DIR."""
|
||||||
|
for k in DROP:
|
||||||
|
monkeypatch.delenv(k, raising=False)
|
||||||
|
main, wt, exp = tmp_path / "main", tmp_path / "wt", tmp_path / "exp" / "jobs"
|
||||||
|
real_logs = main / ".mam" / "delegate_job_logs"
|
||||||
|
real_logs.mkdir(parents=True)
|
||||||
|
wt.mkdir()
|
||||||
|
pane = dict(WORKSPACE_ROOT=str(main), MAM_JOBS_DIR=str(main / ".mam" / "jobs"))
|
||||||
|
res = _wrapper(main, "submit", "--dry-run", "--agent", "human", "--registry-dir", str(exp),
|
||||||
|
"--workdir", str(wt), "--prompt", "t", cwd=wt, **pane)
|
||||||
|
assert res.returncode == 0, res.stdout[-800:] + res.stderr[-800:]
|
||||||
|
jid = _only_job_id(exp)
|
||||||
|
assert res.stdout.strip().splitlines()[-1] == str(Path(os.path.realpath(tmp_path / "exp")) / "delegate_job_logs" / jid)
|
||||||
|
assert (tmp_path / "exp" / "delegate_job_logs" / jid / "meta.json").is_file()
|
||||||
|
assert os.listdir(real_logs) == [], "the experiment leaked into the control root"
|
||||||
|
# the usual run in that pane is unchanged: the default registry's sibling
|
||||||
|
res = _wrapper(main, "submit", "--dry-run", "--agent", "human", "--workdir", str(wt), "--prompt", "t",
|
||||||
|
cwd=wt, **pane)
|
||||||
|
assert res.returncode == 0, res.stdout[-800:] + res.stderr[-800:]
|
||||||
|
jid2 = _only_job_id(main / ".mam" / "jobs")
|
||||||
|
assert res.stdout.strip().splitlines()[-1] == str(Path(os.path.realpath(main)) / ".mam" / "delegate_job_logs" / jid2)
|
||||||
|
assert os.listdir(real_logs) == [jid2]
|
||||||
|
assert not (wt / ".mam" / "delegate_job_logs").exists()
|
||||||
Reference in new issue
Block a user