Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions proofs/test_refusal_persistence.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""
Test that refusals are properly recorded.
"""

from s16code.events.governor import AutonomyGovernor
from s16code.events.models import EventEnvelope

def test_refusal_persistence():
"""Test that a refusal is recorded in a durable store."""
governor = AutonomyGovernor()

# Create an event with a self-actor (should be refused)
event = EventEnvelope(
source="test_source",
actor="s16code", # This matches self_actors
id="test_1"
)

# The governor should refuse the event
verdict = governor.admit_event(event)
assert verdict.admitted is False, "Event should be refused"

# The refusal should be queryable
refusals = governor.get_refusal_history()
assert len(refusals) > 0, "No refusals recorded"

print("✅ Refusal persistence test passed.")
27 changes: 23 additions & 4 deletions s16code/events/governor.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,40 @@ def __init__(self, store: Any, *, self_actors: tuple[str, ...] | None = None,

# ------------------------------------------------------------------ intake


def _record_refusal(self, event, reason, verdict_type="refusal"):
"""Record a refusal event in the persistent store."""
refusal_entry = {
"timestamp": datetime.now(UTC).isoformat(),
"event_id": getattr(event, "id", "unknown"),
"source": getattr(event, "source", "unknown"),
"actor": getattr(event, "actor", ""),
"reason": reason,
"type": verdict_type,
}
# Store in a persistent store
self.store.record_refusal(refusal_entry)

def admit_event(self, event: EventEnvelope, *, now: datetime | None = None) -> Verdict:
"""Refuse before the event is even matched against subscriptions."""
moment = now or datetime.now(UTC)

actor = (event.actor or "").strip()
if actor and actor in self.self_actors:
return Verdict(False, f"event was caused by this agent ({actor}); refusing to answer itself",
"self_trigger", {"actor": actor})
reason = f"event was caused by this agent ({actor}); refusing to answer itself"
self._record_refusal(event, reason, "self_trigger")
return Verdict(False, reason, "self_trigger", {"actor": actor})



if self.source_events_per_minute > 0:
recent = self.store.count_recent_events(event.source, since=moment - timedelta(minutes=1))
if recent >= self.source_events_per_minute:
return Verdict(
reason = f"source {event.source!r} exceeded {self.source_events_per_minute} events/minute"
self._record_refusal(event, reason, "source_rate_limit")
return Verdict(
False,
f"source {event.source!r} exceeded {self.source_events_per_minute} events/minute",
reason,
"source_rate_limit",
{"source": event.source, "observed": recent,
"limit": self.source_events_per_minute},
Expand Down