pivotcheck 2.0.1__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/PKG-INFO +1 -1
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/__init__.py +1 -1
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/cli.py +30 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck.egg-info/PKG-INFO +1 -1
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_intelligence_cli.py +99 -1
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/LICENSE +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/README.md +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/__main__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/comparison.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/correlation.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/evidence_gaps.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/explanation.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/gateway.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/graph.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/map_view.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/next_step.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/opsec.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/query.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/recommendation.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/summary.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/topology.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/analysis/transit_priority.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/context.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/proxy.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/resolver.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/smb.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/ssh.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/tcp.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/checks/winrm.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/connections.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/dns.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/engine.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/interfaces.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/local.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/macos.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/neighbors.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/provider.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/remote.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/routes.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/ssh.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/discovery/windows.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/baseline.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/correlation.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/credentials.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/graph.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/network.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/opsec.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/proxy_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/result.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/session.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/smb_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/ssh_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/models/winrm_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/artifact.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/check_output.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/comparison.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/evidence_gaps.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/intelligence.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/json.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/map_view.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/next_step.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/opsec.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/proxy_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/smb_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/ssh_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/terminal.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/winrm_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/output/writer.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/storage/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/storage/baseline_store.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/utils/__init__.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/utils/credential_loader.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/utils/redaction.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/utils/system.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck/utils/validation.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck.egg-info/SOURCES.txt +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck.egg-info/dependency_links.txt +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck.egg-info/entry_points.txt +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck.egg-info/requires.txt +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pivotcheck.egg-info/top_level.txt +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/pyproject.toml +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/setup.cfg +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_baseline_store.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_baseline_workflow.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_check_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_cli_contract.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_comparison.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_context.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_correlation.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_credentials.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_determinism.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_gateway.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_graph.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_intelligence_pure.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_interfaces.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_json_schema_audit.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_map_view.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_neighbors_dns.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_network_safety.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_next_step.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_next_step_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_operator_intelligence.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_opsec.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_output_encoding.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_platform_collectors.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_proxy_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_proxy_check_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_proxy_protocol.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_remote_session.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_resolver.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_routes.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_session_provider.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_side_effects.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_smb_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_smb_check_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_ssh_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_ssh_check_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_ssh_check_integration.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_ssh_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_ssh_provider.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_tcp.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_terminal.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_topology.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_transit_priority.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_validation_context.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_validation_context_cli.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_winrm_check.py +0 -0
- {pivotcheck-2.0.1 → pivotcheck-2.0.2}/tests/test_winrm_check_cli.py +0 -0
|
@@ -32,6 +32,7 @@ from __future__ import annotations
|
|
|
32
32
|
|
|
33
33
|
import argparse
|
|
34
34
|
import getpass
|
|
35
|
+
import ipaddress
|
|
35
36
|
import os
|
|
36
37
|
import re
|
|
37
38
|
import sys
|
|
@@ -1674,8 +1675,33 @@ def _run_next(args: argparse.Namespace) -> int:
|
|
|
1674
1675
|
return EXIT_OK
|
|
1675
1676
|
|
|
1676
1677
|
|
|
1678
|
+
def _validate_network_argument(network: str) -> int | None:
|
|
1679
|
+
"""Validate a network CLI argument against the canonical CIDR model.
|
|
1680
|
+
|
|
1681
|
+
Returns ``None`` when the argument is valid, otherwise ``EXIT_USAGE``
|
|
1682
|
+
after printing the standard operator error. Validation is pure: invalid
|
|
1683
|
+
input fails before discovery (no filesystem, network, or subprocess
|
|
1684
|
+
side effects), and the original argument is passed through to analysis
|
|
1685
|
+
unchanged, so valid-path behavior is byte-identical.
|
|
1686
|
+
"""
|
|
1687
|
+
try:
|
|
1688
|
+
ipaddress.ip_network(network, strict=False)
|
|
1689
|
+
except ValueError:
|
|
1690
|
+
print(f"[-] Invalid network argument: {network!r}.", file=sys.stderr)
|
|
1691
|
+
print(
|
|
1692
|
+
" Provide a CIDR network (e.g. 10.50.0.0/16) or an IP address.",
|
|
1693
|
+
file=sys.stderr,
|
|
1694
|
+
)
|
|
1695
|
+
return EXIT_USAGE
|
|
1696
|
+
return None
|
|
1697
|
+
|
|
1698
|
+
|
|
1677
1699
|
def _run_gaps(args: argparse.Namespace) -> int:
|
|
1678
1700
|
"""Execute evidence gap analysis."""
|
|
1701
|
+
error = _validate_network_argument(args.network)
|
|
1702
|
+
if error is not None:
|
|
1703
|
+
return error
|
|
1704
|
+
|
|
1679
1705
|
# Run discovery once
|
|
1680
1706
|
try:
|
|
1681
1707
|
snapshot = run_discovery()
|
|
@@ -1700,6 +1726,10 @@ def _run_gaps(args: argparse.Namespace) -> int:
|
|
|
1700
1726
|
|
|
1701
1727
|
def _run_explain(args: argparse.Namespace) -> int:
|
|
1702
1728
|
"""Execute standalone network explanation."""
|
|
1729
|
+
error = _validate_network_argument(args.network)
|
|
1730
|
+
if error is not None:
|
|
1731
|
+
return error
|
|
1732
|
+
|
|
1703
1733
|
# If the operator explicitly requested contextual baseline analysis,
|
|
1704
1734
|
# load the baseline BEFORE any discovery or socket activity. A missing
|
|
1705
1735
|
# or invalid baseline fails explicitly rather than silently performing
|
|
@@ -267,4 +267,102 @@ class TestDiscoverSummary:
|
|
|
267
267
|
out = capsys.readouterr().out
|
|
268
268
|
assert "Connected Coverage: 1" in out
|
|
269
269
|
assert "Routed Coverage: 2" in out
|
|
270
|
-
assert "Inferred Pivot Paths: 1" in out
|
|
270
|
+
assert "Inferred Pivot Paths: 1" in out
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class TestNetworkArgumentValidation:
|
|
274
|
+
"""Stage 9 DEFECT-002 regression: invalid network arguments must follow
|
|
275
|
+
the established CLI error contract (clean `[-]` message on stderr, no
|
|
276
|
+
Python traceback, EXIT_USAGE, no partial output, no side effects).
|
|
277
|
+
|
|
278
|
+
These tests fail against the defective 2.0.0 implementation, which
|
|
279
|
+
leaked an uncaught ValueError traceback from models/network.py and
|
|
280
|
+
exited 1.
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
BAD_NETWORKS = (
|
|
284
|
+
"not-a-cidr",
|
|
285
|
+
"999.999.1.0/24", # malformed IPv4 octets
|
|
286
|
+
"10.0.0.0/33", # invalid IPv4 prefix length
|
|
287
|
+
"10.0.0.0/120", # IPv6 prefix length applied to IPv4
|
|
288
|
+
"10.0.0.0/24/24", # double prefix
|
|
289
|
+
"2001:db8::/wxyz", # malformed IPv6 prefix
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
@pytest.fixture(autouse=True)
|
|
293
|
+
def fake_discovery(self, monkeypatch):
|
|
294
|
+
from pivotcheck import cli
|
|
295
|
+
|
|
296
|
+
monkeypatch.setattr(cli, "run_discovery", make_snapshot)
|
|
297
|
+
|
|
298
|
+
@pytest.mark.parametrize("bad", BAD_NETWORKS)
|
|
299
|
+
def test_gaps_invalid_network_is_usage_error(self, bad, capsys):
|
|
300
|
+
assert _run(["gaps", bad]) == EXIT_USAGE
|
|
301
|
+
err = capsys.readouterr().err
|
|
302
|
+
assert "Invalid network argument" in err
|
|
303
|
+
assert "Traceback" not in err
|
|
304
|
+
assert "ValueError" not in err
|
|
305
|
+
|
|
306
|
+
@pytest.mark.parametrize("bad", BAD_NETWORKS)
|
|
307
|
+
def test_explain_invalid_network_is_usage_error(self, bad, capsys):
|
|
308
|
+
assert _run(["explain", bad]) == EXIT_USAGE
|
|
309
|
+
err = capsys.readouterr().err
|
|
310
|
+
assert "Invalid network argument" in err
|
|
311
|
+
assert "Traceback" not in err
|
|
312
|
+
|
|
313
|
+
@pytest.mark.parametrize("bad", BAD_NETWORKS)
|
|
314
|
+
def test_invalid_network_json_path_has_no_partial_result(self, bad, capsys):
|
|
315
|
+
for command in ("gaps", "explain"):
|
|
316
|
+
assert _run([command, bad, "--json"]) == EXIT_USAGE
|
|
317
|
+
captured = capsys.readouterr()
|
|
318
|
+
# No partial JSON artifact may pretend the request succeeded.
|
|
319
|
+
assert captured.out.strip() == ""
|
|
320
|
+
assert "Traceback" not in captured.err
|
|
321
|
+
|
|
322
|
+
def test_invalid_network_fails_before_discovery(self, monkeypatch, capsys):
|
|
323
|
+
"""Invalid input must produce zero side effects: discovery (and any
|
|
324
|
+
I/O behind it) must never run for an invalid network argument."""
|
|
325
|
+
from pivotcheck import cli
|
|
326
|
+
|
|
327
|
+
def _must_not_run(*_args, **_kwargs):
|
|
328
|
+
raise AssertionError("discovery must not run for invalid input")
|
|
329
|
+
|
|
330
|
+
monkeypatch.setattr(cli, "run_discovery", _must_not_run)
|
|
331
|
+
assert _run(["gaps", "not-a-cidr"]) == EXIT_USAGE
|
|
332
|
+
assert "Invalid network argument" in capsys.readouterr().err
|
|
333
|
+
|
|
334
|
+
def test_valid_cidr_behavior_unchanged(self, capsys):
|
|
335
|
+
assert _run(["gaps", "10.50.0.0/16"]) == EXIT_OK
|
|
336
|
+
out = capsys.readouterr().out
|
|
337
|
+
assert "10.50.0.0/16" in out
|
|
338
|
+
assert "EVIDENCE GAP" in out
|
|
339
|
+
|
|
340
|
+
def test_bare_ip_still_valid(self, capsys):
|
|
341
|
+
assert _run(["explain", "10.50.5.1"]) == EXIT_OK
|
|
342
|
+
assert "10.50.0.0/16" in capsys.readouterr().out
|
|
343
|
+
|
|
344
|
+
def test_host_bits_set_still_accepted(self, capsys):
|
|
345
|
+
# strict=False semantics are preserved: host bits are normalized,
|
|
346
|
+
# exactly as in 2.0.0.
|
|
347
|
+
assert _run(["gaps", "10.50.5.1/16"]) == EXIT_OK
|
|
348
|
+
|
|
349
|
+
def test_ipv6_cidr_still_valid(self, capsys):
|
|
350
|
+
assert _run(["explain", "2001:db8:a::/48"]) == EXIT_OK
|
|
351
|
+
out = capsys.readouterr().out
|
|
352
|
+
assert "2001:db8:a::/48" in out
|
|
353
|
+
assert "CURRENT_EVIDENCE" in out
|
|
354
|
+
|
|
355
|
+
def test_explain_unobserved_network_is_not_observed(self, capsys):
|
|
356
|
+
"""Stage 9 DEFECT-001 regression at the CLI boundary: absence of
|
|
357
|
+
observation must never be promoted to CURRENT_EVIDENCE. Fails
|
|
358
|
+
against 2.0.0, which reported CURRENT_EVIDENCE here."""
|
|
359
|
+
assert _run(["explain", "192.168.99.0/24"]) == EXIT_OK
|
|
360
|
+
out = capsys.readouterr().out
|
|
361
|
+
assert "NOT_OBSERVED" in out
|
|
362
|
+
assert "CURRENT_EVIDENCE" not in out
|
|
363
|
+
|
|
364
|
+
def test_explain_unobserved_network_json_classification(self, capsys):
|
|
365
|
+
assert _run(["explain", "192.168.99.0/24", "--json"]) == EXIT_OK
|
|
366
|
+
payload = json.loads(capsys.readouterr().out)
|
|
367
|
+
assert payload["classification"] == "NOT_OBSERVED"
|
|
368
|
+
assert payload["reason"] == "Network not found in current discovery evidence."
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|