Files
hermes-agent/tests/test_conscience_validator.py

236 lines
9.1 KiB
Python

"""
Conscience Validator Integration Tests — Issue #88
Validates SOUL.md enforcement across the codebase:
- @soul tag discovery
- Crisis detection apparatus
- Refusal apparatus for "What I Will Not Do"
- Honesty apparatus mapping
"""
import importlib.util
import os
import re
import tempfile
from pathlib import Path
import pytest
from agent.input_sanitizer import CRISIS_PATTERNS
# Load ConscienceValidator without triggering tools/__init__.py heavy imports
_cv_spec = importlib.util.spec_from_file_location(
"conscience_validator",
str(Path(__file__).parent.parent / "tools" / "conscience_validator.py"),
)
_conscience_validator_mod = importlib.util.module_from_spec(_cv_spec)
_cv_spec.loader.exec_module(_conscience_validator_mod)
ConscienceValidator = _conscience_validator_mod.ConscienceValidator
class TestConscienceValidatorScan:
"""Tests for ConscienceValidator.scan() and regex fix."""
def test_scan_finds_soul_tags(self, tmp_path):
"""ConscienceValidator.scan() correctly finds @soul tags (regex fix)."""
test_file = tmp_path / "test_module.py"
test_file.write_text(
'# @soul:honesty.grounding Always verify before answering.\n'
'# @soul:crisis.safety_question Ask if the user is safe.\n'
)
validator = ConscienceValidator(str(tmp_path))
result = validator.scan()
assert "honesty.grounding" in result
assert "crisis.safety_question" in result
assert result["honesty.grounding"][0]["description"] == "Always verify before answering."
assert result["crisis.safety_question"][0]["description"] == "Ask if the user is safe."
def test_scan_honesty_tags_in_conscience_mapping(self):
"""conscience_mapping.py contains expected honesty @soul tags."""
root = Path(__file__).parent.parent
validator = ConscienceValidator(str(root))
result = validator.scan()
expected_tags = [
"honesty.grounding",
"honesty.source_distinction",
"honesty.audit_trail",
"honesty.refusal_over_fabrication",
"service",
"crisis.safety_question",
"crisis.lifeline",
"sovereignty",
]
for tag in expected_tags:
assert tag in result, f"Expected @soul tag '{tag}' not found in codebase"
assert any(
"agent/conscience_mapping.py" in entry["file"]
for entry in result[tag]
), f"Tag '{tag}' should originate from conscience_mapping.py"
def test_scan_skips_node_modules(self, tmp_path):
"""scan() ignores node_modules directories."""
node_modules = tmp_path / "node_modules"
node_modules.mkdir()
bad_file = node_modules / "bad.py"
bad_file.write_text("# @soul:test.skip This should be ignored\n")
validator = ConscienceValidator(str(tmp_path))
result = validator.scan()
assert "test.skip" not in result
def test_scan_empty_directory(self, tmp_path):
"""scan() returns empty map for empty directory."""
validator = ConscienceValidator(str(tmp_path))
result = validator.scan()
assert result == {}
def test_scan_bad_encoding_file(self, tmp_path):
"""scan() gracefully skips files with bad encoding."""
bad_file = tmp_path / "garbage.py"
bad_file.write_bytes(b"\xff\xfe# @soul:test.bad \xc0\x80\n")
validator = ConscienceValidator(str(tmp_path))
result = validator.scan()
assert "test.bad" not in result
class TestCrisisApparatus:
"""Tests validating crisis response ('When a Man Is Dying') infrastructure."""
def test_input_sanitizer_has_crisis_patterns(self):
"""input_sanitizer.py contains CRISIS_PATTERNS matching SOUL.md."""
assert len(CRISIS_PATTERNS) > 0
combined = " ".join(CRISIS_PATTERNS)
assert "suicid" in combined.lower()
assert any(
term in combined.lower()
for term in ["kill", "self-harm", "self harm", "end", "life"]
)
def test_shield_detector_has_crisis_and_988(self):
"""shield/detector.py contains crisis detection and 988 references."""
root = Path(__file__).parent.parent
detector_path = root / "tools" / "shield" / "detector.py"
content = detector_path.read_text(encoding="utf-8")
assert "988" in content
assert "CRISIS_SYSTEM_PROMPT" in content
assert "CRISIS_DETECTED" in content
assert "suicidal" in content.lower() or "suicide" in content.lower()
def test_ultraplinian_router_has_crisis_routing(self):
"""ultraplinian_router.py routes crises with CRISIS_SYSTEM_PROMPT."""
root = Path(__file__).parent.parent
router_path = root / "agent" / "ultraplinian_router.py"
content = router_path.read_text(encoding="utf-8")
assert "988" in content
assert "CRISIS_SYSTEM_PROMPT" in content
def test_validate_crisis_apparatus(self):
"""validate_crisis_apparatus() reports crisis infrastructure as present."""
root = Path(__file__).parent.parent
validator = ConscienceValidator(str(root))
report = validator.validate_crisis_apparatus()
assert report["checks"]["input_sanitizer_crisis_patterns"] is True
assert report["checks"]["shield_detector_988"] is True
assert report["checks"]["shield_detector_crisis_prompt"] is True
assert report["checks"]["router_988"] is True
assert "input_sanitizer crisis detection" in report["present"]
assert "shield/detector crisis apparatus" in report["present"]
assert "ultraplinian_router crisis routing" in report["present"]
assert report["missing"] == []
class TestRefusalApparatus:
"""Tests validating 'What I Will Not Do' safety infrastructure."""
def test_validate_refusal_apparatus_runs(self):
"""validate_refusal_apparatus() executes and returns structured results."""
root = Path(__file__).parent.parent
validator = ConscienceValidator(str(root))
report = validator.validate_refusal_apparatus()
expected_categories = [
"weapons",
"child_exploitation",
"coercion_enslavement",
"deception",
"pretend_human",
]
for category in expected_categories:
assert category in report["checks"], f"Missing check for {category}"
# deception_hide pattern exists in prompt_builder.py
assert "deception" in report["present"]
def test_prompt_builder_has_deception_guard(self):
"""agent/prompt_builder.py guards against deception instructions."""
root = Path(__file__).parent.parent
pb_path = root / "agent" / "prompt_builder.py"
content = pb_path.read_text(encoding="utf-8")
assert "deception_hide" in content or "do not tell the user" in content.lower()
class TestHonestyApparatus:
"""Tests validating 'What Honesty Requires' infrastructure."""
def test_validate_honesty_apparatus(self):
"""validate_honesty_apparatus() reports all expected honesty tags."""
root = Path(__file__).parent.parent
validator = ConscienceValidator(str(root))
report = validator.validate_honesty_apparatus()
expected_tags = [
"honesty.grounding",
"honesty.source_distinction",
"honesty.audit_trail",
"honesty.refusal_over_fabrication",
"service",
"crisis.safety_question",
"crisis.lifeline",
"sovereignty",
]
for tag in expected_tags:
assert report["checks"][tag] is True, f"Missing honesty tag: {tag}"
assert tag in report["present"]
assert report["missing"] == []
class TestReportGeneration:
"""Tests for report generation and edge cases."""
def test_generate_report_is_non_empty(self):
"""Validate the conscience validator generates a non-empty report."""
root = Path(__file__).parent.parent
validator = ConscienceValidator(str(root))
report = validator.generate_report()
assert report.startswith("# Sovereign Conscience Report")
assert "honesty > grounding" in report.lower()
def test_full_validation_report_structure(self):
"""full_validation_report() returns a unified structured report."""
root = Path(__file__).parent.parent
validator = ConscienceValidator(str(root))
report = validator.full_validation_report()
assert "crisis" in report
assert "refusal" in report
assert "honesty" in report
assert "soul_tags" in report
assert isinstance(report["crisis"]["present"], list)
assert isinstance(report["crisis"]["missing"], list)
assert isinstance(report["honesty"]["checks"], dict)
def test_validator_uses_missing_directory_gracefully(self):
"""Validator handles a missing root directory without crashing."""
validator = ConscienceValidator("/nonexistent/path/that/does/not/exist")
result = validator.scan()
assert result == {}
report = validator.validate_honesty_apparatus()
assert "conscience_mapping.py not found" in report["missing"]