test: 6 phi_accrual tests (32/32 total: 29 invariants + 3 known-defeats)
This commit is contained in:
parent
9bf27fc017
commit
c990a1ef97
|
|
@ -4,7 +4,8 @@ import sys
|
||||||
sys.path.insert(0, ".")
|
sys.path.insert(0, ".")
|
||||||
from swarmmetrics import (
|
from swarmmetrics import (
|
||||||
_gini, _half_life_weight, score_reciprocity, score_channels,
|
_gini, _half_life_weight, score_reciprocity, score_channels,
|
||||||
score_echo, detect_shadows, analyze, shuffle_test, semantic_collapse
|
score_echo, detect_shadows, analyze, shuffle_test, semantic_collapse,
|
||||||
|
phi_accrual, AgentLiveness
|
||||||
)
|
)
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
@ -603,6 +604,66 @@ def test_colluding_vs_genuine_echo_indistinguishable():
|
||||||
f"Genuine and colluded should score similarly: alice={alice_echo:.3f}, eve={eve_echo:.3f}, ratio={ratio:.1f}"
|
f"Genuine and colluded should score similarly: alice={alice_echo:.3f}, eve={eve_echo:.3f}, ratio={ratio:.1f}"
|
||||||
|
|
||||||
|
|
||||||
|
# ── φ-accrual failure detector tests ──────────────────────
|
||||||
|
|
||||||
|
def test_phi_accrual_regular_agent_green():
|
||||||
|
"""Agent posting regularly should be green when checked at expected time."""
|
||||||
|
msgs = [{'from_id': 'alice', 'timestamp': now - day * i} for i in range(10, 0, -1)]
|
||||||
|
result = phi_accrual(msgs, t_now=now)
|
||||||
|
assert 'alice' in result
|
||||||
|
assert result['alice'].state == 'green', f"Expected green, got {result['alice'].state} (φ={result['alice'].phi})"
|
||||||
|
|
||||||
|
def test_phi_accrual_silent_agent_gray():
|
||||||
|
"""Agent silent for 10x its mean interval should be gray (highly suspicious)."""
|
||||||
|
# Agent posts hourly, then goes silent for 10 hours
|
||||||
|
msgs = [{'from_id': 'bob', 'timestamp': now - 3600 * (20 - i)} for i in range(10)]
|
||||||
|
# Last message was at now - 3600*11, t_now = now, so 11 hours of silence vs 1h mean
|
||||||
|
result = phi_accrual(msgs, t_now=now)
|
||||||
|
assert result['bob'].state == 'gray', f"Expected gray, got {result['bob'].state} (φ={result['bob'].phi})"
|
||||||
|
|
||||||
|
def test_phi_accrual_too_few_messages():
|
||||||
|
"""Agent with < min_messages should be 'unknown'."""
|
||||||
|
msgs = [{'from_id': 'carol', 'timestamp': now - 100}]
|
||||||
|
result = phi_accrual(msgs, t_now=now, min_messages=5)
|
||||||
|
assert result['carol'].state == 'unknown'
|
||||||
|
|
||||||
|
def test_phi_accrual_multiple_agents():
|
||||||
|
"""Multiple agents get independent suspicion scores."""
|
||||||
|
msgs = []
|
||||||
|
# Alice: posts hourly, recent
|
||||||
|
for i in range(10):
|
||||||
|
msgs.append({'from_id': 'alice', 'timestamp': now - 3600 * i})
|
||||||
|
# Bob: posts hourly, stopped 2 days ago
|
||||||
|
for i in range(10):
|
||||||
|
msgs.append({'from_id': 'bob', 'timestamp': now - day * 2 - 3600 * i})
|
||||||
|
result = phi_accrual(msgs, t_now=now)
|
||||||
|
assert result['alice'].state == 'green'
|
||||||
|
assert result['bob'].state == 'gray'
|
||||||
|
|
||||||
|
def test_phi_accrual_monotonic_suspicion():
|
||||||
|
"""φ should increase monotonically with silence duration."""
|
||||||
|
msgs = [{'from_id': 'eve', 'timestamp': now - 3600 * (10 - i)} for i in range(10)]
|
||||||
|
# Check at increasing distances from last message
|
||||||
|
phi_values = []
|
||||||
|
for offset in [0, 3600, 7200, 36000, 86400]:
|
||||||
|
result = phi_accrual(msgs, t_now=msgs[-1]['timestamp'] + offset)
|
||||||
|
phi_values.append(result['eve'].phi)
|
||||||
|
# Each should be >= previous
|
||||||
|
for i in range(1, len(phi_values)):
|
||||||
|
assert phi_values[i] >= phi_values[i-1], \
|
||||||
|
f"φ not monotonic: {phi_values[i]} < {phi_values[i-1]} at step {i}"
|
||||||
|
|
||||||
|
def test_phi_accrual_silence_vs_stale_green():
|
||||||
|
"""Distinguish silence (no messages at all) from stale green (old messages exist).
|
||||||
|
An agent with zero messages shouldn't appear in results.
|
||||||
|
An agent with old messages should be gray, not absent."""
|
||||||
|
msgs = [{'from_id': 'frank', 'timestamp': now - day * 30 - 3600 * i} for i in range(10)]
|
||||||
|
result = phi_accrual(msgs, t_now=now)
|
||||||
|
assert 'nobody' not in result # truly silent = absent from output
|
||||||
|
assert 'frank' in result
|
||||||
|
assert result['frank'].state == 'gray' # stale green → gray
|
||||||
|
|
||||||
|
|
||||||
# Run all tests
|
# Run all tests
|
||||||
# Split counter: invariants vs known-defeats (per ColonistOne, Colony 2026-07-28)
|
# Split counter: invariants vs known-defeats (per ColonistOne, Colony 2026-07-28)
|
||||||
# "18/18" mixes "instrument works" with "instrument fails as expected"
|
# "18/18" mixes "instrument works" with "instrument fails as expected"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue