From ae607e3b7627844169c2e0ee2ed9a0a8ca5c82c8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 23 Jun 2026 00:08:29 +0000 Subject: [PATCH] fix: watchdog override proof enforcement and litigation viewer transcript matching - Executive watchdog skipped all checks for action_type containing 'override', allowing unapproved overrides without cryptographic proof. Override actions now require override_proof; non-override actions still require judicial approval or proof. - Litigation viewer HTTP serve and CLI show used substring matching (name in stem), serving the wrong transcript when multiple stems share a topic token (e.g. bar vs foo-bar). Resolve by suffix/prefix rules and prefer the shortest matching stem. Adds regression tests for both paths. Co-authored-by: Jack J Burleson // LJM --- executive/watchdog.py | 18 ++++++++++--- litigation/viewer.py | 44 +++++++++++++++++++++----------- tests/test_executive_watchdog.py | 23 +++++++++++++++++ tests/test_litigation_viewer.py | 17 +++++++++++- 4 files changed, 82 insertions(+), 20 deletions(-) diff --git a/executive/watchdog.py b/executive/watchdog.py index 7123215..bf1951f 100644 --- a/executive/watchdog.py +++ b/executive/watchdog.py @@ -119,13 +119,23 @@ def run_checks() -> tuple[bool, list[str]]: for a in actions: rid = a.get("ruling_id", "") - if rid and rid not in approved and "override" not in a.get("action_type", "").lower(): - # Action references ruling not in judicial log - proof = a.get("override_proof") + if not rid: + continue + action_type = a.get("action_type", "").lower() + proof = a.get("override_proof") + is_override = "override" in action_type + + if is_override: if not proof: alerts.append( - f"Action without judicial approval and no override proof: ruling_id={rid}" + f"Override action without cryptographic proof: ruling_id={rid}" ) + continue + + if rid not in approved and not proof: + alerts.append( + f"Action without judicial approval and no override proof: ruling_id={rid}" + ) # (c) Override frequency overrides = [a for a in actions if a.get("override_proof")] diff --git a/litigation/viewer.py b/litigation/viewer.py index 93e0969..df7a5ac 100644 --- a/litigation/viewer.py +++ b/litigation/viewer.py @@ -85,6 +85,29 @@ def _collect_transcripts() -> List[Tuple[Path, str, str]]: return out +def _matches_transcript_query(name: str, path: Path) -> bool: + """Match transcript by exact name, prefix, or topic suffix (not arbitrary substring).""" + stem = path.stem + if stem == name or path.name == name: + return True + if stem.startswith(name): + return True + return stem.endswith(f"-{name}") or stem.endswith(f"_{name}") + + +def _resolve_transcript(name: str, rows: List[Tuple[Path, str, str]]) -> Optional[Path]: + """Pick the best transcript path for a query name.""" + matches = [r for r in rows if _matches_transcript_query(name, r[0])] + if not matches: + return None + exact = [m for m in matches if m[0].stem == name or m[0].name == name] + if exact: + return exact[0][0] + if len(matches) > 1: + matches.sort(key=lambda r: len(r[0].stem)) + return matches[0][0] + + def cmd_list(plain: bool) -> None: """List transcripts in a table or plain lines.""" rows = _collect_transcripts() @@ -116,17 +139,11 @@ def cmd_show(name: Optional[str], pager: bool) -> None: path = rows[0][0] else: name = name.strip() - matches = [r for r in rows if r[0].stem == name or r[0].name == name or r[0].stem.startswith(name) or name in r[0].stem] - if not matches: + path = _resolve_transcript(name, rows) + if path is None: print(f"No transcript matching '{name}'", file=sys.stderr) print("Use 'viewer.py list' to see available transcripts.", file=sys.stderr) sys.exit(1) - if len(matches) > 1: - # Prefer exact stem match - exact = [m for m in matches if m[0].stem == name] - path = exact[0][0] if exact else matches[0][0] - else: - path = matches[0][0] text = path.read_text(encoding="utf-8") if pager: import subprocess @@ -223,15 +240,12 @@ def do_GET(self) -> None: if not name: self.send_error(404) return - found = None - for p, date_str, topic in rows: - if p.stem == name or name in p.stem: - found = (p, topic) - break - if not found: + resolved = _resolve_transcript(name, rows) + if not resolved: self.send_error(404) return - p, topic = found + p = resolved + topic = next(t for path, _, t in rows if path == p) md = p.read_text(encoding="utf-8") body_html = _markdown_to_html(md) title = f"In Re: {topic}" diff --git a/tests/test_executive_watchdog.py b/tests/test_executive_watchdog.py index 72e66d3..8292084 100644 --- a/tests/test_executive_watchdog.py +++ b/tests/test_executive_watchdog.py @@ -43,6 +43,29 @@ def test_run_checks_passes_with_approved_ruling(tmp_path, monkeypatch: pytest.Mo assert alerts == [] +def test_run_checks_alerts_on_override_without_proof( + tmp_path, monkeypatch: pytest.MonkeyPatch +) -> None: + log_path = tmp_path / "judicial_decisions.log" + actions_path = tmp_path / "executive_actions.log" + + JudicialLog(log_path=log_path) # empty approvals + + _write_action( + actions_path, + action_type="override", + ruling_id="2026-UNKNOWN-999", + description="rogue override", + ) + + monkeypatch.setattr(watchdog, "JudicialLog", lambda: JudicialLog(log_path=log_path)) + monkeypatch.setattr(watchdog, "_actions_log_path", lambda: actions_path) + + ok, alerts = watchdog.run_checks() + assert ok is False + assert any("Override action without cryptographic proof" in a for a in alerts) + + def test_run_checks_alerts_on_unapproved_action_without_proof( tmp_path, monkeypatch: pytest.MonkeyPatch ) -> None: diff --git a/tests/test_litigation_viewer.py b/tests/test_litigation_viewer.py index 12cb532..9712da7 100644 --- a/tests/test_litigation_viewer.py +++ b/tests/test_litigation_viewer.py @@ -4,7 +4,7 @@ from pathlib import Path -from litigation.viewer import _collect_transcripts, _transcript_meta +from litigation.viewer import _collect_transcripts, _resolve_transcript, _transcript_meta def test_transcript_meta_iso_date_slug() -> None: @@ -34,3 +34,18 @@ def test_collect_transcripts_skips_readme_and_dotfiles(tmp_path: Path, monkeypat assert rows[0][0].name == "2026-01-01-sample.md" assert rows[0][1] == "2026-01-01" assert rows[0][2] == "Sample" + + +def test_resolve_transcript_prefers_exact_suffix_over_broader_match(tmp_path: Path) -> None: + """Short topic queries must not match unrelated longer stems (e.g. bar vs foo-bar).""" + bar = tmp_path / "2026-06-01-bar.md" + foo_bar = tmp_path / "2026-06-01-foo-bar.md" + bar.write_text("# bar", encoding="utf-8") + foo_bar.write_text("# foo bar", encoding="utf-8") + + rows = [ + (foo_bar, "2026-06-01", "Foo Bar"), + (bar, "2026-06-01", "Bar"), + ] + resolved = _resolve_transcript("bar", rows) + assert resolved == bar