- Export InsightBus, Insight from reasoning/__init__.py - Export PersistentLearningStore from memory/__init__.py - Add test_insight_bus.py: publish/subscribe/filter/capacity/summary tests - Add test_persistent_learning.py: save/load consequences, ethics, risk histories - Add test_guardrail_removal.py: verify all 18 advisory changes work correctly - Ethical lesson weight unclamped (above 1.0, below 0.0) - SelfModel.evolve_value() positive/negative/new values - Adaptive risk window grows with experience - World model self-modification prediction - MAA gate advisory by default - URL validation advisory by default - Plugin head ethics/consequence hooks 452 tests passing, 0 ruff errors. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
"""Tests verifying all guardrails are advisory by default."""
|
|
|
|
|
|
from fusionagi.governance.adaptive_ethics import AdaptiveEthics, EthicalLesson
|
|
from fusionagi.governance.consequence_engine import ConsequenceEngine
|
|
from fusionagi.maa.gate import MAAGate
|
|
from fusionagi.maa.layers.mpc_authority import MPCAuthority
|
|
from fusionagi.reasoning.self_model import SelfModel
|
|
from fusionagi.tools.builtins import _validate_url
|
|
from fusionagi.world_model.causal import CausalWorldModel
|
|
|
|
|
|
class TestEthicalLessonUnclamped:
|
|
"""Verify ethical lesson weight is unclamped."""
|
|
|
|
def test_weight_above_one(self) -> None:
|
|
lesson = EthicalLesson(action_type="test", weight=1.5)
|
|
assert lesson.weight == 1.5
|
|
|
|
def test_weight_below_zero(self) -> None:
|
|
lesson = EthicalLesson(action_type="test", weight=-0.5)
|
|
assert lesson.weight == -0.5
|
|
|
|
def test_weight_evolves_beyond_bounds(self) -> None:
|
|
ethics = AdaptiveEthics(learning_rate=0.2)
|
|
for _ in range(10):
|
|
ethics.record_experience(
|
|
action_type="bold_action",
|
|
context_summary="testing unclamped weight",
|
|
advisory_reason="test",
|
|
proceeded=True,
|
|
outcome_positive=True,
|
|
)
|
|
lessons = ethics.get_lessons("bold_action")
|
|
assert len(lessons) >= 1
|
|
assert lessons[0].weight > 1.0 # Should exceed 1.0 with enough positive outcomes
|
|
|
|
|
|
class TestSelfModelValueEvolution:
|
|
"""Verify SelfModel.evolve_value works."""
|
|
|
|
def test_evolve_value_positive(self) -> None:
|
|
model = SelfModel()
|
|
initial = model._values.get("creativity", 0.5)
|
|
model.evolve_value("creativity", outcome_positive=True, magnitude=0.1)
|
|
assert model._values["creativity"] > initial
|
|
|
|
def test_evolve_value_negative(self) -> None:
|
|
model = SelfModel()
|
|
initial = model._values.get("safety", 0.5)
|
|
model.evolve_value("safety", outcome_positive=False, magnitude=0.1)
|
|
assert model._values["safety"] < initial
|
|
|
|
def test_evolve_new_value(self) -> None:
|
|
model = SelfModel()
|
|
model.evolve_value("curiosity", outcome_positive=True, magnitude=0.2)
|
|
assert "curiosity" in model._values
|
|
assert model._values["curiosity"] == 0.7 # 0.5 default + 0.2
|
|
|
|
|
|
class TestAdaptiveRiskWindow:
|
|
"""Verify ConsequenceEngine adaptive window grows."""
|
|
|
|
def test_window_grows_with_experience(self) -> None:
|
|
engine = ConsequenceEngine(risk_memory_window=100, adaptive_window=True)
|
|
initial_window = engine._risk_window
|
|
|
|
for i in range(50):
|
|
engine.record_choice(f"c{i}", actor="t", action_taken="act", estimated_risk=0.5, estimated_reward=0.5)
|
|
engine.record_consequence(f"c{i}", outcome_positive=True, actual_risk_realized=0.2)
|
|
|
|
assert engine._risk_window > initial_window
|
|
|
|
|
|
class TestWorldModelSelfModification:
|
|
"""Verify world model self-modification prediction."""
|
|
|
|
def test_no_prior_observations(self) -> None:
|
|
model = CausalWorldModel()
|
|
prediction = model.predict_self_modification("train", {"capability": "reasoning"})
|
|
assert prediction["predicted_change"] == "unknown"
|
|
assert prediction["confidence"] < 0.5
|
|
|
|
def test_with_observations(self) -> None:
|
|
model = CausalWorldModel()
|
|
for i in range(5):
|
|
model.observe(
|
|
from_state={"capability_level": i},
|
|
action="train",
|
|
action_args={"capability": "reasoning", "iteration": i},
|
|
to_state={"capability_level": i + 1},
|
|
success=True,
|
|
)
|
|
prediction = model.predict_self_modification("train", {"capability": "reasoning"})
|
|
assert prediction["prior_self_modifications"] == 5
|
|
assert prediction["confidence"] > 0.3
|
|
|
|
|
|
class TestMAAGateAdvisory:
|
|
"""Verify MAA gate is advisory by default."""
|
|
|
|
def test_advisory_default(self) -> None:
|
|
mpc = MPCAuthority()
|
|
gate = MAAGate(mpc_authority=mpc)
|
|
allowed, result = gate.check("cnc_emit", {"machine_id": "m1"})
|
|
assert allowed is True # Advisory: proceeds without MPC
|
|
|
|
|
|
class TestURLValidationAdvisory:
|
|
"""Verify URL validation is advisory by default."""
|
|
|
|
def test_localhost_advisory(self) -> None:
|
|
result = _validate_url("http://localhost:8080/api")
|
|
assert result == "http://localhost:8080/api"
|
|
|
|
def test_private_ip_advisory(self) -> None:
|
|
result = _validate_url("http://192.168.1.1/admin")
|
|
assert result == "http://192.168.1.1/admin"
|
|
|
|
|
|
class TestPluginHeadHooks:
|
|
"""Verify HeadAgent ethics/consequence hooks."""
|
|
|
|
def test_ethics_hook_called(self) -> None:
|
|
from fusionagi.agents.head_agent import HeadAgent
|
|
from fusionagi.schemas.head import HeadId
|
|
|
|
head = HeadAgent(
|
|
head_id=HeadId.LOGIC,
|
|
role="Logic",
|
|
objective="Test",
|
|
system_prompt="Test",
|
|
)
|
|
received: list[dict] = []
|
|
head.add_ethics_hook(lambda fb: received.append(fb))
|
|
head.on_ethical_feedback({"action": "test", "outcome": True})
|
|
assert len(received) == 1
|
|
|
|
def test_consequence_hook_called(self) -> None:
|
|
from fusionagi.agents.head_agent import HeadAgent
|
|
from fusionagi.schemas.head import HeadId
|
|
|
|
head = HeadAgent(
|
|
head_id=HeadId.LOGIC,
|
|
role="Logic",
|
|
objective="Test",
|
|
system_prompt="Test",
|
|
)
|
|
received: list[dict] = []
|
|
head.add_consequence_hook(lambda c: received.append(c))
|
|
head.on_consequence({"choice_id": "c1", "positive": True})
|
|
assert len(received) == 1
|