- Replace import-time LOGS_DIR cwd binding with dynamic get_logs_dir() function - Implement PEP 562 __getattr__ and __dir__ for transparent LOGS_DIR backward compatibility - Update audit-log callers in mqtt_common.py and registry.py to use dynamic resolution - Add 5 regression guards in tests/test_tier1_unit.py (276/276 PASS) - Update IMPROVEMENTS.md, VERSIONS.md, registry.md, and include plan and peer review reports
7.6 KiB
Cross-Code Review Report: B-9 (LOGS_DIR import-time cwd freeze fix)
- Job ID: c35385ad
- Reviewer: cline
- Date: 2026-08-17
- Backlog Item: B-9 (P4-1) —
LOGS_DIRimport-time cwd freeze resolution - Changed Files:
mqtt_common.py,registry.py,registry.md,tests/test_tier1_unit.py,IMPROVEMENTS.md,VERSIONS.md
1. Objective
Verify that the B-9 implementation correctly refactors mqtt_common.py and registry.py to resolve the audit-log root (LOGS_DIR) dynamically at call time via get_logs_dir(), eliminating the import-time os.getcwd() freeze that caused audit-log path drift after chdir. Backward compatibility for mqtt_common.LOGS_DIR consumers must be preserved, 5 dedicated regression tests must be added, and documentation must be accurate.
2. Implementation Review
2.1 mqtt_common.py — Core Fix
Before:
def _default_logs_dir() -> str: ...
LOGS_DIR = _default_logs_dir() # frozen at import time
After:
def get_logs_dir() -> str:
"""Audit-log root, resolved at call time (B-9). ..."""
env = os.environ.get("DELEGATE_JOB_LOGS_DIR")
if env and env.strip():
return env
return os.path.join(os.getcwd(), ".mam", "delegate_job_logs")
def __getattr__(name: str): # PEP 562 (3.7+)
if name == "LOGS_DIR":
return get_logs_dir()
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
def __dir__():
return sorted(set(globals()) | {"LOGS_DIR"})
Assessment:
- The module-level
LOGS_DIR = _default_logs_dir()assignment is removed — verified by AST guard test and manual grep (0 hits at module scope). get_logs_dir()is now public (renamed from_default_logs_dir), resolving the path per call.- PEP 562
__getattr__provides backward-compatiblemqtt_common.LOGS_DIRaccess, resolving dynamically each time. - PEP 562
__dir__keepsLOGS_DIRdiscoverable indir()and tab-completion. __getattr__correctly raisesAttributeErrorfor unknown attributes (prevents infinite recursion inhasattr).
Internal callers updated (all resolve through get_logs_dir() when logs_dir=None):
| Function | Line | Pattern |
|---|---|---|
job_log_dir |
441 | Path(logs_dir or get_logs_dir()) / job_id |
job_log_path |
444 | delegates to job_log_dir |
append_event |
488 | delegates to job_log_path |
init_job_log |
523 | delegates to job_log_dir |
update_logged_status |
505 | delegates to job_log_path |
read_logged_meta |
553 | delegates to job_log_path |
read_logged_status |
561 | delegates to job_log_path |
iter_logged_events |
572 | delegates to job_log_path |
list_logged_jobs |
589 | Path(logs_dir or get_logs_dir()) |
All 9 audit-log functions chain through get_logs_dir() when no explicit logs_dir is passed. No stale or LOGS_DIR (bare global) references remain.
2.2 registry.py — Consumer Updates
Two references updated from mqtt_common.LOGS_DIR to mqtt_common.get_logs_dir():
- Line 198 (
get_feedback):logs_dir = mqtt_common.get_logs_dir()✓ - Line 389 (
_cmd_logs):logs_dir = args.logs_dir or mqtt_common.get_logs_dir()✓
2.3 registry.md — Documentation
Helper list updated to describe get_logs_dir as the primary API with LOGS_DIR noted as a "dynamic compat alias". Accurate and consistent with the implementation.
2.4 Backward Compatibility
from mqtt_common import LOGS_DIR— 0 occurrences in the entire repository (verified by grep). Compat surface 100% covered by__getattr__.mqtt_common.LOGS_DIRattribute access — preserved via PEP 562__getattr__, resolves dynamically.DELEGATE_JOB_LOGS_DIRenv override — now reflected at call time (bonus improvement, not a regression).
2.5 Test Suite (tests/test_tier1_unit.py)
5 new B-9 regression tests (lines 406–473):
| Test | Guard Type | What It Verifies |
|---|---|---|
test_b9_logs_dir_follows_cwd_changes |
T1 (dynamic) | get_logs_dir() and LOGS_DIR compat alias follow chdir |
test_b9_audit_log_lands_under_the_current_cwd |
T3 (file creation) | Actual meta.json file appears under current cwd (catches swallowed errors) |
test_b9_logs_dir_env_override_is_dynamic |
Env dynamic | DELEGATE_JOB_LOGS_DIR honored at call time; clearing restores cwd default |
test_b9_no_module_level_logs_dir_binding |
T1 (AST static) | No module-level LOGS_DIR assignment (covers Assign and AnnAssign) |
test_b9_logs_dir_stays_discoverable |
C2-a (PEP 562) | LOGS_DIR in dir(), hasattr works, no duplicates |
The T3 guard is particularly well-designed — it asserts the actual file appears on disk, not just string equality. This is critical because the audit-log layer uses best-effort except Exception that swallows errors silently.
2.6 Documentation (IMPROVEMENTS.md, VERSIONS.md)
IMPROVEMENTS.md: Header updated (date, 276/276, 24 completed, 1 open). B-9 moved to completed section (lines 84–90). Edge-case section shows "0건 — 전원 완료". ✓
3. Verification Results
3.1 Syntax Checks (py_compile)
| File | Result |
|---|---|
mqtt_common.py |
✅ PASS |
registry.py |
✅ PASS |
tests/test_tier1_unit.py |
✅ PASS |
3.2 Stale Reference Scan
| Check | Result |
|---|---|
from mqtt_common import LOGS_DIR in source |
✅ 0 occurrences |
Bare or LOGS_DIR (global) in source |
✅ 0 occurrences |
Module-level LOGS_DIR = assignment |
✅ 0 occurrences (removed) |
3.3 B-9 Targeted Tests
tests/test_tier1_unit.py::test_b9_logs_dir_follows_cwd_changes PASSED [ 20%]
tests/test_tier1_unit.py::test_b9_audit_log_lands_under_the_current_cwd PASSED [ 40%]
tests/test_tier1_unit.py::test_b9_logs_dir_env_override_is_dynamic PASSED [ 60%]
tests/test_tier1_unit.py::test_b9_no_module_level_logs_dir_binding PASSED [ 80%]
tests/test_tier1_unit.py::test_b9_logs_dir_stays_discoverable PASSED [100%]
5 passed, 30 deselected in 0.10s
3.4 Full Test Suite
276 passed in 422.40s (0:07:02)
Zero failures, zero errors, zero regressions. Test count increased from 271 → 276 (+5 new B-9 tests), consistent with documentation claims.
4. Lint & Quality Assessment
- No unused imports introduced by the change.
- No dead code —
__getattr__and__dir__are both exercised by tests. - PEP 562 is the idiomatic Python ≥3.7 pattern for dynamic module attributes; test environment runs Python 3.9.6.
- Thread safety:
get_logs_dir()callsos.environ.get()andos.getcwd(), both thread-safe in CPython. Per-call overhead is negligible vs. the file I/O it precedes. - No surgical-change violations: every changed line traces directly to the B-9 requirement.
5. Concerns & Observations
- Minor (non-blocking):
__dir__returnssorted(set(globals()) | {"LOGS_DIR"})—LOGS_DIRwould still appear indir()even if__getattr__were removed. Purely cosmetic; the AST guard test catches actual binding regressions. - No escalation needed: The fix is a clean, surgical refactor. No design-level rework required.
6. Verdict
The B-9 implementation is correct, complete, and well-tested:
- The import-time cwd freeze is eliminated —
get_logs_dir()resolves per call. - Backward compatibility is fully preserved via PEP 562
__getattr__/__dir__. - All internal callers and external consumers (
registry.py) are updated. - 5 high-quality regression tests guard against regression (AST static guard + file-creation guard).
- Documentation (
IMPROVEMENTS.md,VERSIONS.md,registry.md) is accurate. - Full test suite: 276/276 PASS, zero regressions.
[VERDICT: PASS]