Commit Graph
7 Commits
Author SHA1 Message Date
GodopuandClaude Opus 5.5 8b47cc76ee test(issue-17): every test that sources lib.sh does it under its own WORKSPACE_ROOT (review grok, codex)
d198dd5 fixed one helper; the same defect was in eleven more modules
(reviews grok and codex R1/R2): their shells inherited the process
environment, so each `source lib.sh` rewrote <WORKSPACE_ROOT>/.mam/shim/herdr
- the checkout's own when the variable is unset or names this checkout (which
pytest_configure keeps), another workspace's under --noconftest.

- conftest: own_workspace_root, an opt-in fixture setting WORKSPACE_ROOT to
  <tmp_path>/.mam-test-state/workspace. Not autouse: runner.py and
  mqtt_common read WORKSPACE_ROOT in-process, and some tests unset it on
  purpose. mam_sandbox and explicit envs run later and still win.
- pytestmark usefixtures("own_workspace_root") in test_version_consistency,
  test_b19_headless_reconcile_fixes, test_b8_send_keys_verification,
  test_issue6_fail_closed, test_loop_cli, test_issue5_phase3_loop,
  test_issue5_phase4_runtime, test_o3_scoped_guard, test_issue25_plan_reuse,
  test_orc_onboard (their shells inherit os.environ, many with no env at all).
- test_orc_onboard (R2): SCRUBBED_ENV is taken at import and no longer
  carries WORKSPACE_ROOT; every call builds _scrubbed_env(root) with the root
  of the call (ws / cwd / mam_sandbox, else this test's).
- test_workspace_scope (unittest) and test_issue21_worktree_prereqs._mutate
  pin the root they already create.

Tests (test_issue17_workspace_root_isolation.py): W1 copies the checkout and
runs one test of each of those modules there with WORKSPACE_ROOT naming the
copy - the copy gets no .mam; W2 imports test_orc_onboard under a sentinel
root, then calls it under another - the sentinel stays empty. At d198dd5 both
fail (the copy gets .mam/shim/herdr; the sentinel gets it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
2026-10-09 00:22:54 +09:00
GodopuandClaude Opus 5 4ad622e372 test(issue-21): rec 3 - T2-6 isolates MAM_LOOP_WORKTREE/MAM_LOOP_BASE with monkeypatch
T2-6 set both variables in os.environ directly and restored them in a
finally block; a failing assertion between the two could still leak them to
later tests in the process. They are now set with monkeypatch.setenv (the
wt_repo fixture already delenvs both). The assertions are unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 20:54:53 +00:00
GodopuandClaude Opus 5 4a81d15d95 test(issue-21): review N2 - T2-8 runs in mam_sandbox, so lib.sh's herdr shim never lands in the caller's control root
run_loop.sh sources lib.sh before it refuses --worktree/--base/--review-only
under MAM_LOOP_PY=0, and sourcing lib.sh runs _init_herdr_isolation, which
rewrites $WORKSPACE_ROOT/.mam/shim/herdr. T2-8 copied the caller's
environment as is, so that root was the caller's control root (normally the
real checkout). The test now requests mam_sandbox, pins WORKSPACE_ROOT to it
explicitly, runs from it, and asserts the shim was written there; the four
parametrized refusal cases and their three assertions are unchanged. No
product code changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 08:09:29 +00:00
GodopuandClaude Opus 5 af8a54c028 fix(issue-21): review R1-R3 - no env as agent arguments on a reused pane, one jobs dir for a relative MAM_JOBS_DIR, stop keeps the audit-log root
Review 9a1adb46 (planner-reviewer-codex-01):

- R1 [P1] lib.sh new-session: a forwarded binding reached the agent as
  arguments. With env flags the idle-pane branch prefixed `env K=V ...` to
  the command that `herdr agent start ... --kind <agent> -- <args>` passes to
  the agent executable. An idle pane is now reused only when no environment
  has to be set; otherwise the pane is created by pane split / workspace
  create, which take --env. The dead `env` prefix code is removed (its
  send-text fallback would have typed the same line).
- R2 [P2] runner: a relative MAM_JOBS_DIR is resolved once against the
  control root, the way the wrapper (cwd=workspace_root) reads it, and that
  absolute dir is used for delegation, wait, report lookup and cleanup.
- R3 [P2] stop: the row's control_plane.delegate_job_logs_dir is restored
  (exported unless the caller set DELEGATE_JOB_LOGS_DIR), so the
  cancellation history lands in the row's audit-log root.

Tests: T3-1 and T3-3 now assert that the binding arrives as --env on the
pane-creation calls and that no env/K=V token follows `--` in agent start
(fresh and reused pane); T3-4c (audit root on stop) and T2-15 (relative
MAM_JOBS_DIR) are new. The design doc's compatibility table and a create
comment no longer describe the removed idle-pane env path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 05:00:02 +00:00
GodopuandClaude Opus 5 ee1d8966c1 feat(issue-21): persist and forward the control-plane binding in create/resume/stop; status and reconcile honor MAM_JOBS_DIR
A session row now records the control plane it belongs to, and the pane
gets it explicitly instead of relying on env inheritance.

- lib_py/control_plane.py (+ thin lib.sh wrappers mam_control_plane_json /
  mam_control_plane_prefix): the binding is WORKSPACE_ROOT,
  AGENT_SESSIONS_YAML, MAM_JOBS_DIR, DELEGATE_JOB_LOGS_DIR as absolute paths.
- create: the binding is always persisted on the row (`control_plane`) and
  forwarded to the pane only when it differs from what --workspace alone
  implies, as leading K=V tokens that lib.sh's new-session already turns into
  --env (fresh pane) or `env K=V` (reused idle pane). pane.cmd_full and
  start_command stay unprefixed. A binding with whitespace that would have to
  be forwarded is refused with exit 2 (lib.sh splits the flags unquoted); the
  ~/.local/bin wrapper path records the binding but warns it is not forwarded.
- resume: the row's binding wins over the caller's env (a difference is
  reported on stderr) and is forwarded again; a legacy row without one uses
  the caller's binding and update_yaml_resumed.sh records it.
- stop: registry order MAM_JOBS_DIR > the row's mam_jobs_dir >
  $WORKSPACE_ROOT/.mam/jobs > .mam/jobs, exported for delegate_publish_event.
- status: both get_job_status copies look in MAM_JOBS_DIR, the row's
  registry and the matching audit logs before the old candidates.
- reconcile: MAM_JOBS_DIR wins in the subscribe heredoc and is passed in.

Tests T3-0..T3-8 (mock herdr: forwarding is verified at the herdr-call level
only; real pane env propagation is left to item 7). T3-7 is a static guard.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 03:34:48 +00:00
GodopuandClaude Opus 5 d907359746 feat(issue-21): loop runner --worktree/--base/--review-only with a fail-closed empty change-set guard; control plane stays in the main checkout
- --worktree <path> (CLI over MAM_LOOP_WORKTREE): the source root. Only the
  diff, base, head and the empty-set check use it; the wrapper, registry,
  checkpoint, loop guard, promotion and cleanup stay on the control root. The
  collector is the control root's diff_collect.sh (never the worktree's
  unreviewed copy), its arguments shlex-quoted. Validation: an existing
  directory, the top of its working tree, the same git common dir as the
  control root (".git" is resolved against the directory it came from).
