add new structure

This commit is contained in:
larssand
2026-09-23 17:06:05 +02:00
parent 7cbec64b0e
commit 8c155ec9cf
8 changed files with 343 additions and 24 deletions

View File

@@ -8,7 +8,7 @@ from fgai.config import ConfigStore
from fgai.feedback import FeedbackStore
from fgai.history import StatusSnapshotStore
from fgai.history import FieldDiscoveryStore
from fgai.monitor import add_llm_assessment, build_status, cached_status_with_error, monitor_cycle_timeout_seconds, write_refreshing_status, write_status
from fgai.monitor import add_llm_assessment, build_status, cached_status_with_error, monitor_cycle_timeout_seconds, write_refreshing_status, write_status, _cluster_field_deviations
class MonitorTests(unittest.TestCase):
@@ -39,6 +39,75 @@ class MonitorTests(unittest.TestCase):
self.assertFalse(status["status_cache"]["served_from_cache"])
self.assertGreaterEqual(len(status["anomalies"]), 1)
def test_cluster_field_deviations_dampens_large_identical_clusters(self):
field_deviations = {
f"10.0.0.{index}": [{"detector": "relationship", "field": "client_ip->context", "score": 40}]
for index in range(1, 6)
}
_cluster_field_deviations(field_deviations)
for entity, deviations in field_deviations.items():
deviation = deviations[0]
self.assertEqual(deviation["cluster_size"], 5)
self.assertEqual(deviation["score"], 25)
self.assertTrue(deviation["cluster_dampened"])
def test_cluster_field_deviations_leaves_small_clusters_untouched(self):
field_deviations = {
f"10.0.0.{index}": [{"detector": "relationship", "field": "client_ip->context", "score": 40}]
for index in range(1, 4)
}
_cluster_field_deviations(field_deviations)
for entity, deviations in field_deviations.items():
deviation = deviations[0]
self.assertEqual(deviation["cluster_size"], 3)
self.assertEqual(deviation["score"], 40)
self.assertNotIn("cluster_dampened", deviation)
def test_cluster_field_deviations_excludes_suppressed_entities_from_cluster_size(self):
field_deviations = {
f"10.0.0.{index}": [{"detector": "relationship", "field": "client_ip->context", "score": 40}]
for index in range(1, 5)
}
field_deviations["10.0.0.99"] = [{"detector": "relationship", "field": "client_ip->context", "score": 40, "feedback": "false_positive"}]
_cluster_field_deviations(field_deviations)
self.assertEqual(field_deviations["10.0.0.1"][0]["cluster_size"], 4)
self.assertNotIn("cluster_dampened", field_deviations["10.0.0.1"][0])
self.assertEqual(field_deviations["10.0.0.99"][0]["score"], 40)
def test_cluster_field_deviations_dampen_large_clusters_in_build_status(self):
with tempfile.TemporaryDirectory() as tmp:
srcips = [f"203.0.113.{index}" for index in range(1, 6)]
log_path = Path(tmp) / "fg.log"
log_path.write_text(
"\n".join(
f"type=utm subtype=ips srcip={ip} dstip=10.0.0.10 service=https action=blocked severity=critical"
for ip in srcips
),
encoding="utf-8",
)
canned_deviations = {
ip: [{"detector": "relationship", "field": "client_ip->context", "stream_id": "utm", "score": 40, "reason": "new relationship: client_ip->context"}]
for ip in srcips
}
with patch("fgai.monitor.BaselineStore.profile_deviations", return_value=canned_deviations):
status = build_status(str(log_path), baseline_path=str(Path(tmp) / "baseline.sqlite3"), incident_path=str(Path(tmp) / "incidents.json"))
for ip in srcips:
deviation = status["field_deviations"][ip][0]
self.assertEqual(deviation["cluster_size"], 5)
self.assertEqual(deviation["score"], 25)
self.assertTrue(deviation["cluster_dampened"])
anomalies_by_subject = {item["subject"]: item for item in status["anomalies"]}
self.assertIn("new relationship: client_ip->context", anomalies_by_subject[srcips[0]]["reasons"])
def test_entity_scope_feedback_suppresses_anomaly_in_build_status(self):
with tempfile.TemporaryDirectory() as tmp:
log_path = Path(tmp) / "fg.log"