Skip to content

Commit 9840ebb

Browse files
committed
feat: add config-change-investigation-demo
1 parent 803a277 commit 9840ebb

1 file changed

Lines changed: 117 additions & 0 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from __future__ import annotations
2+
3+
import json
4+
from pathlib import Path
5+
6+
from telemetry_window_demo.config_change_investigation_demo import default_demo_root, run_demo
7+
from telemetry_window_demo.config_change_investigation_demo.pipeline import (
8+
build_investigations,
9+
evaluate_risky_config_changes,
10+
load_jsonl,
11+
load_yaml,
12+
normalize_config_changes,
13+
normalize_follow_on_events,
14+
normalize_policy_denials,
15+
)
16+
17+
18+
def _load_demo_inputs():
19+
demo_root = default_demo_root()
20+
config = load_yaml(demo_root / "config" / "investigation.yaml")
21+
config_changes = normalize_config_changes(
22+
load_jsonl(demo_root / "data" / "raw" / "config_changes.jsonl")
23+
)
24+
policy_denials = normalize_policy_denials(
25+
load_jsonl(demo_root / "data" / "raw" / "policy_denials.jsonl")
26+
)
27+
follow_on_events = normalize_follow_on_events(
28+
load_jsonl(demo_root / "data" / "raw" / "follow_on_events.jsonl")
29+
)
30+
return demo_root, config, config_changes, policy_denials, follow_on_events
31+
32+
33+
def _load_json_file(path: Path):
34+
return json.loads(path.read_text(encoding="utf-8"))
35+
36+
37+
def test_normalize_config_changes_is_sorted_and_complete() -> None:
38+
_, _, config_changes, _, _ = _load_demo_inputs()
39+
40+
assert [change["change_id"] for change in config_changes] == [
41+
"cfg-001",
42+
"cfg-002",
43+
"cfg-003",
44+
"cfg-004",
45+
]
46+
assert config_changes[0]["target_system"] == "identity-proxy"
47+
assert config_changes[1]["config_key"] == "public_bind_cidr"
48+
49+
50+
def test_evaluate_risky_config_changes_flags_expected_changes() -> None:
51+
_, config, config_changes, _, _ = _load_demo_inputs()
52+
hits = evaluate_risky_config_changes(config_changes, config["rules"])
53+
54+
assert [hit["change_event"]["change_id"] for hit in hits] == [
55+
"cfg-001",
56+
"cfg-002",
57+
"cfg-004",
58+
]
59+
assert [hit["severity"] for hit in hits] == ["critical", "high", "high"]
60+
61+
62+
def test_build_investigations_uses_bounded_system_and_time_correlation() -> None:
63+
_, config, config_changes, policy_denials, follow_on_events = _load_demo_inputs()
64+
hits = evaluate_risky_config_changes(config_changes, config["rules"])
65+
investigations = build_investigations(
66+
hits,
67+
policy_denials,
68+
follow_on_events,
69+
correlation_minutes=int(config["correlation_minutes"]),
70+
)
71+
72+
identity = next(item for item in investigations if item["investigation_id"] == "CCI-001")
73+
payments = next(item for item in investigations if item["investigation_id"] == "CCI-002")
74+
vault = next(item for item in investigations if item["investigation_id"] == "CCI-003")
75+
76+
assert identity["evidence_counts"] == {"policy_denials": 2, "follow_on_events": 2}
77+
assert payments["evidence_counts"] == {"policy_denials": 1, "follow_on_events": 2}
78+
assert vault["evidence_counts"] == {"policy_denials": 0, "follow_on_events": 0}
79+
80+
assert all(
81+
denial["target_system"] == "payments-api"
82+
for denial in payments["attached_policy_denials"]
83+
)
84+
assert all(
85+
event["event_id"] != "fo-005" for event in vault["attached_follow_on_events"]
86+
)
87+
88+
89+
def test_run_demo_is_deterministic_and_matches_committed_artifacts(tmp_path) -> None:
90+
demo_root, _, _, _, _ = _load_demo_inputs()
91+
first_dir = tmp_path / "run-one"
92+
second_dir = tmp_path / "run-two"
93+
94+
first_result = run_demo(demo_root=demo_root, artifacts_dir=first_dir)
95+
second_result = run_demo(demo_root=demo_root, artifacts_dir=second_dir)
96+
97+
assert first_result["change_event_count"] == 4
98+
assert first_result["risky_change_count"] == 3
99+
assert first_result["investigation_count"] == 3
100+
assert second_result["investigation_count"] == first_result["investigation_count"]
101+
102+
for name in (
103+
"change_events_normalized.json",
104+
"investigation_hits.json",
105+
"investigation_summary.json",
106+
):
107+
expected = _load_json_file(demo_root / "artifacts" / name)
108+
first = _load_json_file(first_dir / name)
109+
second = _load_json_file(second_dir / name)
110+
assert first == expected
111+
assert second == expected
112+
113+
expected_report = (
114+
demo_root / "artifacts" / "investigation_report.md"
115+
).read_text(encoding="utf-8")
116+
assert (first_dir / "investigation_report.md").read_text(encoding="utf-8") == expected_report
117+
assert (second_dir / "investigation_report.md").read_text(encoding="utf-8") == expected_report

0 commit comments

Comments
 (0)