Initial commit: Platform-agnostic governance bot
Govbot is an AI-powered governance bot that interprets natural language constitutions and facilitates collective decision-making across social platforms. Core features: - Agentic architecture with constitutional reasoning (RAG) - Platform-agnostic design (Mastodon, Discord, Telegram, etc.) - Action primitives for flexible governance processes - Temporal awareness for multi-day proposals and voting - Audit trail with constitutional citations - Reversible actions with supermajority veto - Works with local (Ollama) and cloud AI models Platform support: - Mastodon: Full implementation with streaming, moderation, and admin skills - Discord/Telegram: Platform abstraction ready for implementation Documentation: - README.md: Architecture and overview - QUICKSTART.md: Getting started guide - PLATFORMS.md: Platform implementation guide for developers - MASTODON_SETUP.md: Complete Mastodon deployment guide - constitution.md: Example governance constitution Technical stack: - Python 3.11+ - SQLAlchemy for state management - llm CLI for model abstraction - Mastodon.py for Mastodon integration - Pydantic for configuration validation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
172
tests/test_basic.py
Normal file
172
tests/test_basic.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""
|
||||
Basic tests for Govbot functionality.
|
||||
|
||||
These tests demonstrate the core features without requiring Mastodon.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
# Add src to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from src.govbot.db.models import init_db, get_session
|
||||
from src.govbot.agent import GovernanceAgent
|
||||
from src.govbot.governance.constitution import ConstitutionalReasoner
|
||||
from src.govbot.governance.primitives import GovernancePrimitives
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_db():
|
||||
"""Create a temporary test database"""
|
||||
temp_dir = tempfile.mkdtemp()
|
||||
db_path = Path(temp_dir) / "test.db"
|
||||
|
||||
engine = init_db(str(db_path))
|
||||
session = get_session(engine)
|
||||
|
||||
yield session
|
||||
|
||||
session.close()
|
||||
shutil.rmtree(temp_dir)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def constitution_path():
|
||||
"""Get path to test constitution"""
|
||||
return str(Path(__file__).parent.parent / "constitution.md")
|
||||
|
||||
|
||||
def test_constitution_loading(constitution_path):
|
||||
"""Test that constitution loads and parses correctly"""
|
||||
reasoner = ConstitutionalReasoner(constitution_path)
|
||||
|
||||
assert reasoner.constitution_text
|
||||
assert len(reasoner.sections) > 0
|
||||
|
||||
# Test section listing
|
||||
sections = reasoner.list_sections()
|
||||
assert any("Proposal" in s for s in sections)
|
||||
|
||||
|
||||
def test_constitutional_query(constitution_path):
|
||||
"""Test querying the constitution"""
|
||||
reasoner = ConstitutionalReasoner(constitution_path)
|
||||
|
||||
result = reasoner.query("What are the rules for standard proposals?")
|
||||
|
||||
assert "answer" in result
|
||||
assert "citations" in result
|
||||
assert len(result["citations"]) > 0
|
||||
|
||||
|
||||
def test_primitives_store_and_query(test_db):
|
||||
"""Test basic primitive operations"""
|
||||
primitives = GovernancePrimitives(test_db)
|
||||
|
||||
# Store a record
|
||||
record_id = primitives.store_record(
|
||||
record_type="test_record",
|
||||
data={"foo": "bar", "count": 42},
|
||||
actor="test_user",
|
||||
reasoning="Testing storage",
|
||||
)
|
||||
|
||||
assert record_id > 0
|
||||
|
||||
# Query it back
|
||||
records = primitives.query_records(record_type="store_test_record")
|
||||
assert len(records) > 0
|
||||
|
||||
|
||||
def test_create_process(test_db):
|
||||
"""Test creating a governance process"""
|
||||
primitives = GovernancePrimitives(test_db)
|
||||
|
||||
process_id = primitives.create_process(
|
||||
process_type="test_proposal",
|
||||
creator="test_user",
|
||||
deadline_days=6,
|
||||
constitutional_basis="Article 3, Section 1",
|
||||
initial_state={"proposal_text": "Test proposal"},
|
||||
)
|
||||
|
||||
assert process_id > 0
|
||||
|
||||
# Verify it was created
|
||||
from src.govbot.db import queries
|
||||
|
||||
process = queries.get_process(test_db, process_id)
|
||||
assert process is not None
|
||||
assert process.process_type == "test_proposal"
|
||||
assert process.creator == "test_user"
|
||||
|
||||
|
||||
def test_vote_counting(test_db):
|
||||
"""Test vote counting and threshold checking"""
|
||||
primitives = GovernancePrimitives(test_db)
|
||||
|
||||
# Create a process
|
||||
process_id = primitives.create_process(
|
||||
process_type="test_proposal",
|
||||
creator="alice",
|
||||
deadline_days=6,
|
||||
constitutional_basis="Article 3",
|
||||
initial_state={"votes": {}},
|
||||
)
|
||||
|
||||
# Cast some votes
|
||||
primitives.update_process_state(
|
||||
process_id,
|
||||
{
|
||||
"votes.alice": {"vote": "agree"},
|
||||
"votes.bob": {"vote": "agree"},
|
||||
"votes.charlie": {"vote": "disagree"},
|
||||
},
|
||||
)
|
||||
|
||||
# Count votes
|
||||
counts = primitives.count_votes(process_id)
|
||||
assert counts["agree"] == 2
|
||||
assert counts["disagree"] == 1
|
||||
|
||||
# Check threshold
|
||||
passed = primitives.check_threshold(counts, "simple_majority")
|
||||
assert passed is True
|
||||
|
||||
|
||||
def test_agent_proposal_creation(test_db, constitution_path):
|
||||
"""Test agent creating a proposal from natural language"""
|
||||
agent = GovernanceAgent(test_db, constitution_path, model=None)
|
||||
|
||||
result = agent.process_request(
|
||||
request="I propose we have weekly meetings",
|
||||
actor="test_user",
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["process_id"] is not None
|
||||
assert "proposal" in result["response"].lower()
|
||||
|
||||
|
||||
def test_proposal_interpretation(constitution_path):
|
||||
"""Test interpreting different proposal types"""
|
||||
reasoner = ConstitutionalReasoner(constitution_path)
|
||||
|
||||
# Standard proposal
|
||||
info = reasoner.interpret_proposal("We should update the guidelines")
|
||||
assert "proposal_type" in info
|
||||
assert info["discussion_period_days"] >= 2
|
||||
|
||||
# Urgent proposal
|
||||
info = reasoner.interpret_proposal("URGENT: We need to address this spam attack")
|
||||
# Should recognize urgency (though AI interpretation may vary)
|
||||
assert "proposal_type" in info
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run tests
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user