# Review Report — PR #58 (Issue #57: Audit logs follow a moved registry) - **Reviewer**: `agy` (`herdr:reviewer-agy-01`) - **Job ID**: `d61d1344` - **Branch / Range**: `fix/issue-57` (`e32c9574644a9185813a4dc881812694d62b6204..63d816468e6d48d01bd96f31d8a25eeadfbaab60`, 11 commits, 21 files) - **Verification Worktree**: `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261014T152849Z-f1a415/1` - **Platform**: macOS Darwin (`arm64-apple-darwin25`, `/usr/bin/python3` 3.9.6, `.venv/bin/python` 3.9.6, `/bin/bash` 3.2.57) --- ## 1. Executive Summary PR #58 resolves Issue #57 by establishing a single source of truth (`mqtt_common.get_logs_dir(registry_dir)` and `mqtt_common.sibling_logs_dir(registry_dir)`) so that moving the job registry via `--registry-dir` or `MAM_JOBS_DIR` moves its sibling `delegate_job_logs` audit trail as well, unless `DELEGATE_JOB_LOGS_DIR` is explicitly set. It also fixes the `job_subscriber._check_disk_fallback` bug where the `status.json` dict was compared directly against status strings instead of extracting `.get("status")`, while guarding versioned records against stale audit statuses from prior attempts via `transition_seq` comparison. Furthermore, per §2-B (`c57e766`), `control_plane.forwarded_keys()` omits `delegate_job_logs_dir` from pane `--env` forwarding whenever it is the derivable sibling of `mam_jobs_dir`, allowing `--registry-dir` inside a forwarded worktree pane to relocate both the registry and its audit log cleanly. All 21 modified files pass Python 3.9.6 AST/syntax checks and Bash 3.2 (`set -u`) syntax checks, and all 135 targeted tests across 5 test suites pass on macOS without leaving any artifacts in the verification worktree or real control plane. --- ## 2. macOS / Python 3.9 / Bash 3.2 Compatibility Verification 1. **Python 3.9 Compatibility (`/usr/bin/python3` 3.9.6)**: - All 13 modified/added Python files (`control_plane.py`, `engine.py`, `job_subscriber.py`, `mqtt_common.py`, `publish_event.py`, `registry.py`, `remote.py`, `runner.py`, and the 5 test modules) parse cleanly under `/usr/bin/python3` 3.9.6 (`ast.parse`). - No Python 3.10+ runtime union syntax (`X | Y`), `match`/`case`, `tomllib`, or 3.11-only stdlib APIs are used without `from __future__ import annotations`. 2. **Bash 3.2 + `set -u` Safety (`/bin/bash` 3.2.57)**: - All 5 modified shell scripts (`lib.sh`, `worktree_prepare.sh`, `multi-agent-mux-delegate-job`, `reconcile.sh`, `status.sh`) pass `/bin/bash -n`. - In `multi-agent-mux-delegate-job`: - `LOGS_DIR_ABS=""` is initialized at the top level before functions run under `set -euo pipefail`, and `cmd_submit` resolves it fail-closed (`LOGS_DIR_ABS="$(_logs_dir_abs)" || { ... exit 1; }`) and exports `DELEGATE_JOB_LOGS_DIR="$LOGS_DIR_ABS"`. - `cmd_logs` uses scalar locals (`local reg="$REGISTRY_DIR_DEFAULT" jid="" list=0`) instead of potentially empty arrays, avoiding Bash 3.2's unbound-variable failure on empty array expansion under `set -u`. - In `.agents/skills/lib.sh` and `worktree_prepare.sh`: - `${DELEGATE_JOB_LOGS_DIR:-}` and `${MAM_JOBS_DIR:+$(dirname "$MAM_JOBS_DIR")/delegate_job_logs}` are safely guarded for `set -u`. 3. **macOS Symlink Normalization (`/tmp` -> `/private/tmp`, `/var` -> `/private/var`)**: - `control_plane._abs(p)` normalizes paths via `os.path.realpath(os.path.expanduser(p))`. - Both `from_env()` (`b["delegate_job_logs_dir"] = _abs(mqtt_common.sibling_logs_dir(b["mam_jobs_dir"]))`) and `forwarded_keys()` (`desc.get("delegate_job_logs_dir") == _abs(mqtt_common.sibling_logs_dir(desc["mam_jobs_dir"]))`) apply `_abs()`, ensuring symlinked macOS temporary directories (`tmp_path` under `/var/folders/...` vs `/private/var/folders/...`) compare equal. - `tests/test_issue57_logs_follow_registry.py` consistently uses `os.path.realpath` when comparing paths resolved by `_logs_dir_abs` or `control_plane`. --- ## 3. Functional & Architectural Review - **Resolver & Single Source of Truth (`mqtt_common.py`)**: - `sibling_logs_dir(registry_dir)` purely computes `os.path.join(os.path.dirname(os.path.abspath(registry_dir)), "delegate_job_logs")`. - `get_logs_dir(registry_dir=None)` honors `DELEGATE_JOB_LOGS_DIR` when non-empty, and otherwise delegates to `sibling_logs_dir(registry_dir or default_registry_dir())`. - PEP 562 module `__getattr__("LOGS_DIR")` remains intact for backwards compatibility. - **Engine, Registry, Subscriber, Publisher, Remote, Runner**: - `engine.py` threads `registry_dir` into `_flush(notes, registry_dir)` and `_append_conflict_refused(..., registry_dir)`, ensuring conflict refusals and post-commit audit mirrors write next to the active registry. - `registry.py` threads `logs = mqtt_common.get_logs_dir(registry_dir)` through `register_job`, legacy `pick_pending` (`MAM_ENGINE_CAS=0`), `get_feedback`, and CLI `logs`. - `job_subscriber._check_disk_fallback` reads `logged = mqtt_common.read_logged_status(jid, mqtt_common.get_logs_dir(registry_dir))`, checks `stale = rec_seq is not None and (aud_seq is None or int(aud_seq) < int(rec_seq))` (catching `(TypeError, ValueError)` as `stale = True`), and extracts `disk_status = logged.get("status")` only when not stale. - **Control Plane & Session Forwarding (`control_plane.py`, `worktree_prepare.sh`, `status.sh`)**: - `control_plane.from_env()` derives `delegate_job_logs_dir` from `mam_jobs_dir` when `DELEGATE_JOB_LOGS_DIR` is unset. - `control_plane.forwarded_keys()` omits `delegate_job_logs_dir` when it equals `_abs(mqtt_common.sibling_logs_dir(desc["mam_jobs_dir"]))`, so the row still records all 4 keys while the pane only receives `DELEGATE_JOB_LOGS_DIR` if it was explicitly set to a non-sibling location. - `worktree_prepare.sh` only forwards `DELEGATE_JOB_LOGS_DIR` into `run_cmd` when explicitly set in the caller's environment. - `status.sh` checks `sibling_logs_dir(r)` for both `MAM_JOBS_DIR` and `cp.get("mam_jobs_dir")`. --- ## 4. Test Execution Summary Executed in `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261014T152849Z-f1a415/1` with `HERDR_*`, `WORKSPACE_ROOT`, `MAM_ENV_FILE`, `MAM_JOBS_DIR`, and `DELEGATE_JOB_LOGS_DIR` scrubbed, `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`, and `--basetemp=/tmp/mam_verify_pr58_bt`: - `tests/test_issue57_logs_follow_registry.py`: **12 passed** - `tests/test_issue15_registry_isolation.py`: **20 passed** - `tests/test_issue21_worktree_prereqs.py`: **27 passed** - `tests/test_issue21_acceptance.py`: **9 passed** - `tests/test_tier1_unit.py`: **67 passed** Total: **135 passed, 0 failed**. Verification worktree `git status -s` is empty and no `.mam` directory was created. [VERDICT: PASS]