Fix 10 audit bugs: path prefix matching, verdict parsing, CORS, stale-task detection, phase mapping
CI / build (push) Has been cancelled

Batch 1 (High severity):
- Bug 1: --audit cat3 now checks .automaton/tasks/ paths
- Bug 4: Verdict PASS/FAIL uses structured ## Status: line parsing
- Bug 5: register-guards.sh checks .json/.jsonc, writes plugin key, strips comments
- Bug 7: --can-edit/--scope-check path prefix uses os.sep boundary

Batch 2 (Medium/Low severity):
- Bug 2: migrate-project.sh find command parentheses for -prune binding
- Bug 3: vram_detect model prefix matching with known-suffix whitelist
- Bug 6: dashboard reads .state file before artifact heuristic fallback
- Bug 8: removed wildcard CORS, added security headers (nosniff, DENY)
- Bug 9: stale-task detection uses .state.lastedit instead of .state mtime
- Bug 10: TEST_PLAN.md maps to test_design (was implement)

249 tests pass (up from 235). All 10 tasks driven through full workflow to completion.
This commit is contained in:
Lap Tran
2026-06-22 10:40:58 -04:00
parent f32f98575b
commit 81ccf548e5
106 changed files with 1643 additions and 436 deletions
+5 -5
View File
@@ -89,9 +89,9 @@ def test_static_valid_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> N
class TestCORSAndSecurityHeaders:
"""Tests for CORS and security headers on API responses."""
"""Tests for security headers on API responses (no CORS wildcard)."""
def test_send_json_includes_cors(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def test_send_json_no_cors_wildcard(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
html_dir = tmp_path / "html"
html_dir.mkdir()
monkeypatch.setattr(DashboardHandler, "dashboard_path", html_dir)
@@ -105,10 +105,10 @@ class TestCORSAndSecurityHeaders:
handler._send_json({"test": True})
header_dict = dict(response_headers)
assert header_dict.get("Access-Control-Allow-Origin") == "*"
assert "Access-Control-Allow-Origin" not in header_dict
assert header_dict.get("X-Content-Type-Options") == "nosniff"
def test_send_error_includes_cors(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
def test_send_error_no_cors_wildcard(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
html_dir = tmp_path / "html"
html_dir.mkdir()
monkeypatch.setattr(DashboardHandler, "dashboard_path", html_dir)
@@ -122,7 +122,7 @@ class TestCORSAndSecurityHeaders:
handler._send_error(404, "Not found")
header_dict = dict(response_headers)
assert header_dict.get("Access-Control-Allow-Origin") == "*"
assert "Access-Control-Allow-Origin" not in header_dict
assert header_dict.get("X-Content-Type-Options") == "nosniff"
+191 -1
View File
@@ -572,4 +572,194 @@ class TestCodeReviewListStates:
assert code == 0
assert "code_review * requires approval" in out
assert "code_review:awaiting_approval" in out
assert "code_review:approved" in out
assert "code_review:approved" in out
class TestCat3AuditRegularProjectPaths:
"""Bug 1: Category 3 audit must recognize .automaton/tasks/ paths for regular projects."""
def test_regular_project_task_path_not_flagged(self, tmp_project):
"""Files inside .automaton/tasks/ in a regular project should NOT be flagged as unauthorized."""
import subprocess
task_dir = _create_task(tmp_project, "edit-task", "implement")
# Simulate a change inside the task folder
(task_dir / "IMPLEMENTATION.md").write_text("# Impl")
subprocess.run(["git", "init"], cwd=str(tmp_project), capture_output=True)
subprocess.run(["git", "add", "-A"], cwd=str(tmp_project), capture_output=True)
subprocess.run(["git", "commit", "-m", "init"], cwd=str(tmp_project), capture_output=True)
# Make a change inside task folder
(task_dir / "IMPLEMENTATION.md").write_text("# Updated Impl")
out, code = _run_status(["--audit"], tmp_project)
# The task file should NOT appear as unauthorized
assert ".automaton/tasks/edit-task/IMPLEMENTATION.md" not in out or "[PASS]" in out
class TestVerdictPassInference:
"""Bug 4: _infer_state_from_artifacts must not use substring 'PASS' search."""
def test_fail_verdict_with_pass_in_body_is_human_intervention(self, tmp_project):
"""A FAIL verdict mentioning 'PASS' in body must be human_intervention, not complete."""
task_dir = tmp_project / ".automaton" / "tasks" / "verdict-fail-pass"
task_dir.mkdir(parents=True)
(task_dir / "VERDICT.md").write_text(
"# Verdict\n## Status: FAIL\n\nAll unit tests PASS but spec is not met.\n"
)
out, code = _run_status(["--upgrade", "--task", "verdict-fail-pass"], tmp_project)
assert code == 0
state = (task_dir / ".state").read_text().strip()
assert state == "human_intervention", f"Expected human_intervention, got {state}"
def test_pass_verdict_is_complete(self, tmp_project):
"""A PASS verdict with ## Status: PASS should be complete."""
task_dir = tmp_project / ".automaton" / "tasks" / "verdict-pass"
task_dir.mkdir(parents=True)
(task_dir / "VERDICT.md").write_text("# Verdict\n## Status: PASS\n\nAll good.\n")
out, code = _run_status(["--upgrade", "--task", "verdict-pass"], tmp_project)
assert code == 0
state = (task_dir / ".state").read_text().strip()
assert state == "complete", f"Expected complete, got {state}"
def test_needs_review_verdict_is_human_intervention(self, tmp_project):
"""A NEEDS_REVIEW verdict should be human_intervention."""
task_dir = tmp_project / ".automaton" / "tasks" / "verdict-nr"
task_dir.mkdir(parents=True)
(task_dir / "VERDICT.md").write_text("# Verdict\n## Status: NEEDS_REVIEW\n\nNeeds manual review.\n")
out, code = _run_status(["--upgrade", "--task", "verdict-nr"], tmp_project)
assert code == 0
state = (task_dir / ".state").read_text().strip()
assert state == "human_intervention", f"Expected human_intervention, got {state}"
class TestCanEditPathPrefix:
"""Bug 7: --can-edit and --scope-check must not match sibling directories."""
def test_scope_check_rejects_sibling_directory(self, tmp_project):
"""A file in a sibling directory (e.g. project-evil) must be OUT_OF_SCOPE."""
_create_task(tmp_project, "scope-sibling", "implement")
sibling = tmp_project.parent / (tmp_project.name + "-evil")
sibling.mkdir(exist_ok=True)
evil_file = sibling / "file.py"
evil_file.write_text("# evil")
out, code = _run_status(
["--scope-check", "--task", "scope-sibling", "--file", str(evil_file)],
tmp_project,
)
assert code == 1
assert "OUT_OF_SCOPE" in out
def test_scope_check_accepts_subdirectory(self, tmp_project):
"""A file inside the project directory should be IN_SCOPE."""
_create_task(tmp_project, "scope-sub", "implement")
sub = tmp_project / "subdir"
sub.mkdir()
test_file = sub / "file.py"
test_file.write_text("# ok")
out, code = _run_status(
["--scope-check", "--task", "scope-sub", "--file", str(test_file)],
tmp_project,
)
assert code == 0
assert "IN_SCOPE" in out
def test_can_edit_task_rejects_sibling_directory(self, tmp_project):
"""--can-edit --task --file must reject files in sibling directories."""
task_dir = _create_task(tmp_project, "edit-sibling", "implement")
sibling = tmp_project.parent / (tmp_project.name + "-evil")
sibling.mkdir(exist_ok=True)
evil_file = sibling / "file.py"
evil_file.write_text("# evil")
out, code = _run_status(
["--can-edit", "--task", "edit-sibling", "--file", str(evil_file)],
tmp_project,
)
assert code == 1
assert "OUT_OF_SCOPE" in out
class TestStateLastEdit:
"""Bug 9: --can-edit should use .state.lastedit for staleness, not .state mtime."""
def test_can_edit_creates_lastedit_on_allowed(self, tmp_project):
"""ALLOWED response should create .state.lastedit file."""
task_dir = _create_task(tmp_project, "lastedit-create", "implement")
assert not (task_dir / ".state.lastedit").exists()
out, code = _run_status(["--can-edit", "--task", "lastedit-create"], tmp_project)
assert code == 0
assert "ALLOWED" in out
assert (task_dir / ".state.lastedit").exists()
def test_can_edit_creates_lastedit_with_file_scope(self, tmp_project):
"""ALLOWED with --file should create .state.lastedit for primary task."""
task_dir = _create_task(tmp_project, "lastedit-file", "implement")
test_file = tmp_project / "src" / "main.py"
test_file.parent.mkdir(parents=True, exist_ok=True)
test_file.write_text("# test")
assert not (task_dir / ".state.lastedit").exists()
out, code = _run_status(
["--can-edit", "--file", str(test_file)],
tmp_project,
)
assert code == 0
assert "ALLOWED" in out
assert (task_dir / ".state.lastedit").exists()
def test_stale_uses_lastedit_not_state_mtime(self, tmp_project):
"""If .state is old but .state.lastedit is recent, task should not be stale."""
import time
task_dir = _create_task(tmp_project, "stale-lastedit", "implement")
# Make .state old (31 minutes ago)
state_file = task_dir / ".state"
old_time = time.time() - 31 * 60
os.utime(state_file, (old_time, old_time))
# Make .state.lastedit recent (1 minute ago)
lastedit = task_dir / ".state.lastedit"
lastedit.touch()
recent_time = time.time() - 60
os.utime(lastedit, (recent_time, recent_time))
out, code = _run_status(["--can-edit", "--task", "stale-lastedit"], tmp_project)
assert code == 0
assert "ALLOWED" in out
def test_stale_when_lastedit_old(self, tmp_project):
"""If .state.lastedit is old, task should be denied as stale."""
import time
task_dir = _create_task(tmp_project, "stale-old", "implement")
# Make .state recent (1 minute ago)
state_file = task_dir / ".state"
recent_time = time.time() - 60
os.utime(state_file, (recent_time, recent_time))
# Make .state.lastedit old (31 minutes ago)
lastedit = task_dir / ".state.lastedit"
lastedit.touch()
old_time = time.time() - 31 * 60
os.utime(lastedit, (old_time, old_time))
out, code = _run_status(["--can-edit", "--task", "stale-old"], tmp_project)
assert code == 1
assert "stale" in out.lower()
def test_falls_back_to_state_mtime_without_lastedit(self, tmp_project):
"""Without .state.lastedit, should fall back to .state mtime (backward compat)."""
import time
task_dir = _create_task(tmp_project, "fallback-state", "implement")
# No .state.lastedit — .state is 31 minutes old
state_file = task_dir / ".state"
old_time = time.time() - 31 * 60
os.utime(state_file, (old_time, old_time))
out, code = _run_status(["--can-edit", "--task", "fallback-state"], tmp_project)
assert code == 1
assert "stale" in out.lower()
class TestTestPlanPhaseMapping:
"""Bug 10: TEST_PLAN.md should map to test_design, not implement."""
def test_test_plan_maps_to_test_design(self, tmp_project):
"""--upgrade should infer test_design when TEST_PLAN.md exists (no .state)."""
task_dir = tmp_project / ".automaton" / "tasks" / "testplan-infer"
task_dir.mkdir(parents=True)
(task_dir / "SPEC.md").write_text("# Spec")
(task_dir / "TEST_PLAN.md").write_text("# Tests")
out, code = _run_status(["--upgrade", "--task", "testplan-infer"], tmp_project)
assert code == 0
assert "test_design" in out
assert (task_dir / ".state").read_text().strip() == "test_design"
+3 -3
View File
@@ -55,7 +55,7 @@ def test_implementation_from_test_plan(tmp_path: Path) -> None:
{"SPEC.md": "# Spec", "TEST_PLAN.md": "# Tests"},
)
state, _ = determine_task_state(task_dir)
assert state == TaskState.IMPLEMENT
assert state == TaskState.TEST_DESIGN
def test_bug_find_state(tmp_path: Path) -> None:
@@ -232,10 +232,10 @@ class TestStateMachineAlignment:
state, _ = determine_task_state(task_dir)
assert state == TaskState.RESEARCH
def test_test_plan_shows_implement(self, tmp_path: Path) -> None:
def test_test_plan_shows_test_design(self, tmp_path: Path) -> None:
task_dir = _make_task(tmp_path, "testplan", {"SPEC.md": "# Spec", "TEST_PLAN.md": "# Tests"})
state, _ = determine_task_state(task_dir)
assert state == TaskState.IMPLEMENT
assert state == TaskState.TEST_DESIGN
def test_design_with_spec_shows_design(self, tmp_path: Path) -> None:
task_dir = _make_task(tmp_path, "design-spec", {"SPEC.md": "# Spec", "DESIGN.md": "# Design"})
+6
View File
@@ -256,6 +256,12 @@ def test_lookup_model_context_prefix_match() -> None:
assert vram._lookup_model_context("llama-3.1-8b-instruct") == 128_000
def test_lookup_model_context_no_false_prefix_match() -> None:
"""phi-4-mini should NOT match phi-4 (different model, wrong context)."""
assert vram._lookup_model_context("phi-4-mini-instruct") == 0
assert vram._lookup_model_context("gpt-4o-foo-unknown") == 0
def test_detect_model_context_ollama_probe(monkeypatch, tmp_path: Path) -> None:
monkeypatch.setattr(vram.Path, "home", lambda: tmp_path)
monkeypatch.setattr(