- --base <ref> (CLI only, no env - a stale sha would silently widen the next
  range): resolved to a full sha, must be an ancestor of the source HEAD.
- --review-only: requires --base, refuses --plan; Phase 2 is skipped and
  logged; correction and comply jobs still go to the creator.
- With --worktree or --base every delegated prompt starts with a context
  block (path, branch, base and its origin, head, base..head commit count,
  uncommitted changes); without them prompts are byte-identical. All call
  sites in run() go through one _dispatch helper.
- Fail-closed guard (only with --worktree/--base): before each review round,
  self-review included, an empty change set or one git cannot determine
  (exit codes, never output parsing) ends the loop with rc 1 instead of
  asking for reviews. This closes the fail-open where a loop started after
  an out-of-loop commit sent reviewers "(no changes since base commit)".
- The runner honours MAM_JOBS_DIR and passes --registry-dir to the wrapper.
- run_loop.sh accepts the three options; the legacy shell loop
  (MAM_LOOP_PY=0) refuses them before the freeze and the loop lock.

Tests T2-1..T2-14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 03:21:43 +00:00
GodopuandClaude Opus 5 d3d3a76c83 fix(issue-21): absolute brief, start-command registry, audit-log root and report paths in the delegate-job wrapper
The injected prompt, the start/success/error commands and the brief's Output
Report Path used cwd-relative `.mam/jobs/...`, and the wrapper ignored
MAM_JOBS_DIR. A creator whose cwd is a git worktree could not run them as
written, and its audit log fell back to <cwd>/.mam/delegate_job_logs.

- REGISTRY_DIR defaults to ${MAM_JOBS_DIR:-.mam/jobs} and is made absolute
  right after `cd "$WORKDIR"` (a relative value still resolves against WORKDIR)
- the publish command is shell-quoted (printf %q) and prefixed with
  DELEGATE_JOB_LOGS_DIR=<abs>, resolved by the same get_logs_dir() the
  wrapper's subscriber uses; the heartbeat timer inherits it
- direct and loop jobs share one _instructions helper; brief and report paths
  are $REGISTRY_DIR/$JOB_ID/... (absolute)
- create --onboard: absolute publish_event.py, --registry-dir and log root

Tests T1-1..T1-5 (a space in the control root, commands run from another cwd
with no workspace env).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
2026-09-14 03:20:27 +00:00