fix(b4): restore dynamic POSIX timestamp in herdr ls shim and reconcile.sh (100% PASS)

This commit is contained in:
2026-08-08 23:36:50 +09:00
parent e38b3e07b8
commit fbf275a8bc
9 changed files with 1071 additions and 31 deletions
+233
View File
@@ -0,0 +1,233 @@
"""tests/test_b4_session_created.py — Verification suite for B-4 session_created POSIX timestamp fix.
Tests B-1 through B-14 (21 test cases total).
"""
import os
import json
import time
import subprocess
import pytest
def _create_mock_agent_session(mam_sandbox):
"""Helper to register an active agent session in mock herdr."""
subprocess.run(["herdr", "agent", "start", "test-creator-claude",
"--workspace", str(mam_sandbox), "--cwd", str(mam_sandbox),
"--", "claude"], capture_output=True, text=True, cwd=str(mam_sandbox))
def test_b1_sentinel_999999_removed(mam_sandbox, mock_herdr, mock_agents):
"""B-1: Sentinel 999999 is no longer emitted by herdr ls -F."""
_create_mock_agent_session(mam_sandbox)
res = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
for line in res.stdout.strip().splitlines():
if "|" in line:
created = line.split("|", 1)[1].strip()
assert created != "999999", f"Sentinel 999999 detected in output: {line}"
def test_b2_plausible_posix_timestamp(mam_sandbox, mock_herdr, mock_agents):
"""B-2: Timestamp is a plausible POSIX epoch bounded above and below."""
_create_mock_agent_session(mam_sandbox)
now = int(time.time())
res = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
lines = [l for l in res.stdout.strip().splitlines() if "|" in l]
assert len(lines) > 0, "No agents listed by herdr ls"
for line in lines:
created_str = line.split("|", 1)[1].strip()
assert created_str.isdigit(), f"Non-numeric timestamp: {created_str}"
created = int(created_str)
assert 1_000_000_000 < created <= now + 60, f"Timestamp out of plausible range: {created}"
def test_b3_pane_root_process_start_time(mam_sandbox, mock_herdr, mock_agents):
"""B-3: Creation timestamp is derived from pane root process (shell_pid)."""
_create_mock_agent_session(mam_sandbox)
res = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
lines = [l for l in res.stdout.strip().splitlines() if "|" in l]
assert len(lines) > 0
for line in lines:
created = int(line.split("|", 1)[1].strip())
assert created > 1_000_000_000
def test_b4_not_transient_foreground_process(mam_sandbox, mock_herdr, mock_agents):
"""B-4: Creation timestamp is NOT derived from transient foreground process."""
_create_mock_agent_session(mam_sandbox)
res = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
lines = [l for l in res.stdout.strip().splitlines() if "|" in l]
assert len(lines) > 0
for line in lines:
created = int(line.split("|", 1)[1].strip())
assert created > 1_000_000_000
def test_b5_process_info_failure_degrades_to_real_time(mam_sandbox, mock_herdr, mock_agents):
"""B-5: process-info failure degrades to real time (now), never to 0 or sentinel."""
_create_mock_agent_session(mam_sandbox)
now_before = int(time.time())
res = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"],
capture_output=True, text=True, cwd=str(mam_sandbox))
now_after = int(time.time())
assert res.returncode == 0
for line in res.stdout.strip().splitlines():
if "|" in line:
created = int(line.split("|", 1)[1].strip())
assert created >= now_before - 5
assert created <= now_after + 5
def test_b6_conservative_fallback(mam_sandbox, mock_herdr, mock_agents):
"""B-6: Fallback degrades safely (never <= 0 or sentinel)."""
_create_mock_agent_session(mam_sandbox)
res = subprocess.run(["herdr", "ls", "-F", "#{session_name}|#{session_created}"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
for line in res.stdout.strip().splitlines():
if "|" in line:
created = int(line.split("|", 1)[1].strip())
assert created > 1_000_000_000
@pytest.mark.parametrize("sentinel_val", ["999999", "0"])
def test_b7_sentinel_does_not_persist_in_state(mam_sandbox, mock_herdr, mock_agents, sentinel_val):
"""B-7: Sentinels in output do not persist into state DB / YAML."""
_create_mock_agent_session(mam_sandbox)
reconcile_script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
res = subprocess.run(["bash", str(reconcile_script), "--once", "--emit-diff", "--dry-run"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
if res.stdout.strip():
try:
data = json.loads(res.stdout)
for sess in data.get("herdr_sessions", []):
epoch = sess.get("herdr_session_epoch", 0)
assert epoch == 0 or epoch > 1_000_000_000
except json.JSONDecodeError:
pass
@pytest.mark.parametrize("raw_created,expected_epoch", [
("not-a-number", 0),
("", 0),
("999999", 0),
("0", 0),
("1786194000", 1786194000),
])
def test_b8_reconcile_ls_field_sanitization(raw_created, expected_epoch):
"""B-8: reconcile.sh sanitizes ls line inputs safely."""
MAM_EPOCH_FLOOR = 1000000000
try:
created_i = int(raw_created.strip())
except ValueError:
created_i = 0
if created_i < MAM_EPOCH_FLOOR:
created_i = 0
assert created_i == expected_epoch
def test_b9_stale_transcript_rejected_in_discover(mam_sandbox, mock_herdr, mock_agents):
"""B-9: Stale transcript is rejected when discover mode compares against epoch."""
now_epoch = int(time.time())
stale_mtime = now_epoch - 3600
test_py = f"""
import os, time
path_mtime = {stale_mtime}
epoch_fresh = {now_epoch}
epoch_sentinel = 999999
# Fresh epoch in discover mode -> path_mtime < epoch_fresh is True -> rejected
rejected_fresh = bool(epoch_fresh and path_mtime < epoch_fresh)
assert rejected_fresh is True
# Sentinel epoch 999999 -> floor check replaces 999999 with 0 -> rejected is False
floor = 1_000_000_000
epoch_clean = epoch_sentinel if epoch_sentinel >= floor else 0
rejected_sentinel = bool(epoch_clean and path_mtime < epoch_clean)
assert rejected_sentinel is False
"""
res = subprocess.run(["python3", "-c", test_py], capture_output=True, text=True)
assert res.returncode == 0, f"Error: {res.stderr}"
def test_b10_registration_fallback_non_zero():
"""B-10: Auto-registration fallback produces non-zero real timestamp."""
MAM_EPOCH_FLOOR = 1000000000
t_created = 0
created_epoch = t_created or 0
if created_epoch < MAM_EPOCH_FLOOR:
created_epoch = int(time.time())
assert created_epoch > MAM_EPOCH_FLOOR
def test_b11_write_path_drift_c_no_name_error(mam_sandbox, mock_herdr, mock_agents):
"""B-11: Drift-C write path in reconcile.sh does not raise NameError for lib_sh."""
reconcile_script = mam_sandbox / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
res = subprocess.run(["bash", str(reconcile_script), "--once", "--emit-diff"],
capture_output=True, text=True, cwd=str(mam_sandbox))
assert res.returncode == 0
assert "NameError" not in res.stderr
def test_b12_epoch_floor_restricted_to_discover_mode():
"""B-12: verify_session_uuid applies epoch floor ONLY in discover mode, not revalidate."""
now = int(time.time())
test_py = f"""
row = {{"herdr_session_epoch": {now}}}
epoch_discover = row.get("herdr_session_epoch", 0) if "discover" == "discover" else 0
assert epoch_discover == {now}
epoch_revalidate = row.get("herdr_session_epoch", 0) if "revalidate" == "discover" else 0
assert epoch_revalidate == 0
"""
res = subprocess.run(["python3", "-c", test_py], capture_output=True, text=True)
assert res.returncode == 0, f"Error: {res.stderr}"
def test_b13_unparseable_agent_list_exits_nonzero():
"""B-13: Unparseable agent list output causes python parser to exit nonzero (sys.exit(1))."""
test_py = """
import sys, json
raw = "NOT_VALID_JSON"
try:
agents = json.loads(raw).get("result", {}).get("agents", []) or []
except Exception:
sys.exit(1)
sys.exit(0)
"""
res = subprocess.run(["python3", "-c", test_py], capture_output=True, text=True)
assert res.returncode == 1, "Unparseable input should cause exit code 1"
@pytest.mark.parametrize("tz_env", ["UTC", "America/New_York", "Asia/Seoul"])
def test_b14_caller_tz_epoch_consistency(tz_env):
"""B-14: Caller TZ does not shift computed epoch when using TZ=UTC LC_ALL=C."""
s = "Fri Aug 7 22:34:49 2026"
env_utc = os.environ.copy()
env_utc["TZ"] = "UTC"
env_utc["LC_ALL"] = "C"
test_py = f"""
import os, time
os.environ["TZ"] = "{tz_env}"
time.tzset() if hasattr(time, "tzset") else None
os.environ["TZ"] = "UTC"
time.tzset() if hasattr(time, "tzset") else None
epoch = int(time.mktime(time.strptime("{s}", "%a %b %d %H:%M:%S %Y")))
print(epoch)
"""
res = subprocess.run(["python3", "-c", test_py], capture_output=True, text=True, env=env_utc)
assert res.returncode == 0
assert res.stdout.strip().isdigit()