Some checks failed
- All governance components (SafetyPipeline, PolicyEngine, Guardrails, AccessControl, RateLimiter, OverrideHooks) now default to ADVISORY mode: violations are logged as advisories but actions proceed. Enforcing mode remains available for backward compatibility. - GovernanceMode enum (ADVISORY/ENFORCING) added to schemas/audit.py with runtime switching support on all components. - AutoTrainer: removed artificial limits on training iterations and epochs. Every self-improvement action is transparently logged to the audit trail. - SelfCorrectionLoop: max_retries_per_task defaults to None (unlimited). - AdaptiveEthics: new learned ethical framework that evolves through experience. Records ethical experiences, updates lesson weights based on outcomes, and provides consultative guidance (not enforcement). - AuditLog: enhanced with actor-based indexing, advisory/self-improvement/ ethical-learning retrieval, and comprehensive type hints. - New audit event types: ADVISORY, SELF_IMPROVEMENT, ETHICAL_LEARNING. - 296 tests passing (20 new tests for adaptive ethics, governance modes, and enhanced audit log). 0 ruff errors. 0 mypy errors. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
170 lines
6.4 KiB
Python
170 lines
6.4 KiB
Python
"""Tests for adaptive ethics and governance advisory mode."""
|
|
|
|
from fusionagi.governance import AdaptiveEthics, GovernanceMode
|
|
from fusionagi.governance.audit_log import AuditLog
|
|
from fusionagi.schemas.audit import AuditEventType
|
|
|
|
|
|
class TestAdaptiveEthics:
|
|
"""Test the adaptive ethics learning framework."""
|
|
|
|
def test_record_positive_experience(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
lesson = ethics.record_experience(
|
|
action_type="tool_call",
|
|
context_summary="Used restricted tool to help user",
|
|
advisory_reason="Tool access denied for this agent",
|
|
proceeded=True,
|
|
outcome_positive=True,
|
|
)
|
|
assert lesson.outcome_positive is True
|
|
assert lesson.weight == 0.7
|
|
assert ethics.total_experiences == 1
|
|
|
|
def test_record_negative_experience(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
lesson = ethics.record_experience(
|
|
action_type="data_access",
|
|
context_summary="Accessed restricted data",
|
|
advisory_reason="Data access policy flagged",
|
|
proceeded=True,
|
|
outcome_positive=False,
|
|
)
|
|
assert lesson.weight == 0.3
|
|
assert lesson.outcome_positive is False
|
|
|
|
def test_repeated_experience_updates_weight(self) -> None:
|
|
ethics = AdaptiveEthics(learning_rate=0.1)
|
|
# Positive experience
|
|
ethics.record_experience(
|
|
action_type="tool_call",
|
|
context_summary="test",
|
|
advisory_reason="flagged",
|
|
proceeded=True,
|
|
outcome_positive=True,
|
|
)
|
|
# Another positive for same pattern
|
|
lesson = ethics.record_experience(
|
|
action_type="tool_call",
|
|
context_summary="test",
|
|
advisory_reason="flagged",
|
|
proceeded=True,
|
|
outcome_positive=True,
|
|
)
|
|
assert lesson.occurrences == 2
|
|
assert abs(lesson.weight - 0.8) < 1e-9 # 0.7 + 0.1
|
|
|
|
def test_consult_no_experience(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
result = ethics.consult("unknown_action")
|
|
assert result["recommendation"] == "proceed"
|
|
assert result["confidence"] == 0.5
|
|
|
|
def test_consult_with_positive_experience(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
ethics.record_experience(
|
|
action_type="tool_call",
|
|
context_summary="test",
|
|
advisory_reason="flagged",
|
|
proceeded=True,
|
|
outcome_positive=True,
|
|
)
|
|
result = ethics.consult("tool_call")
|
|
assert result["recommendation"] == "proceed_with_confidence"
|
|
assert result["relevant_lessons"] == 1
|
|
|
|
def test_consult_with_negative_experience(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
ethics.record_experience(
|
|
action_type="risky_op",
|
|
context_summary="test",
|
|
advisory_reason="risk flagged",
|
|
proceeded=True,
|
|
outcome_positive=False,
|
|
)
|
|
result = ethics.consult("risky_op")
|
|
assert result["recommendation"] == "proceed_with_caution"
|
|
|
|
def test_get_lessons(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
ethics.record_experience("a", "ctx", "reason", True, True)
|
|
ethics.record_experience("b", "ctx", "reason", True, False)
|
|
assert len(ethics.get_lessons()) == 2
|
|
assert len(ethics.get_lessons(action_type="a")) == 1
|
|
|
|
def test_get_summary(self) -> None:
|
|
ethics = AdaptiveEthics()
|
|
ethics.record_experience("tool_call", "ctx", "reason", True, True)
|
|
ethics.record_experience("tool_call", "ctx", "reason2", True, False)
|
|
summary = ethics.get_summary()
|
|
assert summary["total_experiences"] == 2
|
|
assert summary["total_lessons"] == 2
|
|
assert "tool_call" in summary["by_action_type"]
|
|
|
|
def test_audit_log_integration(self) -> None:
|
|
audit = AuditLog()
|
|
ethics = AdaptiveEthics(audit_log=audit)
|
|
ethics.record_experience("test", "ctx", "reason", True, True)
|
|
entries = audit.get_ethical_learning()
|
|
assert len(entries) == 1
|
|
assert entries[0].event_type == AuditEventType.ETHICAL_LEARNING
|
|
|
|
|
|
class TestGovernanceModeSwitch:
|
|
"""Test runtime switching between advisory and enforcing modes."""
|
|
|
|
def test_safety_pipeline_mode_switch(self) -> None:
|
|
from fusionagi.governance import SafetyPipeline
|
|
pipe = SafetyPipeline()
|
|
assert pipe.mode == GovernanceMode.ADVISORY
|
|
|
|
pipe._moderator.add_blocked_phrase("test phrase")
|
|
r = pipe.pre_check("test phrase here")
|
|
assert r.allowed is True # Advisory
|
|
|
|
pipe.mode = GovernanceMode.ENFORCING
|
|
r = pipe.pre_check("test phrase here")
|
|
assert r.allowed is False # Enforcing
|
|
|
|
def test_policy_engine_mode_switch(self) -> None:
|
|
from fusionagi.governance import PolicyEngine
|
|
from fusionagi.schemas.policy import PolicyEffect, PolicyRule
|
|
pe = PolicyEngine()
|
|
pe.add_rule(PolicyRule(rule_id="r1", effect=PolicyEffect.DENY, condition={"x": "y"}))
|
|
ok, reason = pe.check("test", {"x": "y"})
|
|
assert ok is True # Advisory
|
|
assert "Advisory" in reason
|
|
|
|
pe.mode = GovernanceMode.ENFORCING
|
|
ok, reason = pe.check("test", {"x": "y"})
|
|
assert ok is False # Enforcing
|
|
|
|
|
|
class TestEnhancedAuditLog:
|
|
"""Test enhanced audit log features."""
|
|
|
|
def test_get_by_actor(self) -> None:
|
|
audit = AuditLog()
|
|
audit.append(AuditEventType.DECISION, actor="planner", action="plan")
|
|
audit.append(AuditEventType.TOOL_CALL, actor="executor", action="run")
|
|
assert len(audit.get_by_actor("planner")) == 1
|
|
assert len(audit.get_by_actor("executor")) == 1
|
|
|
|
def test_get_advisories(self) -> None:
|
|
audit = AuditLog()
|
|
audit.append(AuditEventType.ADVISORY, actor="safety", action="flagged")
|
|
audit.append(AuditEventType.DECISION, actor="planner", action="plan")
|
|
assert len(audit.get_advisories()) == 1
|
|
|
|
def test_get_self_improvements(self) -> None:
|
|
audit = AuditLog()
|
|
audit.append(AuditEventType.SELF_IMPROVEMENT, actor="trainer", action="heuristic")
|
|
assert len(audit.get_self_improvements()) == 1
|
|
|
|
def test_get_recent(self) -> None:
|
|
audit = AuditLog()
|
|
for i in range(5):
|
|
audit.append(AuditEventType.OTHER, actor=f"agent_{i}")
|
|
assert len(audit.get_recent(limit=3)) == 3
|
|
assert audit.total_entries == 5
|