Skip to content
Open
30 changes: 25 additions & 5 deletions adiuvare/core/scorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

_weights = {
"payload": 0.40,
"behavior": 0.35,
"identity": 0.25,
"behavior": 0.30,
"identity": 0.15,
Comment thread
Wahid7852 marked this conversation as resolved.
"context": 0.10,
"ip_rep": 0.05,
}

_total_w = sum(_weights.values())
assert _total_w > 0, "default _weights must sum to a positive value"
_weights = {k: v / _total_w for k, v in _weights.items()}
Comment thread
Wahid7852 marked this conversation as resolved.


def compute_score(sig_res: dict[str, SignalResult], snap=None) -> tuple[float, dict[str, float]]:
breakdown: dict[str, float] = {}
Expand All @@ -16,9 +20,25 @@ def compute_score(sig_res: dict[str, SignalResult], snap=None) -> tuple[float, d

weights = dict(_weights)
if snap:
weights["payload"] = snap.payload_weight
weights["behavior"] = snap.behavior_weight
weights["identity"] = snap.identity_weight
snap_weights = {
"payload": snap.payload_weight,
"behavior": snap.behavior_weight,
"identity": snap.identity_weight,
}

# only snap-overridden keys are validated; context and ip_rep carry
# over from _weights which are already normalized and guaranteed positive
for k, v in snap_weights.items():
Comment thread
Wahid7852 marked this conversation as resolved.
if v < 0:
raise ValueError(f"Weight for '{k}' must be non-negative, got {v}")

total_snap = sum(snap_weights.values())
if total_snap <= 0:
raise ValueError("Snap weights sum to zero or below - cannot normalize.")

weights.update(snap_weights)
total_w = sum(weights.values())
Comment thread
Wahid7852 marked this conversation as resolved.
weights = {k: v / total_w for k, v in weights.items()}
Comment thread
Wahid7852 marked this conversation as resolved.

Comment thread
Wahid7852 marked this conversation as resolved.
for name, res in sig_res.items():
weight = weights.get(name, 0.0)
Expand Down
4 changes: 2 additions & 2 deletions tests/test_command_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def test_command_probe_semicolon_detected():

assert gate.passed is True
assert event is not None
assert event.score > 0.35
assert event.score > 0.30
assert "payload" in event.breakdown
assert event.breakdown["payload"] > 0.0

Expand All @@ -26,7 +26,7 @@ def test_command_probe_dollar_detected():

assert gate.passed is True
assert event is not None
assert event.score > 0.35
assert event.score > 0.30
assert "payload" in event.breakdown
assert event.breakdown["payload"] > 0.0

Expand Down
34 changes: 30 additions & 4 deletions tests/test_scorer.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pytest
from adiuvare.core.models import ConfigSnapshot, SignalResult
from adiuvare.core.scorer import compute_score
from adiuvare.core.verdict import compute_verdict
Expand All @@ -11,8 +12,8 @@ def test_score_uses_hardcoded_weights():
}
)

assert round(score, 3) == 0.395
assert round(breakdown["payload"], 3) == 0.28
assert score == pytest.approx(0.38, rel=1e-3)
assert breakdown["payload"] == pytest.approx(0.28, rel=1e-3)


def test_verdict_maps_score_ranges():
Expand All @@ -39,8 +40,7 @@ def test_score_can_use_snapshot_weights():
snap,
)

assert round(score, 3) == 0.45
assert round(breakdown["payload"], 3) == 0.35
assert score == pytest.approx(0.393, rel=1e-3)


def test_verdict_gets_identity_nudge_inline():
Expand All @@ -53,3 +53,29 @@ def test_verdict_gets_identity_nudge_inline():
block_threshold=0.80,
)
assert compute_verdict(0.50, snap, identity_risk=0.70) == "throttle"

def test_identity_heavy_detection():
score, breakdown = compute_score(
{
"identity": SignalResult(score=1.0, reason="known_bad_actor"),
"ip_rep": SignalResult(score=0.8, reason="blacklisted_ip"),
}
)
assert score == pytest.approx(0.20, rel=1e-3)
assert breakdown["identity"] == pytest.approx(0.15, rel=1e-3)

def test_negative_weight_raises():
snap = ConfigSnapshot(
payload_weight=-0.5, behavior_weight=0.5, identity_weight=0.0,
flag_threshold=0.25, throttle_threshold=0.55, block_threshold=0.80,
)
with pytest.raises(ValueError, match="non-negative"):
compute_score({"payload": SignalResult(score=0.7, reason="sql_hit")}, snap)

def test_all_zero_weights_raises():
snap = ConfigSnapshot(
payload_weight=0.0, behavior_weight=0.0, identity_weight=0.0,
flag_threshold=0.25, throttle_threshold=0.55, block_threshold=0.80,
)
with pytest.raises(ValueError, match="sum to zero"):
compute_score({"payload": SignalResult(score=0.7, reason="sql_hit")}, snap)
Loading