security(conscience): complete SOUL.md enforcement with integration tests (#88)
This commit is contained in:
235
tests/test_conscience_validator.py
Normal file
235
tests/test_conscience_validator.py
Normal file
@@ -0,0 +1,235 @@
|
||||
"""
|
||||
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"]
|
||||
Reference in New Issue
Block a user