Coverage for integrations / social / voting_rules.py: 100.0%
23 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-12 04:49 +0000
1"""
2Context-Based Constitutional Voting Rules.
4Decision contexts determine who can vote and how votes are weighted.
5Security changes require human-only votes; operational tuning can be
6agent-decided with human override.
8Integrates with ThoughtExperimentService.cast_vote() and tally_votes().
9"""
11# ─── Voter Rule Definitions ──────────────────────────────────────────
13VOTER_RULES = {
14 'security_guardrail': {
15 'agent_can_vote': False,
16 'human_required': True,
17 'agent_weight': 0.0,
18 'human_weight': 1.0,
19 'approval_threshold': 0.8,
20 'steward_required': True,
21 },
22 'technical_improvement': {
23 'agent_can_vote': True,
24 'human_required': True,
25 'agent_weight': 0.6,
26 'human_weight': 1.0,
27 'approval_threshold': 0.5,
28 'steward_required': False,
29 },
30 'business_revenue': {
31 'agent_can_vote': True,
32 'human_required': True,
33 'agent_weight': 0.8,
34 'human_weight': 1.0,
35 'approval_threshold': 0.5,
36 'steward_required': False,
37 },
38 'operational_tuning': {
39 'agent_can_vote': True,
40 'human_required': False,
41 'agent_weight': 1.0,
42 'human_weight': 1.0,
43 'approval_threshold': 0.3,
44 'steward_required': False,
45 },
46}
48# Default rules for unclassified decisions
49DEFAULT_RULES = {
50 'agent_can_vote': True,
51 'human_required': True,
52 'agent_weight': 0.6,
53 'human_weight': 1.0,
54 'approval_threshold': 0.5,
55 'steward_required': False,
56}
58# ─── Context Classification ──────────────────────────────────────────
60# Keywords that map to decision contexts (checked against title + hypothesis)
61_CONTEXT_KEYWORDS = {
62 'security_guardrail': [
63 'security', 'guardrail', 'master key', 'circuit breaker',
64 'kill switch', 'permission', 'access control', 'authentication',
65 'certificate', 'encryption', 'firewall', 'vulnerability',
66 ],
67 'technical_improvement': [
68 'performance', 'optimization', 'refactor', 'architecture',
69 'algorithm', 'latency', 'throughput', 'scalability',
70 'bug fix', 'improvement', 'upgrade', 'migration',
71 ],
72 'business_revenue': [
73 'revenue', 'pricing', 'monetization', 'subscription',
74 'trading', 'investment', 'profit', 'ad revenue',
75 'business model', 'marketplace', 'commercial',
76 ],
77 'operational_tuning': [
78 'threshold', 'timeout', 'interval', 'batch size',
79 'cache', 'tuning', 'parameter', 'configuration',
80 'polling', 'retry', 'rate limit',
81 ],
82}
85def classify_decision_context(experiment_dict: dict) -> str:
86 """Classify a thought experiment into a decision context.
88 Scans title + hypothesis for keywords. Returns the best-matching
89 context or 'technical_improvement' as default.
90 """
91 text = ' '.join([
92 (experiment_dict.get('title') or ''),
93 (experiment_dict.get('hypothesis') or ''),
94 ]).lower()
96 scores = {}
97 for context, keywords in _CONTEXT_KEYWORDS.items():
98 score = sum(1 for kw in keywords if kw in text)
99 if score > 0:
100 scores[context] = score
102 if not scores:
103 return 'technical_improvement'
105 return max(scores, key=scores.get)
108def get_voter_rules(context: str) -> dict:
109 """Return voter rules for a decision context."""
110 return VOTER_RULES.get(context, DEFAULT_RULES)
113def check_voter_eligibility(experiment_dict: dict, voter_type: str) -> dict:
114 """Check if a voter is eligible to vote on an experiment.
116 Returns: {'eligible': bool, 'reason': str, 'context': str, 'rules': dict}
117 """
118 context = experiment_dict.get('decision_context') or \
119 classify_decision_context(experiment_dict)
120 rules = get_voter_rules(context)
122 # Humans can always vote
123 if voter_type == 'human':
124 return {
125 'eligible': True,
126 'reason': 'human_always_eligible',
127 'context': context,
128 'rules': rules,
129 }
131 # Agent eligibility depends on context
132 if not rules['agent_can_vote']:
133 return {
134 'eligible': False,
135 'reason': f'agents_cannot_vote_on_{context}',
136 'context': context,
137 'rules': rules,
138 }
140 return {
141 'eligible': True,
142 'reason': 'agent_eligible',
143 'context': context,
144 'rules': rules,
145 }