feat(loop): complete Issue #5 Phase 4 immutable runtime and freeze removal
- Implement eager module loading (lib_py.loop.eager) to preload all framework dependencies into sys.modules and enforce PYTHONDONTWRITEBYTECODE=1. - Synchronize runtime protocol version with MAM_VERSION constant (4.3.0). - Bypass /tmp/mam-loop-freeze.XXXXXX copy overhead by default in Python mode while preserving legacy rollback fallback (MAM_LOOP_PY=0 / MAM_LOOP_FREEZE=1). - Add tests/test_issue5_phase4_runtime.py verifying eager import completeness, mid-flight code modification immunity (B-13), and freeze bypass. - Pass full test suite (749 tests, 0 failures).
This commit is contained in:
1 parent
8cd256cc7a
commit
f413f6ada6
6 files changed
+390
-11
No files matched your search
@@ -0,0 +1,185 @@
|
||||
"""Issue #5 Phase 4 — Immutable Runtime Verification & Freeze Removal.
|
||||
|
||||
Verifies:
|
||||
- P4-01: Eager import completeness, bytecode inhibition (PYTHONDONTWRITEBYTECODE=1),
|
||||
and MAM_VERSION protocol sync.
|
||||
- P4-02: Mid-flight on-disk code modification immunity (B-13 bug prevention).
|
||||
- P4-03: Freeze bypass under Python mode (default) and legacy fallback preservation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
SKILLS = REPO_ROOT / ".agents" / "skills"
|
||||
if str(SKILLS) not in sys.path:
|
||||
sys.path.insert(0, str(SKILLS))
|
||||
|
||||
from lib_py.loop import eager, parser, checkpoint, runner
|
||||
|
||||
|
||||
def test_p4_01_eager_import_completeness_and_bytecode_inhibition(monkeypatch):
|
||||
"""P4-01: Verify all core framework modules are eagerly loaded and bytecode writes disabled."""
|
||||
loaded = eager.eager_load()
|
||||
assert eager.is_eager_loaded()
|
||||
for mod in eager.CORE_MODULES:
|
||||
assert mod in sys.modules, f"Module {mod} was not eagerly loaded into sys.modules"
|
||||
|
||||
# Verify bytecode inhibition
|
||||
assert sys.dont_write_bytecode is True
|
||||
assert os.environ.get("PYTHONDONTWRITEBYTECODE") == "1"
|
||||
|
||||
# Verify framework version constant sync
|
||||
assert eager.FRAMEWORK_VERSION == "4.3.0"
|
||||
eager.verify_framework_version("4.3.0")
|
||||
|
||||
# Verify mismatch raises RuntimeError
|
||||
monkeypatch.setenv("MAM_VERSION", "9.9.9")
|
||||
with pytest.raises(RuntimeError, match="MAM_VERSION mismatch"):
|
||||
eager.verify_framework_version()
|
||||
|
||||
|
||||
def test_p4_01_loop_runner_instantiation_triggers_eager_load():
|
||||
"""P4-01: LoopRunner.__init__ must eagerly load all modules immediately."""
|
||||
lr = runner.LoopRunner(["--creator", "test-agent", "--task", "test"])
|
||||
assert eager.is_eager_loaded()
|
||||
assert lr.creator == "" # Not parsed until parse_args()
|
||||
|
||||
|
||||
def test_p4_02_midflight_code_replacement_stress(tmp_path):
|
||||
"""P4-02: Simulates B-13 mid-flight disk corruption.
|
||||
|
||||
A running Python loop process that has eagerly loaded its modules must continue
|
||||
executing correctly across iterations even if underlying source files on disk
|
||||
are overwritten with syntax-breaking garbage.
|
||||
"""
|
||||
# Create a sandbox directory with a copy of a worker module
|
||||
sandbox = tmp_path / "sandbox"
|
||||
sandbox.mkdir()
|
||||
mod_file = sandbox / "dummy_worker.py"
|
||||
mod_file.write_text(
|
||||
"def compute_verdict(val: int) -> str:\n"
|
||||
" return f'PASS (score={val})'\n",
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
||||
runner_script = tmp_path / "run_subproc.py"
|
||||
script_content = (
|
||||
"import sys, os\n"
|
||||
f"sys.path.insert(0, r'{SKILLS}')\n"
|
||||
f"sys.path.insert(0, r'{sandbox}')\n"
|
||||
"from lib_py.loop import eager, parser, checkpoint\n"
|
||||
"import dummy_worker\n"
|
||||
"eager.eager_load()\n"
|
||||
"print('READY', flush=True)\n"
|
||||
"line = sys.stdin.readline()\n"
|
||||
"# Invoke functions after disk file has been corrupted\n"
|
||||
"res1 = dummy_worker.compute_verdict(42)\n"
|
||||
"verdict = parser.parse_verdict('Review output\\nVERDICT: PASS')\n"
|
||||
"chk_path = sys.stdin.readline().strip()\n"
|
||||
"checkpoint.save_checkpoint(chk_path, {'status': 'OK', 'res': res1, 'verdict': verdict})\n"
|
||||
"loaded = checkpoint.load_checkpoint(chk_path)\n"
|
||||
"assert loaded['status'] == 'OK'\n"
|
||||
"print('FINISHED_SUCCESSFULLY', flush=True)\n"
|
||||
)
|
||||
runner_script.write_text(script_content, encoding="utf-8")
|
||||
|
||||
py_bin = sys.executable
|
||||
proc = subprocess.Popen(
|
||||
[py_bin, str(runner_script)],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
|
||||
# Wait for process to eagerly load and signal READY
|
||||
ready_line = proc.stdout.readline()
|
||||
assert ready_line.strip() == "READY", f"Child failed to start: {proc.stderr.read()}"
|
||||
|
||||
# --- B-13 Corruption Phase ---
|
||||
# While process is suspended waiting on stdin, corrupt dummy_worker.py on disk
|
||||
mod_file.write_text("INVALID SYNTAX !!!\n unexpected EOF def broken():\n", encoding="utf-8")
|
||||
|
||||
# Tell child process to proceed
|
||||
chk_target = tmp_path / "checkpoint.json"
|
||||
proc.stdin.write(f"PROCEED\n{chk_target}\n")
|
||||
proc.stdin.flush()
|
||||
|
||||
out_line = proc.stdout.readline()
|
||||
assert out_line.strip() == "FINISHED_SUCCESSFULLY", f"Child error: {proc.stderr.read()}"
|
||||
|
||||
proc.communicate(timeout=5)
|
||||
assert proc.returncode == 0
|
||||
assert chk_target.exists()
|
||||
|
||||
|
||||
def test_p4_03_freeze_bypass_by_default(tmp_path):
|
||||
"""P4-03: Under MAM_LOOP_PY=1 (default), run_loop.sh must bypass /tmp/mam-loop-freeze.*."""
|
||||
run_loop_sh = SKILLS / "multi-agent-mux-loop" / "scripts" / "run_loop.sh"
|
||||
env = os.environ.copy()
|
||||
env.pop("MAM_LOOP_FREEZE_DIR", None)
|
||||
env.pop("MAM_LOOP_FREEZE", None)
|
||||
env.pop("MAM_LOOP_PY", None) # defaults to 1
|
||||
|
||||
# Snapshot current freeze dirs in /tmp
|
||||
before_dirs = set(glob.glob(f"{tempfile.gettempdir()}/mam-loop-freeze.*"))
|
||||
|
||||
res = subprocess.run(
|
||||
["bash", str(run_loop_sh), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env,
|
||||
cwd=str(REPO_ROOT),
|
||||
)
|
||||
assert res.returncode == 1
|
||||
assert "Usage: " in res.stdout
|
||||
|
||||
after_dirs = set(glob.glob(f"{tempfile.gettempdir()}/mam-loop-freeze.*"))
|
||||
new_dirs = after_dirs - before_dirs
|
||||
assert len(new_dirs) == 0, f"Freeze dir was unexpectedly created: {new_dirs}"
|
||||
|
||||
|
||||
def test_p4_03_explicit_freeze_fallback(tmp_path):
|
||||
"""P4-03: Setting MAM_LOOP_FREEZE=1 or MAM_LOOP_PY=0 restores freeze snapshot logic."""
|
||||
run_loop_sh = SKILLS / "multi-agent-mux-loop" / "scripts" / "run_loop.sh"
|
||||
|
||||
# 1. Test MAM_LOOP_FREEZE=1
|
||||
env_freeze = os.environ.copy()
|
||||
env_freeze["MAM_LOOP_FREEZE"] = "1"
|
||||
env_freeze.pop("MAM_LOOP_FREEZE_DIR", None)
|
||||
|
||||
res = subprocess.run(
|
||||
["bash", str(run_loop_sh), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env_freeze,
|
||||
cwd=str(REPO_ROOT),
|
||||
)
|
||||
assert res.returncode == 1
|
||||
assert "Usage: " in res.stdout
|
||||
|
||||
# 2. Test MAM_LOOP_NO_FREEZE=1 overrides MAM_LOOP_FREEZE=1
|
||||
env_no_freeze = os.environ.copy()
|
||||
env_no_freeze["MAM_LOOP_FREEZE"] = "1"
|
||||
env_no_freeze["MAM_LOOP_NO_FREEZE"] = "1"
|
||||
env_no_freeze.pop("MAM_LOOP_FREEZE_DIR", None)
|
||||
|
||||
before_dirs = set(glob.glob(f"{tempfile.gettempdir()}/mam-loop-freeze.*"))
|
||||
res = subprocess.run(
|
||||
["bash", str(run_loop_sh), "--help"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=env_no_freeze,
|
||||
cwd=str(REPO_ROOT),
|
||||
)
|
||||
assert res.returncode == 1
|
||||
after_dirs = set(glob.glob(f"{tempfile.gettempdir()}/mam-loop-freeze.*"))
|
||||
assert len(after_dirs - before_dirs) == 0
|
||||
Reference in new issue
Block a user