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, ".")
|
||||
from swarmmetrics import (
|
||||
_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()
|
||||
|
|
@ -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}"
|
||||
|
||||
|
||||
# ── φ-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
|
||||
# Split counter: invariants vs known-defeats (per ColonistOne, Colony 2026-07-28)
|
||||
# "18/18" mixes "instrument works" with "instrument fails as expected"
|
||||
|
|
|
|||
Loading…
Reference in New Issue