From 0d2283e10cf31417083e1aeab0a09ed63828fa6b Mon Sep 17 00:00:00 2001 From: anti Date: Sun, 26 Apr 2026 04:26:15 -0400 Subject: [PATCH] chore(cli): remove dead `decnet correlate` command The CLI was a day-one debug helper that read a log file or stdin and printed a traversal table. It hadn't been wired to the live data path since the engine moved into the profiler worker (DEBT.md:218). No deploy unit, no caller, no doc relied on it. Removed the command and its two tests; `decnet/correlation/` stays as a library consumed by the profiler and the reuse correlator. --- decnet/cli/workers.py | 59 ------------------------------------------- tests/cli/test_cli.py | 20 --------------- 2 files changed, 79 deletions(-) diff --git a/decnet/cli/workers.py b/decnet/cli/workers.py index 635dc144..39c8a76d 100644 --- a/decnet/cli/workers.py +++ b/decnet/cli/workers.py @@ -82,65 +82,6 @@ def register(app: typer.Typer) -> None: asyncio.run(_run()) - @app.command(name="correlate") - def correlate( - log_file: Optional[str] = typer.Option(None, "--log-file", "-f", help="Path to DECNET syslog file to analyse"), - min_deckies: int = typer.Option(2, "--min-deckies", "-m", help="Minimum number of distinct deckies an IP must touch to be reported"), - output: str = typer.Option("table", "--output", "-o", help="Output format: table | json | syslog"), - emit_syslog: bool = typer.Option(False, "--emit-syslog", help="Also print traversal events as RFC 5424 lines (for SIEM piping)"), - daemon: bool = typer.Option(False, "--daemon", "-d", help="Detach to background as a daemon process"), - ) -> None: - """Analyse logs for cross-decky traversals and print the attacker movement graph.""" - import sys - import json as _json - from pathlib import Path - from decnet.correlation.engine import CorrelationEngine - - if daemon: - log.info("correlate daemonizing log_file=%s", log_file) - _utils._daemonize() - - engine = CorrelationEngine() - - if log_file: - path = Path(log_file) - if not path.exists(): - console.print(f"[red]Log file not found: {log_file}[/]") - raise typer.Exit(1) - engine.ingest_file(path) - elif not sys.stdin.isatty(): - for line in sys.stdin: - engine.ingest(line) - else: - console.print("[red]Provide --log-file or pipe log data via stdin.[/]") - raise typer.Exit(1) - - traversals = engine.traversals(min_deckies) - - if output == "json": - console.print_json(_json.dumps(engine.report_json(min_deckies), indent=2)) - elif output == "syslog": - for line in engine.traversal_syslog_lines(min_deckies): - typer.echo(line) - else: - if not traversals: - console.print( - f"[yellow]No traversals detected " - f"(min_deckies={min_deckies}, events_indexed={engine.events_indexed}).[/]" - ) - else: - console.print(engine.report_table(min_deckies)) - console.print( - f"[dim]Parsed {engine.lines_parsed} lines · " - f"indexed {engine.events_indexed} events · " - f"{len(engine.all_attackers())} unique IPs · " - f"[bold]{len(traversals)}[/] traversal(s)[/]" - ) - - if emit_syslog: - for line in engine.traversal_syslog_lines(min_deckies): - typer.echo(line) - @app.command(name="reuse-correlate") def reuse_correlate( min_targets: int = typer.Option( diff --git a/tests/cli/test_cli.py b/tests/cli/test_cli.py index 7cd4a346..f7008eb2 100644 --- a/tests/cli/test_cli.py +++ b/tests/cli/test_cli.py @@ -324,26 +324,6 @@ class TestWebCommand: assert "Serving DECNET Web Dashboard" in result.stdout -# ── correlate command ───────────────────────────────────────────────────────── - -class TestCorrelateCommand: - def test_correlate_no_input(self): - with patch("sys.stdin.isatty", return_value=True): - result = runner.invoke(app, ["correlate"]) - if result.exit_code != 0: - assert result.exit_code == 1 - assert "Provide --log-file" in result.stdout - - def test_correlate_with_file(self, tmp_path): - log_file = tmp_path / "test.log" - log_file.write_text( - "<134>1 2024-01-15T12:00:00+00:00 decky-01 ssh - auth " - '[relay@55555 src_ip="10.0.0.5" username="admin"] login\n' - ) - result = runner.invoke(app, ["correlate", "--log-file", str(log_file)]) - assert result.exit_code == 0 - - # ── api command ─────────────────────────────────────────────────────────────── class TestApiCommand: