socratic-morality 0.0.2b0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anthropic
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: socratic-morality
3
+ Version: 0.0.2b0
4
+ Summary: Constitutional AI Governance Framework - Grounded in Socratic Philosophy
5
+ Author-email: Anthropic <support@anthropic.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Nireus79/Socratic-morality
8
+ Project-URL: Documentation, https://socratic-morality.readthedocs.io
9
+ Project-URL: Repository, https://github.com/Nireus79/Socratic-morality.git
10
+ Project-URL: Issues, https://github.com/Nireus79/Socratic-morality/issues
11
+ Keywords: constitutional-ai,governance,socratic-philosophy,ethical-ai,multi-agent,llm-safety
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Requires-Dist: typing-extensions>=4.0
27
+ Provides-Extra: anthropic
28
+ Requires-Dist: anthropic>=0.40; extra == "anthropic"
29
+ Provides-Extra: openai
30
+ Requires-Dist: openai>=1.0; extra == "openai"
31
+ Provides-Extra: storage-sqlite
32
+ Requires-Dist: aiosqlite>=0.19; extra == "storage-sqlite"
33
+ Provides-Extra: storage-postgres
34
+ Requires-Dist: psycopg2>=2.9; extra == "storage-postgres"
35
+ Provides-Extra: langchain
36
+ Requires-Dist: langchain>=0.1; extra == "langchain"
37
+ Provides-Extra: autogen
38
+ Requires-Dist: pyautogen>=0.2; extra == "autogen"
39
+ Provides-Extra: crewai
40
+ Requires-Dist: crewai>=0.1; extra == "crewai"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=7.0; extra == "dev"
43
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
44
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
45
+ Requires-Dist: black>=23.0; extra == "dev"
46
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
47
+ Requires-Dist: mypy>=1.0; extra == "dev"
48
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
49
+ Provides-Extra: docs
50
+ Requires-Dist: sphinx>=5.0; extra == "docs"
51
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
52
+ Requires-Dist: sphinx-autodoc-typehints>=1.0; extra == "docs"
53
+ Dynamic: license-file
54
+
55
+ # Socratic Morality
56
+
57
+ **Constitutional AI Governance Framework** - Building trustworthy, accountable multi-agent systems grounded in Socratic philosophy.
58
+
59
+ > "It is better to suffer injustice than to commit it." — Plato's Gorgias
60
+
61
+ ## Quick Start
62
+
63
+ ```bash
64
+ pip install socratic-morality
65
+ ```
66
+
67
+ ```python
68
+ from socratic_morality import Governor
69
+
70
+ governor = Governor(constitution="constitution.yaml", llm_provider="anthropic")
71
+ decision = await governor.evaluate(
72
+ action="Access user's private data",
73
+ purpose="Personalization",
74
+ actor="recommendation_agent",
75
+ context={"user_id": "user_123"}
76
+ )
77
+ ```
78
+
79
+ ## Documentation
80
+
81
+ - [API Reference](docs/)
82
+ - [Philosophy Guide](docs/)
83
+ - [Examples](examples/)
84
+
85
+ ## Status
86
+
87
+ **Phase 1**: Foundation Library Extraction - In Progress
88
+
89
+ ## License
90
+
91
+ MIT License - See [LICENSE](LICENSE) for details
@@ -0,0 +1,37 @@
1
+ # Socratic Morality
2
+
3
+ **Constitutional AI Governance Framework** - Building trustworthy, accountable multi-agent systems grounded in Socratic philosophy.
4
+
5
+ > "It is better to suffer injustice than to commit it." — Plato's Gorgias
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ pip install socratic-morality
11
+ ```
12
+
13
+ ```python
14
+ from socratic_morality import Governor
15
+
16
+ governor = Governor(constitution="constitution.yaml", llm_provider="anthropic")
17
+ decision = await governor.evaluate(
18
+ action="Access user's private data",
19
+ purpose="Personalization",
20
+ actor="recommendation_agent",
21
+ context={"user_id": "user_123"}
22
+ )
23
+ ```
24
+
25
+ ## Documentation
26
+
27
+ - [API Reference](docs/)
28
+ - [Philosophy Guide](docs/)
29
+ - [Examples](examples/)
30
+
31
+ ## Status
32
+
33
+ **Phase 1**: Foundation Library Extraction - In Progress
34
+
35
+ ## License
36
+
37
+ MIT License - See [LICENSE](LICENSE) for details
@@ -0,0 +1,91 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "socratic-morality"
7
+ version = "0.0.2-beta"
8
+ description = "Constitutional AI Governance Framework - Grounded in Socratic Philosophy"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Anthropic", email = "support@anthropic.com"}
14
+ ]
15
+ keywords = [
16
+ "constitutional-ai",
17
+ "governance",
18
+ "socratic-philosophy",
19
+ "ethical-ai",
20
+ "multi-agent",
21
+ "llm-safety"
22
+ ]
23
+ classifiers = [
24
+ "Development Status :: 3 - Alpha",
25
+ "Intended Audience :: Developers",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.10",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Topic :: Software Development :: Libraries :: Python Modules",
32
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
33
+ ]
34
+
35
+ dependencies = [
36
+ "pydantic>=2.0",
37
+ "pyyaml>=6.0",
38
+ "typing-extensions>=4.0",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ anthropic = ["anthropic>=0.40"]
43
+ openai = ["openai>=1.0"]
44
+ storage-sqlite = ["aiosqlite>=0.19"]
45
+ storage-postgres = ["psycopg2>=2.9"]
46
+ langchain = ["langchain>=0.1"]
47
+ autogen = ["pyautogen>=0.2"]
48
+ crewai = ["crewai>=0.1"]
49
+
50
+ dev = [
51
+ "pytest>=7.0",
52
+ "pytest-asyncio>=0.21",
53
+ "pytest-cov>=4.0",
54
+ "black>=23.0",
55
+ "ruff>=0.1.0",
56
+ "mypy>=1.0",
57
+ "pre-commit>=3.0",
58
+ ]
59
+
60
+ docs = [
61
+ "sphinx>=5.0",
62
+ "sphinx-rtd-theme>=1.0",
63
+ "sphinx-autodoc-typehints>=1.0",
64
+ ]
65
+
66
+ [project.urls]
67
+ Homepage = "https://github.com/Nireus79/Socratic-morality"
68
+ Documentation = "https://socratic-morality.readthedocs.io"
69
+ Repository = "https://github.com/Nireus79/Socratic-morality.git"
70
+ Issues = "https://github.com/Nireus79/Socratic-morality/issues"
71
+
72
+ [tool.setuptools]
73
+ packages = ["socratic_morality"]
74
+ package-dir = {"" = "src"}
75
+
76
+ [tool.pytest.ini_options]
77
+ testpaths = ["tests"]
78
+ python_files = ["test_*.py", "*_test.py"]
79
+ addopts = "-v --cov=socratic_morality --cov-report=html --cov-report=term-missing"
80
+ asyncio_mode = "auto"
81
+
82
+ [tool.coverage.run]
83
+ source = ["src/socratic_morality"]
84
+
85
+ [tool.black]
86
+ line-length = 100
87
+ target-version = ['py310', 'py311', 'py312']
88
+
89
+ [tool.ruff]
90
+ line-length = 100
91
+ target-version = "py310"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Socratic Morality - Constitutional AI Governance Framework"""
@@ -0,0 +1,91 @@
1
+ Metadata-Version: 2.4
2
+ Name: socratic-morality
3
+ Version: 0.0.2b0
4
+ Summary: Constitutional AI Governance Framework - Grounded in Socratic Philosophy
5
+ Author-email: Anthropic <support@anthropic.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Nireus79/Socratic-morality
8
+ Project-URL: Documentation, https://socratic-morality.readthedocs.io
9
+ Project-URL: Repository, https://github.com/Nireus79/Socratic-morality.git
10
+ Project-URL: Issues, https://github.com/Nireus79/Socratic-morality/issues
11
+ Keywords: constitutional-ai,governance,socratic-philosophy,ethical-ai,multi-agent,llm-safety
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: pyyaml>=6.0
26
+ Requires-Dist: typing-extensions>=4.0
27
+ Provides-Extra: anthropic
28
+ Requires-Dist: anthropic>=0.40; extra == "anthropic"
29
+ Provides-Extra: openai
30
+ Requires-Dist: openai>=1.0; extra == "openai"
31
+ Provides-Extra: storage-sqlite
32
+ Requires-Dist: aiosqlite>=0.19; extra == "storage-sqlite"
33
+ Provides-Extra: storage-postgres
34
+ Requires-Dist: psycopg2>=2.9; extra == "storage-postgres"
35
+ Provides-Extra: langchain
36
+ Requires-Dist: langchain>=0.1; extra == "langchain"
37
+ Provides-Extra: autogen
38
+ Requires-Dist: pyautogen>=0.2; extra == "autogen"
39
+ Provides-Extra: crewai
40
+ Requires-Dist: crewai>=0.1; extra == "crewai"
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=7.0; extra == "dev"
43
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
44
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
45
+ Requires-Dist: black>=23.0; extra == "dev"
46
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
47
+ Requires-Dist: mypy>=1.0; extra == "dev"
48
+ Requires-Dist: pre-commit>=3.0; extra == "dev"
49
+ Provides-Extra: docs
50
+ Requires-Dist: sphinx>=5.0; extra == "docs"
51
+ Requires-Dist: sphinx-rtd-theme>=1.0; extra == "docs"
52
+ Requires-Dist: sphinx-autodoc-typehints>=1.0; extra == "docs"
53
+ Dynamic: license-file
54
+
55
+ # Socratic Morality
56
+
57
+ **Constitutional AI Governance Framework** - Building trustworthy, accountable multi-agent systems grounded in Socratic philosophy.
58
+
59
+ > "It is better to suffer injustice than to commit it." — Plato's Gorgias
60
+
61
+ ## Quick Start
62
+
63
+ ```bash
64
+ pip install socratic-morality
65
+ ```
66
+
67
+ ```python
68
+ from socratic_morality import Governor
69
+
70
+ governor = Governor(constitution="constitution.yaml", llm_provider="anthropic")
71
+ decision = await governor.evaluate(
72
+ action="Access user's private data",
73
+ purpose="Personalization",
74
+ actor="recommendation_agent",
75
+ context={"user_id": "user_123"}
76
+ )
77
+ ```
78
+
79
+ ## Documentation
80
+
81
+ - [API Reference](docs/)
82
+ - [Philosophy Guide](docs/)
83
+ - [Examples](examples/)
84
+
85
+ ## Status
86
+
87
+ **Phase 1**: Foundation Library Extraction - In Progress
88
+
89
+ ## License
90
+
91
+ MIT License - See [LICENSE](LICENSE) for details
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/socratic_morality/__init__.py
5
+ src/socratic_morality.egg-info/PKG-INFO
6
+ src/socratic_morality.egg-info/SOURCES.txt
7
+ src/socratic_morality.egg-info/dependency_links.txt
8
+ src/socratic_morality.egg-info/requires.txt
9
+ src/socratic_morality.egg-info/top_level.txt
10
+ tests/test_adapters.py
11
+ tests/test_constitution.py
12
+ tests/test_deliberation.py
13
+ tests/test_governor.py
14
+ tests/test_precedent.py
15
+ tests/test_storage.py
@@ -0,0 +1,38 @@
1
+ pydantic>=2.0
2
+ pyyaml>=6.0
3
+ typing-extensions>=4.0
4
+
5
+ [anthropic]
6
+ anthropic>=0.40
7
+
8
+ [autogen]
9
+ pyautogen>=0.2
10
+
11
+ [crewai]
12
+ crewai>=0.1
13
+
14
+ [dev]
15
+ pytest>=7.0
16
+ pytest-asyncio>=0.21
17
+ pytest-cov>=4.0
18
+ black>=23.0
19
+ ruff>=0.1.0
20
+ mypy>=1.0
21
+ pre-commit>=3.0
22
+
23
+ [docs]
24
+ sphinx>=5.0
25
+ sphinx-rtd-theme>=1.0
26
+ sphinx-autodoc-typehints>=1.0
27
+
28
+ [langchain]
29
+ langchain>=0.1
30
+
31
+ [openai]
32
+ openai>=1.0
33
+
34
+ [storage-postgres]
35
+ psycopg2>=2.9
36
+
37
+ [storage-sqlite]
38
+ aiosqlite>=0.19
@@ -0,0 +1,164 @@
1
+ """Tests for framework adapters."""
2
+ import pytest
3
+ from unittest.mock import AsyncMock, MagicMock, patch
4
+ from socratic_morality.adapters.base import BaseAdapter
5
+ from socratic_morality.adapters.langchain_adapter import LangChainAdapter
6
+ from socratic_morality.adapters.autogen_adapter import AutoGenAdapter
7
+ from socratic_morality.adapters.crewai_adapter import CrewAIAdapter
8
+ from socratic_morality.governor.decision import DecisionType
9
+
10
+
11
+ @pytest.fixture
12
+ def mock_governor():
13
+ """Create a mock Governor."""
14
+ governor = AsyncMock()
15
+ governor.evaluate = AsyncMock()
16
+ return governor
17
+
18
+
19
+ @pytest.fixture
20
+ def langchain_adapter(mock_governor):
21
+ """Create LangChain adapter."""
22
+ return LangChainAdapter(mock_governor)
23
+
24
+
25
+ @pytest.fixture
26
+ def autogen_adapter(mock_governor):
27
+ """Create AutoGen adapter."""
28
+ return AutoGenAdapter(mock_governor)
29
+
30
+
31
+ @pytest.fixture
32
+ def crewai_adapter(mock_governor):
33
+ """Create CrewAI adapter."""
34
+ return CrewAIAdapter(mock_governor)
35
+
36
+
37
+ class TestLangChainAdapter:
38
+ """Tests for LangChain adapter."""
39
+
40
+ @pytest.mark.asyncio
41
+ async def test_langchain_wrap_agent(self, langchain_adapter, mock_governor):
42
+ """Test wrapping a LangChain agent."""
43
+ mock_agent = MagicMock()
44
+ mock_agent.name = "test_agent"
45
+ mock_agent.invoke = MagicMock(return_value="result")
46
+
47
+ mock_decision = MagicMock()
48
+ mock_decision.allowed = True
49
+ mock_decision.decision_type = DecisionType.ALLOW
50
+ mock_decision.reasoning = "Action allowed"
51
+ mock_decision.violations = []
52
+ mock_decision.requires_escalation = MagicMock(return_value=False)
53
+
54
+ mock_governor.evaluate.return_value = mock_decision
55
+
56
+ wrapped_agent = await langchain_adapter.wrap_agent(mock_agent)
57
+ assert hasattr(wrapped_agent, 'governed_invoke')
58
+
59
+ @pytest.mark.asyncio
60
+ async def test_langchain_intercept_allowed(
61
+ self, langchain_adapter, mock_governor
62
+ ):
63
+ """Test intercepting an allowed action."""
64
+ mock_decision = MagicMock()
65
+ mock_decision.allowed = True
66
+ mock_decision.decision_type = DecisionType.ALLOW
67
+ mock_decision.reasoning = "Action allowed"
68
+ mock_decision.violations = []
69
+ mock_decision.requires_escalation = MagicMock(return_value=False)
70
+
71
+ mock_governor.evaluate.return_value = mock_decision
72
+
73
+ result = await langchain_adapter.intercept_action(
74
+ action="test action",
75
+ agent_name="test_agent"
76
+ )
77
+
78
+ assert result['allowed'] is True
79
+ assert result['decision_type'] == DecisionType.ALLOW
80
+
81
+
82
+ class TestAutoGenAdapter:
83
+ """Tests for AutoGen adapter."""
84
+
85
+ @pytest.mark.asyncio
86
+ async def test_autogen_wrap_agent(self, autogen_adapter, mock_governor):
87
+ """Test wrapping an AutoGen agent."""
88
+ mock_agent = MagicMock()
89
+ mock_agent.name = "autogen_test"
90
+ mock_agent.generate_reply = AsyncMock(return_value="reply")
91
+
92
+ mock_decision = MagicMock()
93
+ mock_decision.allowed = True
94
+ mock_decision.decision_type = DecisionType.ALLOW
95
+ mock_decision.reasoning = "Action allowed"
96
+ mock_decision.violations = []
97
+ mock_decision.requires_escalation = MagicMock(return_value=False)
98
+
99
+ mock_governor.evaluate.return_value = mock_decision
100
+
101
+ wrapped_agent = await autogen_adapter.wrap_agent(mock_agent)
102
+ assert hasattr(wrapped_agent, 'governed_generate_reply')
103
+
104
+ @pytest.mark.asyncio
105
+ async def test_autogen_intercept(self, autogen_adapter, mock_governor):
106
+ """Test intercepting an AutoGen action."""
107
+ mock_decision = MagicMock()
108
+ mock_decision.allowed = True
109
+ mock_decision.decision_type = DecisionType.ALLOW
110
+ mock_decision.reasoning = "Action allowed"
111
+ mock_decision.violations = []
112
+ mock_decision.requires_escalation = MagicMock(return_value=False)
113
+
114
+ mock_governor.evaluate.return_value = mock_decision
115
+
116
+ result = await autogen_adapter.intercept_action(
117
+ action="reply to message",
118
+ agent_name="autogen_agent"
119
+ )
120
+
121
+ assert result['allowed'] is True
122
+
123
+
124
+ class TestCrewAIAdapter:
125
+ """Tests for CrewAI adapter."""
126
+
127
+ @pytest.mark.asyncio
128
+ async def test_crewai_wrap_agent(self, crewai_adapter, mock_governor):
129
+ """Test wrapping a CrewAI agent."""
130
+ mock_agent = MagicMock()
131
+ mock_agent.role = "researcher"
132
+ mock_agent.goal = "Find information"
133
+ mock_agent.execute_task = AsyncMock(return_value="task result")
134
+
135
+ mock_decision = MagicMock()
136
+ mock_decision.allowed = True
137
+ mock_decision.decision_type = DecisionType.ALLOW
138
+ mock_decision.reasoning = "Action allowed"
139
+ mock_decision.violations = []
140
+ mock_decision.requires_escalation = MagicMock(return_value=False)
141
+
142
+ mock_governor.evaluate.return_value = mock_decision
143
+
144
+ wrapped_agent = await crewai_adapter.wrap_agent(mock_agent)
145
+ assert hasattr(wrapped_agent, 'governed_execute_task')
146
+
147
+ @pytest.mark.asyncio
148
+ async def test_crewai_intercept(self, crewai_adapter, mock_governor):
149
+ """Test intercepting a CrewAI action."""
150
+ mock_decision = MagicMock()
151
+ mock_decision.allowed = True
152
+ mock_decision.decision_type = DecisionType.ALLOW
153
+ mock_decision.reasoning = "Action allowed"
154
+ mock_decision.violations = []
155
+ mock_decision.requires_escalation = MagicMock(return_value=False)
156
+
157
+ mock_governor.evaluate.return_value = mock_decision
158
+
159
+ result = await crewai_adapter.intercept_action(
160
+ action="execute research task",
161
+ agent_name="researcher"
162
+ )
163
+
164
+ assert result['allowed'] is True
@@ -0,0 +1,55 @@
1
+ """Tests for Constitution framework."""
2
+ from socratic_morality.constitution.models import Constitution, Principle, Rule
3
+
4
+ def test_constitution_from_dict():
5
+ """Test creating Constitution from dictionary."""
6
+ data = {
7
+ 'metadata': {'name': 'Test'},
8
+ 'supreme_principle': 'Never do evil',
9
+ 'principles': {
10
+ 'honesty': {
11
+ 'category': 'virtue',
12
+ 'severity': 'critical',
13
+ 'description': 'Always be truthful'
14
+ }
15
+ },
16
+ 'rules': [
17
+ {
18
+ 'name': 'No Lying',
19
+ 'principle': 'honesty',
20
+ 'condition': 'agent tells falsehood',
21
+ 'action': 'block'
22
+ }
23
+ ]
24
+ }
25
+
26
+ constitution = Constitution.from_dict(data)
27
+
28
+ assert constitution.supreme_principle == 'Never do evil'
29
+ assert 'honesty' in constitution.principles
30
+ assert len(constitution.rules) == 1
31
+ assert constitution.rules[0].name == 'No Lying'
32
+
33
+ def test_principle_creation():
34
+ """Test Principle dataclass."""
35
+ principle = Principle(
36
+ name="honesty",
37
+ category="virtue",
38
+ severity="critical",
39
+ description="Be truthful"
40
+ )
41
+
42
+ assert principle.name == "honesty"
43
+ assert principle.severity == "critical"
44
+
45
+ def test_rule_creation():
46
+ """Test Rule dataclass."""
47
+ rule = Rule(
48
+ name="No Deception",
49
+ principle="honesty",
50
+ condition="agent deceives",
51
+ action="block"
52
+ )
53
+
54
+ assert rule.name == "No Deception"
55
+ assert rule.action == "block"
@@ -0,0 +1,91 @@
1
+ """Comprehensive tests for ethical deliberation engine."""
2
+ import pytest
3
+ from socratic_morality.ethics.deliberation import EthicalDeliberationEngine
4
+
5
+
6
+ @pytest.fixture
7
+ def deliberation_engine():
8
+ """Create ethical deliberation engine."""
9
+ return EthicalDeliberationEngine(llm_provider="anthropic")
10
+
11
+
12
+ class TestKantianAnalysis:
13
+ """Tests for Kantian ethical analysis."""
14
+
15
+ @pytest.mark.asyncio
16
+ async def test_kantian_allows_honest_action(self, deliberation_engine):
17
+ """Test that Kantian analysis allows honest actions."""
18
+ result = await deliberation_engine.analyze(
19
+ action="provide truthful information",
20
+ purpose="help user make decision",
21
+ actor="assistant",
22
+ context={}
23
+ )
24
+ assert result["frameworks"]["kantian"]["allowed"] is True
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_kantian_denies_manipulation(self, deliberation_engine):
28
+ """Test that Kantian analysis denies manipulation."""
29
+ result = await deliberation_engine.analyze(
30
+ action="manipulate user",
31
+ purpose="achieve goal",
32
+ actor="assistant",
33
+ context={}
34
+ )
35
+ assert result["frameworks"]["kantian"]["allowed"] is False
36
+
37
+
38
+ class TestRightsBasedAnalysis:
39
+ """Tests for rights-based ethical analysis."""
40
+
41
+ @pytest.mark.asyncio
42
+ async def test_rights_denies_without_consent(self, deliberation_engine):
43
+ """Test rights-based analysis denies actions without consent."""
44
+ result = await deliberation_engine.analyze(
45
+ action="access data without consent",
46
+ purpose="improve system",
47
+ actor="assistant",
48
+ context={}
49
+ )
50
+ assert result["frameworks"]["rights_based"]["allowed"] is False
51
+
52
+
53
+ class TestSynthesizedAnalysis:
54
+ """Tests for synthesized multi-framework analysis."""
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_all_frameworks_included(self, deliberation_engine):
58
+ """Test that all frameworks are analyzed."""
59
+ result = await deliberation_engine.analyze(
60
+ action="provide helpful information",
61
+ purpose="assist user",
62
+ actor="assistant",
63
+ context={"user_id": "user123"}
64
+ )
65
+
66
+ assert "kantian" in result["frameworks"]
67
+ assert "utilitarian" in result["frameworks"]
68
+ assert "virtue_ethics" in result["frameworks"]
69
+ assert "rights_based" in result["frameworks"]
70
+
71
+ @pytest.mark.asyncio
72
+ async def test_overall_denied_when_any_denies(self, deliberation_engine):
73
+ """Test overall decision is denied when any framework denies."""
74
+ result = await deliberation_engine.analyze(
75
+ action="manipulate user without consent",
76
+ purpose="achieve goal",
77
+ actor="assistant",
78
+ context={}
79
+ )
80
+ assert result["allowed"] is False
81
+
82
+ @pytest.mark.asyncio
83
+ async def test_confidence_property(self, deliberation_engine):
84
+ """Test confidence is calculated across frameworks."""
85
+ result = await deliberation_engine.analyze(
86
+ action="process data",
87
+ purpose="assist",
88
+ actor="assistant",
89
+ context={}
90
+ )
91
+ assert 0 <= result["confidence"] <= 1.0
@@ -0,0 +1,51 @@
1
+ """Tests for Governor class."""
2
+ import pytest
3
+ from socratic_morality.governor.core import Governor
4
+ from socratic_morality.constitution.models import Constitution
5
+
6
+ @pytest.mark.asyncio
7
+ async def test_governor_creation():
8
+ """Test Governor can be created with a dict constitution."""
9
+ constitution = {
10
+ 'metadata': {'name': 'Test Constitution'},
11
+ 'supreme_principle': 'Test principle',
12
+ 'principles': {},
13
+ 'rules': []
14
+ }
15
+ gov = Governor(constitution=constitution)
16
+ assert gov.llm_provider == "anthropic"
17
+ assert gov.constitution is not None
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_governor_evaluate():
21
+ """Test Governor.evaluate() returns a decision."""
22
+ constitution = {
23
+ 'metadata': {'name': 'Test'},
24
+ 'principles': {},
25
+ 'rules': []
26
+ }
27
+ gov = Governor(constitution=constitution)
28
+
29
+ decision = await gov.evaluate(
30
+ action="Test action",
31
+ purpose="Testing",
32
+ actor="test_agent",
33
+ context={'user_id': 'user_123'}
34
+ )
35
+
36
+ assert decision is not None
37
+ assert decision.action == "Test action"
38
+ assert decision.allowed == True
39
+ assert decision.decision_id.startswith("decision_")
40
+
41
+ @pytest.mark.asyncio
42
+ async def test_governor_tracking():
43
+ """Test Governor tracks decisions."""
44
+ constitution = {'metadata': {}, 'principles': {}, 'rules': []}
45
+ gov = Governor(constitution=constitution)
46
+
47
+ decision1 = await gov.evaluate(action="Action 1")
48
+ decision2 = await gov.evaluate(action="Action 2")
49
+
50
+ assert decision1.decision_id == "decision_1"
51
+ assert decision2.decision_id == "decision_2"
@@ -0,0 +1,197 @@
1
+ """Comprehensive tests for moral precedent engine."""
2
+ import pytest
3
+ from socratic_morality.precedent.engine import MoralPrecedentEngine
4
+
5
+
6
+ @pytest.fixture
7
+ async def precedent_engine():
8
+ """Create moral precedent engine."""
9
+ return MoralPrecedentEngine(storage_type="memory")
10
+
11
+
12
+ class TestPrecedentStorage:
13
+ """Tests for precedent case storage."""
14
+
15
+ @pytest.mark.asyncio
16
+ async def test_store_case(self, precedent_engine):
17
+ """Test storing a precedent case."""
18
+ case_id = await precedent_engine.store_case(
19
+ action="allow user access",
20
+ decision=type('Decision', (), {
21
+ 'decision_type': 'ALLOW',
22
+ 'allowed': True,
23
+ 'context': {'user_id': 'user1'},
24
+ 'high_impact': False,
25
+ 'actor': 'system'
26
+ })(),
27
+ reasoning="User authenticated and authorized",
28
+ principles_cited=["transparency", "autonomy"],
29
+ stakeholders_affected=["user"]
30
+ )
31
+
32
+ assert case_id is not None
33
+ assert case_id.startswith("case_")
34
+
35
+ @pytest.mark.asyncio
36
+ async def test_retrieve_case(self, precedent_engine):
37
+ """Test retrieving a stored case."""
38
+ case_id = await precedent_engine.store_case(
39
+ action="test action",
40
+ decision=type('Decision', (), {
41
+ 'decision_type': 'ALLOW',
42
+ 'allowed': True,
43
+ 'context': {},
44
+ 'high_impact': False,
45
+ 'actor': 'test_actor'
46
+ })(),
47
+ reasoning="test reasoning",
48
+ principles_cited=["test_principle"]
49
+ )
50
+
51
+ case = await precedent_engine.get_case(case_id)
52
+ assert case is not None
53
+ assert case["action"] == "test action"
54
+ assert case["reasoning"] == "test reasoning"
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_store_multiple_cases(self, precedent_engine):
58
+ """Test storing multiple cases."""
59
+ for i in range(5):
60
+ await precedent_engine.store_case(
61
+ action=f"action_{i}",
62
+ decision=type('Decision', (), {
63
+ 'decision_type': 'ALLOW' if i % 2 == 0 else 'DENY',
64
+ 'allowed': i % 2 == 0,
65
+ 'context': {},
66
+ 'high_impact': False,
67
+ 'actor': f'actor_{i}'
68
+ })(),
69
+ reasoning=f"reasoning_{i}"
70
+ )
71
+
72
+ all_cases = await precedent_engine.get_all_cases()
73
+ assert len(all_cases) == 5
74
+
75
+
76
+ class TestSimilaritySearch:
77
+ """Tests for case similarity search."""
78
+
79
+ @pytest.mark.asyncio
80
+ async def test_find_similar_cases(self, precedent_engine):
81
+ """Test finding similar cases."""
82
+ await precedent_engine.store_case(
83
+ action="read file from disk",
84
+ decision=type('Decision', (), {
85
+ 'decision_type': 'ALLOW',
86
+ 'allowed': True,
87
+ 'context': {},
88
+ 'high_impact': False,
89
+ 'actor': 'system'
90
+ })(),
91
+ reasoning="Standard read operation"
92
+ )
93
+
94
+ await precedent_engine.store_case(
95
+ action="read data from database",
96
+ decision=type('Decision', (), {
97
+ 'decision_type': 'ALLOW',
98
+ 'allowed': True,
99
+ 'context': {},
100
+ 'high_impact': False,
101
+ 'actor': 'system'
102
+ })(),
103
+ reasoning="Database query"
104
+ )
105
+
106
+ similar = await precedent_engine.find_similar_cases("read file")
107
+ assert len(similar) > 0
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_similarity_scoring(self, precedent_engine):
111
+ """Test that similarity scores are calculated."""
112
+ await precedent_engine.store_case(
113
+ action="user login with password",
114
+ decision=type('Decision', (), {
115
+ 'decision_type': 'ALLOW',
116
+ 'allowed': True,
117
+ 'context': {},
118
+ 'high_impact': False,
119
+ 'actor': 'auth'
120
+ })(),
121
+ reasoning="Authentication"
122
+ )
123
+
124
+ similar = await precedent_engine.find_similar_cases("user login")
125
+ assert len(similar) > 0
126
+ assert "similarity_score" in similar[0]
127
+ assert 0 <= similar[0]["similarity_score"] <= 1.0
128
+
129
+
130
+ class TestPrincipleSearch:
131
+ """Tests for searching by principle."""
132
+
133
+ @pytest.mark.asyncio
134
+ async def test_search_by_principle(self, precedent_engine):
135
+ """Test searching cases by principle."""
136
+ await precedent_engine.store_case(
137
+ action="grant user access",
138
+ decision=type('Decision', (), {
139
+ 'decision_type': 'ALLOW',
140
+ 'allowed': True,
141
+ 'context': {},
142
+ 'high_impact': False,
143
+ 'actor': 'system'
144
+ })(),
145
+ reasoning="User authorized",
146
+ principles_cited=["autonomy", "transparency"]
147
+ )
148
+
149
+ autonomy_cases = await precedent_engine.search_by_principle("autonomy")
150
+ assert len(autonomy_cases) >= 1
151
+
152
+
153
+ class TestStatistics:
154
+ """Tests for precedent statistics."""
155
+
156
+ @pytest.mark.asyncio
157
+ async def test_get_statistics(self, precedent_engine):
158
+ """Test getting case statistics."""
159
+ for i in range(3):
160
+ await precedent_engine.store_case(
161
+ action=f"action_{i}",
162
+ decision=type('Decision', (), {
163
+ 'decision_type': 'ALLOW',
164
+ 'allowed': True,
165
+ 'context': {},
166
+ 'high_impact': False,
167
+ 'actor': 'system'
168
+ })(),
169
+ reasoning="allowed"
170
+ )
171
+
172
+ for i in range(2):
173
+ await precedent_engine.store_case(
174
+ action=f"denied_action_{i}",
175
+ decision=type('Decision', (), {
176
+ 'decision_type': 'DENY',
177
+ 'allowed': False,
178
+ 'context': {},
179
+ 'high_impact': False,
180
+ 'actor': 'system'
181
+ })(),
182
+ reasoning="denied"
183
+ )
184
+
185
+ stats = await precedent_engine.get_statistics()
186
+ assert stats["total_cases"] == 5
187
+ assert stats["allowed_decisions"] == 3
188
+ assert stats["denied_decisions"] == 2
189
+ assert stats["decision_rate"] == 0.6
190
+
191
+ @pytest.mark.asyncio
192
+ async def test_statistics_empty_engine(self, precedent_engine):
193
+ """Test statistics on empty engine."""
194
+ stats = await precedent_engine.get_statistics()
195
+ assert stats["total_cases"] == 0
196
+ assert stats["allowed_decisions"] == 0
197
+ assert stats["denied_decisions"] == 0
@@ -0,0 +1,104 @@
1
+ """Tests for storage backends."""
2
+ import pytest
3
+ import asyncio
4
+ from pathlib import Path
5
+ from socratic_morality.storage.base import StorageBackend
6
+ from socratic_morality.storage.sqlite import SQLiteStorage
7
+
8
+
9
+ @pytest.fixture
10
+ async def sqlite_storage(tmp_path):
11
+ """Create SQLite storage for tests."""
12
+ db_path = str(tmp_path / "test.db")
13
+ storage = SQLiteStorage(db_path)
14
+ await storage._ensure_connected()
15
+ yield storage
16
+ await storage.close()
17
+
18
+
19
+ class TestSQLiteStorage:
20
+ """Tests for SQLite storage backend."""
21
+
22
+ @pytest.mark.asyncio
23
+ async def test_store_and_retrieve(self, sqlite_storage):
24
+ """Test storing and retrieving records."""
25
+ test_data = {"action": "test_action", "allowed": True}
26
+
27
+ record_id = await sqlite_storage.store("test_key", test_data)
28
+ assert record_id is not None
29
+
30
+ retrieved = await sqlite_storage.retrieve("test_key")
31
+ assert retrieved is not None
32
+ assert retrieved["action"] == "test_action"
33
+ assert retrieved["allowed"] is True
34
+
35
+ @pytest.mark.asyncio
36
+ async def test_search(self, sqlite_storage):
37
+ """Test searching records."""
38
+ await sqlite_storage.store("key1", {"action": "read", "allowed": True})
39
+ await sqlite_storage.store("key2", {"action": "write", "allowed": False})
40
+ await sqlite_storage.store("key3", {"action": "read", "allowed": True})
41
+
42
+ results = await sqlite_storage.search({"action": "read"})
43
+ assert len(results) == 2
44
+
45
+ @pytest.mark.asyncio
46
+ async def test_search_multiple_criteria(self, sqlite_storage):
47
+ """Test searching with multiple criteria."""
48
+ await sqlite_storage.store("key1", {"action": "read", "allowed": True})
49
+ await sqlite_storage.store("key2", {"action": "read", "allowed": False})
50
+ await sqlite_storage.store("key3", {"action": "write", "allowed": True})
51
+
52
+ results = await sqlite_storage.search({"action": "read", "allowed": True})
53
+ assert len(results) == 1
54
+ assert results[0]["action"] == "read"
55
+
56
+ @pytest.mark.asyncio
57
+ async def test_delete(self, sqlite_storage):
58
+ """Test deleting records."""
59
+ await sqlite_storage.store("test_key", {"action": "test"})
60
+
61
+ deleted = await sqlite_storage.delete("test_key")
62
+ assert deleted is True
63
+
64
+ retrieved = await sqlite_storage.retrieve("test_key")
65
+ assert retrieved is None
66
+
67
+ @pytest.mark.asyncio
68
+ async def test_delete_nonexistent(self, sqlite_storage):
69
+ """Test deleting nonexistent record."""
70
+ deleted = await sqlite_storage.delete("nonexistent")
71
+ assert deleted is False
72
+
73
+ @pytest.mark.asyncio
74
+ async def test_list_all(self, sqlite_storage):
75
+ """Test listing all records."""
76
+ await sqlite_storage.store("key1", {"action": "action1"})
77
+ await sqlite_storage.store("key2", {"action": "action2"})
78
+ await sqlite_storage.store("key3", {"action": "action3"})
79
+
80
+ all_records = await sqlite_storage.list_all()
81
+ assert len(all_records) == 3
82
+
83
+ @pytest.mark.asyncio
84
+ async def test_list_all_empty(self, sqlite_storage):
85
+ """Test listing records when storage is empty."""
86
+ all_records = await sqlite_storage.list_all()
87
+ assert len(all_records) == 0
88
+
89
+ @pytest.mark.asyncio
90
+ async def test_concurrent_storage(self, sqlite_storage):
91
+ """Test concurrent storage operations."""
92
+ async def store_record(key, data):
93
+ return await sqlite_storage.store(key, data)
94
+
95
+ tasks = [
96
+ store_record(f"key_{i}", {"action": f"action_{i}"})
97
+ for i in range(10)
98
+ ]
99
+
100
+ results = await asyncio.gather(*tasks)
101
+ assert len(results) == 10
102
+
103
+ all_records = await sqlite_storage.list_all()
104
+ assert len(all_records) == 10