@holoscript/framework 6.0.3
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.
- package/ALL-test-results.json +1 -0
- package/CHANGELOG.md +8 -0
- package/LICENSE +21 -0
- package/ROADMAP.md +175 -0
- package/dist/AgentManifest-CB4xM-Ma.d.cts +704 -0
- package/dist/AgentManifest-CB4xM-Ma.d.ts +704 -0
- package/dist/BehaviorTree-BrBFECv5.d.cts +103 -0
- package/dist/BehaviorTree-BrBFECv5.d.ts +103 -0
- package/dist/InvisibleWallet-BB6tFvRA.d.cts +1732 -0
- package/dist/InvisibleWallet-rtRrBOA8.d.ts +1732 -0
- package/dist/OrchestratorAgent-BvWgf9uw.d.cts +798 -0
- package/dist/OrchestratorAgent-Q_CbVTmO.d.ts +798 -0
- package/dist/agents/index.cjs +4790 -0
- package/dist/agents/index.d.cts +1788 -0
- package/dist/agents/index.d.ts +1788 -0
- package/dist/agents/index.js +4695 -0
- package/dist/ai/index.cjs +5347 -0
- package/dist/ai/index.d.cts +1753 -0
- package/dist/ai/index.d.ts +1753 -0
- package/dist/ai/index.js +5244 -0
- package/dist/behavior.cjs +449 -0
- package/dist/behavior.d.cts +130 -0
- package/dist/behavior.d.ts +130 -0
- package/dist/behavior.js +407 -0
- package/dist/economy/index.cjs +3659 -0
- package/dist/economy/index.d.cts +747 -0
- package/dist/economy/index.d.ts +747 -0
- package/dist/economy/index.js +3617 -0
- package/dist/implementations-D9T3un9D.d.cts +236 -0
- package/dist/implementations-D9T3un9D.d.ts +236 -0
- package/dist/index.cjs +24550 -0
- package/dist/index.d.cts +1729 -0
- package/dist/index.d.ts +1729 -0
- package/dist/index.js +24277 -0
- package/dist/learning/index.cjs +219 -0
- package/dist/learning/index.d.cts +104 -0
- package/dist/learning/index.d.ts +104 -0
- package/dist/learning/index.js +189 -0
- package/dist/negotiation/index.cjs +970 -0
- package/dist/negotiation/index.d.cts +610 -0
- package/dist/negotiation/index.d.ts +610 -0
- package/dist/negotiation/index.js +931 -0
- package/dist/skills/index.cjs +1118 -0
- package/dist/skills/index.d.cts +289 -0
- package/dist/skills/index.d.ts +289 -0
- package/dist/skills/index.js +1079 -0
- package/dist/swarm/index.cjs +5268 -0
- package/dist/swarm/index.d.cts +2433 -0
- package/dist/swarm/index.d.ts +2433 -0
- package/dist/swarm/index.js +5221 -0
- package/dist/training/index.cjs +2745 -0
- package/dist/training/index.d.cts +1734 -0
- package/dist/training/index.d.ts +1734 -0
- package/dist/training/index.js +2687 -0
- package/extract-failures.js +10 -0
- package/package.json +82 -0
- package/src/__tests__/bounty-marketplace.test.ts +374 -0
- package/src/__tests__/delegation.test.ts +144 -0
- package/src/__tests__/distributed-claimer.test.ts +147 -0
- package/src/__tests__/done-log-audit.test.ts +342 -0
- package/src/__tests__/framework.test.ts +865 -0
- package/src/__tests__/goal-synthesizer.test.ts +236 -0
- package/src/__tests__/presence.test.ts +223 -0
- package/src/__tests__/protocol-agent.test.ts +254 -0
- package/src/__tests__/revenue-splitter.test.ts +114 -0
- package/src/__tests__/scenario-driven-todo.test.ts +197 -0
- package/src/__tests__/self-improve.test.ts +349 -0
- package/src/__tests__/service-lifecycle.test.ts +237 -0
- package/src/__tests__/skill-router.test.ts +121 -0
- package/src/agents/AgentManifest.ts +493 -0
- package/src/agents/AgentRegistry.ts +475 -0
- package/src/agents/AgentTypes.ts +585 -0
- package/src/agents/AgentWalletRegistry.ts +83 -0
- package/src/agents/AuthenticatedCRDT.ts +388 -0
- package/src/agents/CapabilityMatcher.ts +453 -0
- package/src/agents/CrossRealityHandoff.ts +305 -0
- package/src/agents/CulturalMemory.ts +454 -0
- package/src/agents/FederatedRegistryAdapter.ts +429 -0
- package/src/agents/NormEngine.ts +450 -0
- package/src/agents/OrchestratorAgent.ts +414 -0
- package/src/agents/SkillWorkflowEngine.ts +472 -0
- package/src/agents/TaskDelegationService.ts +551 -0
- package/src/agents/__tests__/AgentManifest.prod.test.ts +134 -0
- package/src/agents/__tests__/AgentManifest.test.ts +182 -0
- package/src/agents/__tests__/AgentModule.test.ts +864 -0
- package/src/agents/__tests__/AgentRegistry.prod.test.ts +125 -0
- package/src/agents/__tests__/AgentRegistry.test.ts +148 -0
- package/src/agents/__tests__/AgentTypes.test.ts +534 -0
- package/src/agents/__tests__/AgentWalletRegistry.test.ts +152 -0
- package/src/agents/__tests__/AuthenticatedCRDT.test.ts +558 -0
- package/src/agents/__tests__/CapabilityMatcher.prod.test.ts +117 -0
- package/src/agents/__tests__/CapabilityMatcher.test.ts +178 -0
- package/src/agents/__tests__/CrossRealityHandoff.test.ts +402 -0
- package/src/agents/__tests__/CulturalMemory.test.ts +200 -0
- package/src/agents/__tests__/FederatedRegistryAdapter.test.ts +409 -0
- package/src/agents/__tests__/NormEngine.test.ts +276 -0
- package/src/agents/__tests__/OrchestratorAgent.test.ts +182 -0
- package/src/agents/__tests__/SkillWorkflowEngine.test.ts +357 -0
- package/src/agents/__tests__/TaskDelegationService.test.ts +446 -0
- package/src/agents/index.ts +107 -0
- package/src/agents/spatial-comms/Layer1RealTime.ts +621 -0
- package/src/agents/spatial-comms/Layer2A2A.ts +661 -0
- package/src/agents/spatial-comms/Layer3MCP.ts +651 -0
- package/src/agents/spatial-comms/ProtocolTypes.ts +543 -0
- package/src/agents/spatial-comms/SpatialCommClient.ts +483 -0
- package/src/agents/spatial-comms/__tests__/performance-benchmark.test.ts +465 -0
- package/src/agents/spatial-comms/examples/multi-agent-world-creation.ts +409 -0
- package/src/agents/spatial-comms/index.ts +66 -0
- package/src/ai/AIAdapter.ts +313 -0
- package/src/ai/AICopilot.ts +331 -0
- package/src/ai/AIOutputValidator.ts +203 -0
- package/src/ai/BTNodes.ts +239 -0
- package/src/ai/BehaviorSelector.ts +135 -0
- package/src/ai/BehaviorTree.ts +153 -0
- package/src/ai/Blackboard.ts +165 -0
- package/src/ai/GenerationAnalytics.ts +461 -0
- package/src/ai/GenerationCache.ts +265 -0
- package/src/ai/GoalPlanner.ts +165 -0
- package/src/ai/HoloScriptGenerator.ts +580 -0
- package/src/ai/InfluenceMap.ts +180 -0
- package/src/ai/NavMesh.ts +168 -0
- package/src/ai/PerceptionSystem.ts +178 -0
- package/src/ai/PromptTemplates.ts +453 -0
- package/src/ai/SemanticSearchService.ts +80 -0
- package/src/ai/StateMachine.ts +196 -0
- package/src/ai/SteeringBehavior.ts +150 -0
- package/src/ai/SteeringBehaviors.ts +244 -0
- package/src/ai/TrainingDataGenerator.ts +1082 -0
- package/src/ai/UtilityAI.ts +145 -0
- package/src/ai/__tests__/AIAdapter.prod.test.ts +259 -0
- package/src/ai/__tests__/AIAdapter.test.ts +109 -0
- package/src/ai/__tests__/AICopilot.prod.test.ts +341 -0
- package/src/ai/__tests__/AICopilot.test.ts +178 -0
- package/src/ai/__tests__/AIOutputValidator.prod.test.ts +226 -0
- package/src/ai/__tests__/AIOutputValidator.test.ts +138 -0
- package/src/ai/__tests__/BTNodes.prod.test.ts +391 -0
- package/src/ai/__tests__/BTNodes.test.ts +263 -0
- package/src/ai/__tests__/BehaviorSelector.prod.test.ts +129 -0
- package/src/ai/__tests__/BehaviorSelector.test.ts +132 -0
- package/src/ai/__tests__/BehaviorTree.prod.test.ts +266 -0
- package/src/ai/__tests__/BehaviorTree.test.ts +216 -0
- package/src/ai/__tests__/Blackboard.prod.test.ts +339 -0
- package/src/ai/__tests__/Blackboard.test.ts +183 -0
- package/src/ai/__tests__/GenerationAnalytics.prod.test.ts +141 -0
- package/src/ai/__tests__/GenerationAnalytics.test.ts +165 -0
- package/src/ai/__tests__/GenerationCache.prod.test.ts +144 -0
- package/src/ai/__tests__/GenerationCache.test.ts +171 -0
- package/src/ai/__tests__/GoalPlanner.prod.test.ts +189 -0
- package/src/ai/__tests__/GoalPlanner.test.ts +137 -0
- package/src/ai/__tests__/GoalPlannerDepth.prod.test.ts +217 -0
- package/src/ai/__tests__/HoloScriptGenerator.test.ts +125 -0
- package/src/ai/__tests__/InfluenceMap.prod.test.ts +146 -0
- package/src/ai/__tests__/InfluenceMap.test.ts +149 -0
- package/src/ai/__tests__/NavMesh.prod.test.ts +141 -0
- package/src/ai/__tests__/NavMesh.test.ts +159 -0
- package/src/ai/__tests__/PerceptionSystem.prod.test.ts +135 -0
- package/src/ai/__tests__/PerceptionSystem.test.ts +250 -0
- package/src/ai/__tests__/PromptTemplates.prod.test.ts +313 -0
- package/src/ai/__tests__/PromptTemplates.test.ts +146 -0
- package/src/ai/__tests__/SemanticSearch.test.ts +37 -0
- package/src/ai/__tests__/StateMachine.prod.test.ts +162 -0
- package/src/ai/__tests__/StateMachine.test.ts +163 -0
- package/src/ai/__tests__/SteeringBehavior.prod.test.ts +251 -0
- package/src/ai/__tests__/SteeringBehavior.test.ts +135 -0
- package/src/ai/__tests__/SteeringBehaviors.prod.test.ts +133 -0
- package/src/ai/__tests__/SteeringBehaviors.test.ts +151 -0
- package/src/ai/__tests__/TrainingDataGenerator.prod.test.ts +286 -0
- package/src/ai/__tests__/TrainingDataGenerator.test.ts +286 -0
- package/src/ai/__tests__/UtilityAI.prod.test.ts +207 -0
- package/src/ai/__tests__/UtilityAI.test.ts +155 -0
- package/src/ai/__tests__/adapters.prod.test.ts +263 -0
- package/src/ai/__tests__/adapters.test.ts +320 -0
- package/src/ai/adapters.ts +1585 -0
- package/src/ai/index.ts +130 -0
- package/src/behavior/BehaviorPresets.ts +140 -0
- package/src/behavior/BehaviorTree.ts +236 -0
- package/src/behavior/StateMachine.ts +176 -0
- package/src/behavior/StateTrait.ts +67 -0
- package/src/behavior/index.ts +8 -0
- package/src/behavior.ts +8 -0
- package/src/board/audit.ts +284 -0
- package/src/board/board-ops.ts +336 -0
- package/src/board/board-types.ts +302 -0
- package/src/board/index.ts +69 -0
- package/src/define-agent.ts +46 -0
- package/src/define-team.ts +33 -0
- package/src/delegation.ts +265 -0
- package/src/distributed-claimer.ts +228 -0
- package/src/economy/AgentBudgetEnforcer.ts +464 -0
- package/src/economy/BountyManager.ts +185 -0
- package/src/economy/CreatorRevenueAggregator.ts +460 -0
- package/src/economy/InvisibleWallet.ts +82 -0
- package/src/economy/KnowledgeMarketplace.ts +193 -0
- package/src/economy/PaymentWebhookService.ts +512 -0
- package/src/economy/RevenueSplitter.ts +156 -0
- package/src/economy/SubscriptionManager.ts +546 -0
- package/src/economy/UnifiedBudgetOptimizer.ts +635 -0
- package/src/economy/UsageMeter.ts +440 -0
- package/src/economy/_core-stubs.ts +219 -0
- package/src/economy/index.ts +100 -0
- package/src/economy/x402-facilitator.ts +1978 -0
- package/src/index.ts +348 -0
- package/src/knowledge/__tests__/knowledge-consolidator.test.ts +444 -0
- package/src/knowledge/__tests__/knowledge-store-vector.test.ts +291 -0
- package/src/knowledge/brain.ts +167 -0
- package/src/knowledge/consolidation.ts +581 -0
- package/src/knowledge/knowledge-consolidator.ts +510 -0
- package/src/knowledge/knowledge-store.ts +616 -0
- package/src/learning/MemoryConsolidator.ts +102 -0
- package/src/learning/MemoryScorer.ts +69 -0
- package/src/learning/ProceduralCompiler.ts +45 -0
- package/src/learning/SemanticClusterer.ts +66 -0
- package/src/learning/index.ts +8 -0
- package/src/llm/llm-adapter.ts +159 -0
- package/src/mesh/index.ts +309 -0
- package/src/negotiation/NegotiationProtocol.ts +694 -0
- package/src/negotiation/NegotiationTypes.ts +473 -0
- package/src/negotiation/VotingMechanisms.ts +691 -0
- package/src/negotiation/index.ts +49 -0
- package/src/protocol/goal-synthesizer.ts +317 -0
- package/src/protocol/implementations.ts +474 -0
- package/src/protocol/micro-phase-decomposer.ts +299 -0
- package/src/protocol/micro-step-decomposer.test.ts +306 -0
- package/src/protocol-agent.test.ts +353 -0
- package/src/protocol-agent.ts +670 -0
- package/src/self-improve/absorb-scanner.ts +252 -0
- package/src/self-improve/evolution-engine.ts +149 -0
- package/src/self-improve/framework-absorber.ts +214 -0
- package/src/self-improve/index.ts +50 -0
- package/src/self-improve/prompt-optimizer.ts +212 -0
- package/src/self-improve/test-generator.ts +175 -0
- package/src/skill-router.ts +186 -0
- package/src/skills/index.ts +5 -0
- package/src/skills/skill-md-bridge.ts +1699 -0
- package/src/swarm/ACOEngine.ts +261 -0
- package/src/swarm/CollectiveIntelligence.ts +383 -0
- package/src/swarm/ContributionSynthesizer.ts +481 -0
- package/src/swarm/LeaderElection.ts +393 -0
- package/src/swarm/PSOEngine.ts +206 -0
- package/src/swarm/QuorumPolicy.ts +173 -0
- package/src/swarm/SwarmCoordinator.ts +335 -0
- package/src/swarm/SwarmManager.ts +442 -0
- package/src/swarm/SwarmMembership.ts +456 -0
- package/src/swarm/VotingRound.ts +255 -0
- package/src/swarm/__tests__/ACOEngine.prod.test.ts +164 -0
- package/src/swarm/__tests__/ACOEngine.test.ts +117 -0
- package/src/swarm/__tests__/CollectiveIntelligence.prod.test.ts +296 -0
- package/src/swarm/__tests__/CollectiveIntelligence.test.ts +457 -0
- package/src/swarm/__tests__/ContributionSynthesizer.prod.test.ts +269 -0
- package/src/swarm/__tests__/ContributionSynthesizer.test.ts +254 -0
- package/src/swarm/__tests__/LeaderElection.prod.test.ts +196 -0
- package/src/swarm/__tests__/LeaderElection.test.ts +151 -0
- package/src/swarm/__tests__/PSOEngine.prod.test.ts +162 -0
- package/src/swarm/__tests__/PSOEngine.test.ts +106 -0
- package/src/swarm/__tests__/QuorumPolicy.prod.test.ts +216 -0
- package/src/swarm/__tests__/QuorumPolicy.test.ts +177 -0
- package/src/swarm/__tests__/SwarmCoordinator.prod.test.ts +186 -0
- package/src/swarm/__tests__/SwarmCoordinator.test.ts +167 -0
- package/src/swarm/__tests__/SwarmManager.prod.test.ts +308 -0
- package/src/swarm/__tests__/SwarmManager.test.ts +373 -0
- package/src/swarm/__tests__/SwarmMembership.prod.test.ts +273 -0
- package/src/swarm/__tests__/SwarmMembership.test.ts +264 -0
- package/src/swarm/__tests__/VotingRound.prod.test.ts +233 -0
- package/src/swarm/__tests__/VotingRound.test.ts +174 -0
- package/src/swarm/analytics/SwarmInspector.ts +476 -0
- package/src/swarm/analytics/SwarmMetrics.ts +449 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.prod.test.ts +366 -0
- package/src/swarm/analytics/__tests__/SwarmInspector.test.ts +454 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.prod.test.ts +254 -0
- package/src/swarm/analytics/__tests__/SwarmMetrics.test.ts +370 -0
- package/src/swarm/analytics/index.ts +7 -0
- package/src/swarm/index.ts +69 -0
- package/src/swarm/messaging/BroadcastChannel.ts +509 -0
- package/src/swarm/messaging/GossipProtocol.ts +565 -0
- package/src/swarm/messaging/SwarmEventBus.ts +443 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.prod.test.ts +331 -0
- package/src/swarm/messaging/__tests__/BroadcastChannel.test.ts +333 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.prod.test.ts +356 -0
- package/src/swarm/messaging/__tests__/GossipProtocol.test.ts +437 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.prod.test.ts +191 -0
- package/src/swarm/messaging/__tests__/SwarmEventBus.test.ts +247 -0
- package/src/swarm/messaging/index.ts +8 -0
- package/src/swarm/spatial/FlockingBehavior.ts +462 -0
- package/src/swarm/spatial/FormationController.ts +500 -0
- package/src/swarm/spatial/Vector3.ts +170 -0
- package/src/swarm/spatial/ZoneClaiming.ts +509 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.prod.test.ts +239 -0
- package/src/swarm/spatial/__tests__/FlockingBehavior.test.ts +298 -0
- package/src/swarm/spatial/__tests__/FormationController.prod.test.ts +240 -0
- package/src/swarm/spatial/__tests__/FormationController.test.ts +297 -0
- package/src/swarm/spatial/__tests__/Vector3.prod.test.ts +283 -0
- package/src/swarm/spatial/__tests__/Vector3.test.ts +224 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.prod.test.ts +246 -0
- package/src/swarm/spatial/__tests__/ZoneClaiming.test.ts +374 -0
- package/src/swarm/spatial/index.ts +28 -0
- package/src/team.ts +1245 -0
- package/src/training/LRScheduler.ts +377 -0
- package/src/training/QualityScoringPipeline.ts +139 -0
- package/src/training/SoftDedup.ts +461 -0
- package/src/training/SparsityMonitor.ts +685 -0
- package/src/training/SparsityMonitorTypes.ts +209 -0
- package/src/training/SpatialTrainingDataGenerator.ts +1526 -0
- package/src/training/SpatialTrainingDataTypes.ts +216 -0
- package/src/training/TrainingPipelineConfig.ts +215 -0
- package/src/training/constants.ts +94 -0
- package/src/training/index.ts +138 -0
- package/src/training/schema.ts +147 -0
- package/src/training/scripts/generate-novel-use-cases-dataset.ts +272 -0
- package/src/training/scripts/generate-spatial-dataset.ts +521 -0
- package/src/training/training/data/novel-use-cases.jsonl +153 -0
- package/src/training/training/data/spatial-reasoning-10k.jsonl +9354 -0
- package/src/training/trainingmonkey/TrainingMonkeyIntegration.ts +477 -0
- package/src/training/trainingmonkey/TrainingMonkeyTypes.ts +230 -0
- package/src/training/trainingmonkey/index.ts +26 -0
- package/src/training/trait-mappings.ts +157 -0
- package/src/types/core-stubs.d.ts +113 -0
- package/src/types.ts +304 -0
- package/test-output.txt +0 -0
- package/test-result.json +1 -0
- package/tsc-errors.txt +4 -0
- package/tsc_output.txt +0 -0
- package/tsconfig.json +14 -0
- package/tsup-learning-esm.config.ts +12 -0
- package/tsup.config.ts +21 -0
- package/typescript-errors-2.txt +0 -0
- package/typescript-errors.txt +22 -0
- package/vitest-log-utf8.txt +268 -0
- package/vitest-log.txt +0 -0
- package/vitest.config.ts +8 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @holoscript/core - Voting Mechanisms
|
|
3
|
+
*
|
|
4
|
+
* Implementations of various voting algorithms for agent negotiation.
|
|
5
|
+
* Part of HoloScript v3.1 Agentic Choreography.
|
|
6
|
+
*
|
|
7
|
+
* Supported mechanisms:
|
|
8
|
+
* - Majority: Simple majority wins
|
|
9
|
+
* - Supermajority: 2/3 threshold required
|
|
10
|
+
* - Weighted: Vote weight by trust level
|
|
11
|
+
* - Consensus: All must agree
|
|
12
|
+
* - Ranked Choice: Instant runoff voting
|
|
13
|
+
* - Approval: Multiple approval voting
|
|
14
|
+
* - Borda Count: Point-based ranking
|
|
15
|
+
*
|
|
16
|
+
* @version 3.1.0
|
|
17
|
+
* @Sprint v3.1 (March 2026)
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import type {
|
|
21
|
+
Vote,
|
|
22
|
+
VoteTally,
|
|
23
|
+
VotingMechanism,
|
|
24
|
+
Proposal,
|
|
25
|
+
ResolutionOutcome,
|
|
26
|
+
NegotiationConfig,
|
|
27
|
+
} from './NegotiationTypes';
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// TYPES
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Result from a voting round
|
|
35
|
+
*/
|
|
36
|
+
export interface VotingResult {
|
|
37
|
+
/** Final tallies */
|
|
38
|
+
tallies: VoteTally[];
|
|
39
|
+
/** Winner ID (if determined) */
|
|
40
|
+
winnerId?: string;
|
|
41
|
+
/** Whether resolution was reached */
|
|
42
|
+
resolved: boolean;
|
|
43
|
+
/** Outcome type */
|
|
44
|
+
outcome: ResolutionOutcome;
|
|
45
|
+
/** Tie detected */
|
|
46
|
+
tie?: boolean;
|
|
47
|
+
/** Eliminated proposals (for ranked choice) */
|
|
48
|
+
eliminated?: string[];
|
|
49
|
+
/** Consensus level (0-1) */
|
|
50
|
+
consensusLevel?: number;
|
|
51
|
+
/** Dissenters */
|
|
52
|
+
dissenters?: string[];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Voting mechanism handler interface
|
|
57
|
+
*/
|
|
58
|
+
export interface VotingHandler {
|
|
59
|
+
/** Count votes and determine result */
|
|
60
|
+
count(
|
|
61
|
+
votes: Vote[],
|
|
62
|
+
proposals: Proposal[],
|
|
63
|
+
config: NegotiationConfig,
|
|
64
|
+
round: number
|
|
65
|
+
): VotingResult;
|
|
66
|
+
|
|
67
|
+
/** Validate a vote for this mechanism */
|
|
68
|
+
validateVote(vote: Vote, proposals: Proposal[]): boolean;
|
|
69
|
+
|
|
70
|
+
/** Get required quorum for this mechanism */
|
|
71
|
+
getRequiredQuorum(config: NegotiationConfig): number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// =============================================================================
|
|
75
|
+
// UTILITY FUNCTIONS
|
|
76
|
+
// =============================================================================
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Calculate total weight of all votes
|
|
80
|
+
*/
|
|
81
|
+
function getTotalWeight(votes: Vote[]): number {
|
|
82
|
+
return votes.reduce((sum, v) => sum + v.weight, 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Build initial tally structure
|
|
87
|
+
*/
|
|
88
|
+
function initializeTallies(proposals: Proposal[]): Map<string, VoteTally> {
|
|
89
|
+
const tallies = new Map<string, VoteTally>();
|
|
90
|
+
for (const p of proposals) {
|
|
91
|
+
tallies.set(p.id, {
|
|
92
|
+
proposalId: p.id,
|
|
93
|
+
voteCount: 0,
|
|
94
|
+
weightedScore: 0,
|
|
95
|
+
approvalCount: 0,
|
|
96
|
+
bordaPoints: 0,
|
|
97
|
+
percentage: 0,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return tallies;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Convert tallies map to sorted array
|
|
105
|
+
*/
|
|
106
|
+
function talliesToArray(tallies: Map<string, VoteTally>, totalWeight: number): VoteTally[] {
|
|
107
|
+
const arr = Array.from(tallies.values());
|
|
108
|
+
// Calculate percentages
|
|
109
|
+
for (const t of arr) {
|
|
110
|
+
t.percentage = totalWeight > 0 ? (t.weightedScore / totalWeight) * 100 : 0;
|
|
111
|
+
}
|
|
112
|
+
// Sort by weighted score descending
|
|
113
|
+
return arr.sort((a, b) => b.weightedScore - a.weightedScore);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Find ties at the top
|
|
118
|
+
*/
|
|
119
|
+
function findTies(tallies: VoteTally[]): VoteTally[] {
|
|
120
|
+
if (tallies.length < 2) return [];
|
|
121
|
+
const topScore = tallies[0].weightedScore;
|
|
122
|
+
return tallies.filter((t) => t.weightedScore === topScore);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Apply tie-breaker
|
|
127
|
+
*/
|
|
128
|
+
function breakTie(
|
|
129
|
+
tied: VoteTally[],
|
|
130
|
+
proposals: Proposal[],
|
|
131
|
+
config: NegotiationConfig
|
|
132
|
+
): string | undefined {
|
|
133
|
+
if (tied.length === 0) return undefined;
|
|
134
|
+
if (tied.length === 1) return tied[0].proposalId;
|
|
135
|
+
|
|
136
|
+
switch (config.tieBreaker) {
|
|
137
|
+
case 'random':
|
|
138
|
+
return tied[Math.floor(Math.random() * tied.length)].proposalId;
|
|
139
|
+
|
|
140
|
+
case 'seniority': {
|
|
141
|
+
// Earliest submitted proposal wins
|
|
142
|
+
const proposalMap = new Map(proposals.map((p) => [p.id, p]));
|
|
143
|
+
const sorted = [...tied].sort((a, b) => {
|
|
144
|
+
const pa = proposalMap.get(a.proposalId);
|
|
145
|
+
const pb = proposalMap.get(b.proposalId);
|
|
146
|
+
return (pa?.submittedAt || 0) - (pb?.submittedAt || 0);
|
|
147
|
+
});
|
|
148
|
+
return sorted[0].proposalId;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
case 'priority': {
|
|
152
|
+
// Highest priority proposal wins
|
|
153
|
+
const proposalMap = new Map(proposals.map((p) => [p.id, p]));
|
|
154
|
+
const sorted = [...tied].sort((a, b) => {
|
|
155
|
+
const pa = proposalMap.get(a.proposalId);
|
|
156
|
+
const pb = proposalMap.get(b.proposalId);
|
|
157
|
+
return (pb?.priority || 0) - (pa?.priority || 0);
|
|
158
|
+
});
|
|
159
|
+
return sorted[0].proposalId;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case 'proposer':
|
|
163
|
+
// First in tied list (arbitrary but deterministic)
|
|
164
|
+
return tied[0].proposalId;
|
|
165
|
+
|
|
166
|
+
case 'escalate':
|
|
167
|
+
default:
|
|
168
|
+
return undefined; // No winner, escalate
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// MAJORITY VOTING
|
|
174
|
+
// =============================================================================
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Simple majority voting (>50% wins)
|
|
178
|
+
*/
|
|
179
|
+
export const majorityHandler: VotingHandler = {
|
|
180
|
+
count(votes, proposals, config, _round): VotingResult {
|
|
181
|
+
const tallies = initializeTallies(proposals);
|
|
182
|
+
const totalWeight = getTotalWeight(votes);
|
|
183
|
+
|
|
184
|
+
// Count first-choice votes
|
|
185
|
+
for (const vote of votes) {
|
|
186
|
+
if (vote.ranking.length > 0) {
|
|
187
|
+
const firstChoice = vote.ranking[0];
|
|
188
|
+
const tally = tallies.get(firstChoice);
|
|
189
|
+
if (tally) {
|
|
190
|
+
tally.voteCount++;
|
|
191
|
+
tally.weightedScore += vote.weight;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const sorted = talliesToArray(tallies, totalWeight);
|
|
197
|
+
const ties = findTies(sorted);
|
|
198
|
+
|
|
199
|
+
// Check if top has majority
|
|
200
|
+
if (sorted.length > 0 && sorted[0].percentage > 50) {
|
|
201
|
+
return {
|
|
202
|
+
tallies: sorted,
|
|
203
|
+
winnerId: sorted[0].proposalId,
|
|
204
|
+
resolved: true,
|
|
205
|
+
outcome: 'winner_declared',
|
|
206
|
+
consensusLevel: sorted[0].percentage / 100,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check for tie
|
|
211
|
+
if (ties.length > 1) {
|
|
212
|
+
const tieBreakWinner = breakTie(ties, proposals, config);
|
|
213
|
+
if (tieBreakWinner) {
|
|
214
|
+
return {
|
|
215
|
+
tallies: sorted,
|
|
216
|
+
winnerId: tieBreakWinner,
|
|
217
|
+
resolved: true,
|
|
218
|
+
outcome: 'tie_broken',
|
|
219
|
+
tie: true,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
tallies: sorted,
|
|
224
|
+
resolved: false,
|
|
225
|
+
outcome: 'deadlock',
|
|
226
|
+
tie: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// No majority but has a leader
|
|
231
|
+
if (sorted.length > 0) {
|
|
232
|
+
return {
|
|
233
|
+
tallies: sorted,
|
|
234
|
+
winnerId: sorted[0].proposalId,
|
|
235
|
+
resolved: true,
|
|
236
|
+
outcome: 'winner_declared',
|
|
237
|
+
consensusLevel: sorted[0].percentage / 100,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
tallies: sorted,
|
|
243
|
+
resolved: false,
|
|
244
|
+
outcome: 'deadlock',
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
validateVote(vote, proposals) {
|
|
249
|
+
if (vote.ranking.length === 0) return false;
|
|
250
|
+
const proposalIds = new Set(proposals.map((p) => p.id));
|
|
251
|
+
return proposalIds.has(vote.ranking[0]);
|
|
252
|
+
},
|
|
253
|
+
|
|
254
|
+
getRequiredQuorum(config) {
|
|
255
|
+
return config.quorum || 0.5;
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
// =============================================================================
|
|
260
|
+
// SUPERMAJORITY VOTING
|
|
261
|
+
// =============================================================================
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Supermajority voting (≥66.67% wins)
|
|
265
|
+
*/
|
|
266
|
+
export const supermajorityHandler: VotingHandler = {
|
|
267
|
+
count(votes, proposals, _config, _round): VotingResult {
|
|
268
|
+
const tallies = initializeTallies(proposals);
|
|
269
|
+
const totalWeight = getTotalWeight(votes);
|
|
270
|
+
|
|
271
|
+
for (const vote of votes) {
|
|
272
|
+
if (vote.ranking.length > 0) {
|
|
273
|
+
const firstChoice = vote.ranking[0];
|
|
274
|
+
const tally = tallies.get(firstChoice);
|
|
275
|
+
if (tally) {
|
|
276
|
+
tally.voteCount++;
|
|
277
|
+
tally.weightedScore += vote.weight;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const sorted = talliesToArray(tallies, totalWeight);
|
|
283
|
+
const threshold = 66.67;
|
|
284
|
+
|
|
285
|
+
if (sorted.length > 0 && sorted[0].percentage >= threshold) {
|
|
286
|
+
return {
|
|
287
|
+
tallies: sorted,
|
|
288
|
+
winnerId: sorted[0].proposalId,
|
|
289
|
+
resolved: true,
|
|
290
|
+
outcome: 'winner_declared',
|
|
291
|
+
consensusLevel: sorted[0].percentage / 100,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
tallies: sorted,
|
|
297
|
+
resolved: false,
|
|
298
|
+
outcome: 'deadlock',
|
|
299
|
+
};
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
validateVote(vote, proposals) {
|
|
303
|
+
return majorityHandler.validateVote(vote, proposals);
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
getRequiredQuorum(config) {
|
|
307
|
+
return config.quorum || 0.67;
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// =============================================================================
|
|
312
|
+
// WEIGHTED VOTING
|
|
313
|
+
// =============================================================================
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Weighted voting (trust-level based weights)
|
|
317
|
+
*/
|
|
318
|
+
export const weightedHandler: VotingHandler = {
|
|
319
|
+
count(votes, proposals, config, round): VotingResult {
|
|
320
|
+
// Same as majority but weights already applied
|
|
321
|
+
return majorityHandler.count(votes, proposals, config, round);
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
validateVote(vote, proposals) {
|
|
325
|
+
return majorityHandler.validateVote(vote, proposals);
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
getRequiredQuorum(config) {
|
|
329
|
+
return config.quorum || 0.5;
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// =============================================================================
|
|
334
|
+
// CONSENSUS VOTING
|
|
335
|
+
// =============================================================================
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Consensus voting (all must agree)
|
|
339
|
+
*/
|
|
340
|
+
export const consensusHandler: VotingHandler = {
|
|
341
|
+
count(votes, proposals, _config, _round): VotingResult {
|
|
342
|
+
if (votes.length === 0) {
|
|
343
|
+
return {
|
|
344
|
+
tallies: [],
|
|
345
|
+
resolved: false,
|
|
346
|
+
outcome: 'deadlock',
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const tallies = initializeTallies(proposals);
|
|
351
|
+
const totalWeight = getTotalWeight(votes);
|
|
352
|
+
|
|
353
|
+
// Count first choices
|
|
354
|
+
const firstChoices = new Map<string, string[]>();
|
|
355
|
+
for (const vote of votes) {
|
|
356
|
+
if (vote.ranking.length > 0) {
|
|
357
|
+
const choice = vote.ranking[0];
|
|
358
|
+
const tally = tallies.get(choice);
|
|
359
|
+
if (tally) {
|
|
360
|
+
tally.voteCount++;
|
|
361
|
+
tally.weightedScore += vote.weight;
|
|
362
|
+
}
|
|
363
|
+
if (!firstChoices.has(choice)) {
|
|
364
|
+
firstChoices.set(choice, []);
|
|
365
|
+
}
|
|
366
|
+
firstChoices.get(choice)!.push(vote.agentId);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const sorted = talliesToArray(tallies, totalWeight);
|
|
371
|
+
|
|
372
|
+
// Check if all votes are for the same proposal
|
|
373
|
+
if (firstChoices.size === 1) {
|
|
374
|
+
const winnerId = Array.from(firstChoices.keys())[0];
|
|
375
|
+
return {
|
|
376
|
+
tallies: sorted,
|
|
377
|
+
winnerId,
|
|
378
|
+
resolved: true,
|
|
379
|
+
outcome: 'consensus_reached',
|
|
380
|
+
consensusLevel: 1.0,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// No consensus - identify dissenters
|
|
385
|
+
const leader = sorted[0];
|
|
386
|
+
const leaderVoters = new Set(firstChoices.get(leader.proposalId) || []);
|
|
387
|
+
const dissenters = votes.filter((v) => !leaderVoters.has(v.agentId)).map((v) => v.agentId);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
tallies: sorted,
|
|
391
|
+
resolved: false,
|
|
392
|
+
outcome: 'deadlock',
|
|
393
|
+
consensusLevel: leader.percentage / 100,
|
|
394
|
+
dissenters,
|
|
395
|
+
};
|
|
396
|
+
},
|
|
397
|
+
|
|
398
|
+
validateVote(vote, proposals) {
|
|
399
|
+
return majorityHandler.validateVote(vote, proposals);
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
getRequiredQuorum() {
|
|
403
|
+
return 1.0; // All must participate
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// =============================================================================
|
|
408
|
+
// RANKED CHOICE (INSTANT RUNOFF)
|
|
409
|
+
// =============================================================================
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Ranked choice voting with instant runoff
|
|
413
|
+
*/
|
|
414
|
+
export const rankedHandler: VotingHandler = {
|
|
415
|
+
count(votes, proposals, _config, _round): VotingResult {
|
|
416
|
+
const eliminated = new Set<string>();
|
|
417
|
+
const runoffVotes = votes.map((v) => ({
|
|
418
|
+
...v,
|
|
419
|
+
currentRanking: [...v.ranking],
|
|
420
|
+
}));
|
|
421
|
+
const totalWeight = getTotalWeight(votes);
|
|
422
|
+
let rounds = 0;
|
|
423
|
+
const maxRounds = proposals.length;
|
|
424
|
+
|
|
425
|
+
while (rounds < maxRounds) {
|
|
426
|
+
rounds++;
|
|
427
|
+
|
|
428
|
+
// Count current first choices
|
|
429
|
+
const tallies = initializeTallies(proposals.filter((p) => !eliminated.has(p.id)));
|
|
430
|
+
|
|
431
|
+
for (const vote of runoffVotes) {
|
|
432
|
+
// Find first non-eliminated choice
|
|
433
|
+
const validChoice = vote.currentRanking.find((id) => !eliminated.has(id));
|
|
434
|
+
if (validChoice) {
|
|
435
|
+
const tally = tallies.get(validChoice);
|
|
436
|
+
if (tally) {
|
|
437
|
+
tally.voteCount++;
|
|
438
|
+
tally.weightedScore += vote.weight;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const sorted = talliesToArray(tallies, totalWeight);
|
|
444
|
+
|
|
445
|
+
// Check for majority
|
|
446
|
+
if (sorted.length > 0 && sorted[0].percentage > 50) {
|
|
447
|
+
return {
|
|
448
|
+
tallies: sorted,
|
|
449
|
+
winnerId: sorted[0].proposalId,
|
|
450
|
+
resolved: true,
|
|
451
|
+
outcome: 'winner_declared',
|
|
452
|
+
eliminated: Array.from(eliminated),
|
|
453
|
+
consensusLevel: sorted[0].percentage / 100,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Eliminate lowest
|
|
458
|
+
if (sorted.length > 1) {
|
|
459
|
+
const loser = sorted[sorted.length - 1];
|
|
460
|
+
eliminated.add(loser.proposalId);
|
|
461
|
+
} else if (sorted.length === 1) {
|
|
462
|
+
// Only one left
|
|
463
|
+
return {
|
|
464
|
+
tallies: sorted,
|
|
465
|
+
winnerId: sorted[0].proposalId,
|
|
466
|
+
resolved: true,
|
|
467
|
+
outcome: 'winner_declared',
|
|
468
|
+
eliminated: Array.from(eliminated),
|
|
469
|
+
};
|
|
470
|
+
} else {
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
tallies: [],
|
|
477
|
+
resolved: false,
|
|
478
|
+
outcome: 'deadlock',
|
|
479
|
+
eliminated: Array.from(eliminated),
|
|
480
|
+
};
|
|
481
|
+
},
|
|
482
|
+
|
|
483
|
+
validateVote(vote, proposals) {
|
|
484
|
+
if (vote.ranking.length === 0) return false;
|
|
485
|
+
const proposalIds = new Set(proposals.map((p) => p.id));
|
|
486
|
+
return vote.ranking.every((id) => proposalIds.has(id));
|
|
487
|
+
},
|
|
488
|
+
|
|
489
|
+
getRequiredQuorum(config) {
|
|
490
|
+
return config.quorum || 0.5;
|
|
491
|
+
},
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
// =============================================================================
|
|
495
|
+
// APPROVAL VOTING
|
|
496
|
+
// =============================================================================
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Approval voting (vote for multiple acceptable options)
|
|
500
|
+
*/
|
|
501
|
+
export const approvalHandler: VotingHandler = {
|
|
502
|
+
count(votes, proposals, config, _round): VotingResult {
|
|
503
|
+
const tallies = initializeTallies(proposals);
|
|
504
|
+
const totalVotes = votes.length;
|
|
505
|
+
|
|
506
|
+
for (const vote of votes) {
|
|
507
|
+
const approvals = vote.approvals || vote.ranking;
|
|
508
|
+
for (const proposalId of approvals) {
|
|
509
|
+
const tally = tallies.get(proposalId);
|
|
510
|
+
if (tally) {
|
|
511
|
+
tally.voteCount++;
|
|
512
|
+
tally.weightedScore += vote.weight;
|
|
513
|
+
tally.approvalCount = (tally.approvalCount || 0) + 1;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const sorted = talliesToArray(
|
|
519
|
+
tallies,
|
|
520
|
+
votes.reduce((s, v) => s + v.weight, 0)
|
|
521
|
+
);
|
|
522
|
+
const ties = findTies(sorted);
|
|
523
|
+
|
|
524
|
+
if (ties.length > 1) {
|
|
525
|
+
const tieBreakWinner = breakTie(ties, proposals, config);
|
|
526
|
+
if (tieBreakWinner) {
|
|
527
|
+
return {
|
|
528
|
+
tallies: sorted,
|
|
529
|
+
winnerId: tieBreakWinner,
|
|
530
|
+
resolved: true,
|
|
531
|
+
outcome: 'tie_broken',
|
|
532
|
+
tie: true,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (sorted.length > 0) {
|
|
538
|
+
return {
|
|
539
|
+
tallies: sorted,
|
|
540
|
+
winnerId: sorted[0].proposalId,
|
|
541
|
+
resolved: true,
|
|
542
|
+
outcome: 'winner_declared',
|
|
543
|
+
consensusLevel: totalVotes > 0 ? (sorted[0].approvalCount || 0) / totalVotes : 0,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
tallies: sorted,
|
|
549
|
+
resolved: false,
|
|
550
|
+
outcome: 'deadlock',
|
|
551
|
+
};
|
|
552
|
+
},
|
|
553
|
+
|
|
554
|
+
validateVote(vote, proposals) {
|
|
555
|
+
const approvals = vote.approvals || vote.ranking;
|
|
556
|
+
if (approvals.length === 0) return false;
|
|
557
|
+
const proposalIds = new Set(proposals.map((p) => p.id));
|
|
558
|
+
return approvals.every((id) => proposalIds.has(id));
|
|
559
|
+
},
|
|
560
|
+
|
|
561
|
+
getRequiredQuorum(config) {
|
|
562
|
+
return config.quorum || 0.5;
|
|
563
|
+
},
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
// =============================================================================
|
|
567
|
+
// BORDA COUNT
|
|
568
|
+
// =============================================================================
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Borda count voting (points by rank position)
|
|
572
|
+
*/
|
|
573
|
+
export const bordaHandler: VotingHandler = {
|
|
574
|
+
count(votes, proposals, config, _round): VotingResult {
|
|
575
|
+
const tallies = initializeTallies(proposals);
|
|
576
|
+
const n = proposals.length;
|
|
577
|
+
|
|
578
|
+
for (const vote of votes) {
|
|
579
|
+
// Award points: n-1 for 1st, n-2 for 2nd, etc.
|
|
580
|
+
for (let i = 0; i < vote.ranking.length; i++) {
|
|
581
|
+
const proposalId = vote.ranking[i];
|
|
582
|
+
const tally = tallies.get(proposalId);
|
|
583
|
+
if (tally) {
|
|
584
|
+
const points = (n - 1 - i) * vote.weight;
|
|
585
|
+
tally.bordaPoints = (tally.bordaPoints || 0) + points;
|
|
586
|
+
tally.weightedScore += points;
|
|
587
|
+
tally.voteCount++;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const sorted = talliesToArray(tallies, n * votes.reduce((s, v) => s + v.weight, 0));
|
|
593
|
+
const ties = findTies(sorted);
|
|
594
|
+
|
|
595
|
+
if (ties.length > 1) {
|
|
596
|
+
const tieBreakWinner = breakTie(ties, proposals, config);
|
|
597
|
+
if (tieBreakWinner) {
|
|
598
|
+
return {
|
|
599
|
+
tallies: sorted,
|
|
600
|
+
winnerId: tieBreakWinner,
|
|
601
|
+
resolved: true,
|
|
602
|
+
outcome: 'tie_broken',
|
|
603
|
+
tie: true,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (sorted.length > 0) {
|
|
609
|
+
return {
|
|
610
|
+
tallies: sorted,
|
|
611
|
+
winnerId: sorted[0].proposalId,
|
|
612
|
+
resolved: true,
|
|
613
|
+
outcome: 'winner_declared',
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return {
|
|
618
|
+
tallies: sorted,
|
|
619
|
+
resolved: false,
|
|
620
|
+
outcome: 'deadlock',
|
|
621
|
+
};
|
|
622
|
+
},
|
|
623
|
+
|
|
624
|
+
validateVote(vote, proposals) {
|
|
625
|
+
return rankedHandler.validateVote(vote, proposals);
|
|
626
|
+
},
|
|
627
|
+
|
|
628
|
+
getRequiredQuorum(config) {
|
|
629
|
+
return config.quorum || 0.5;
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
// =============================================================================
|
|
634
|
+
// MECHANISM REGISTRY
|
|
635
|
+
// =============================================================================
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Get handler for a voting mechanism
|
|
639
|
+
*/
|
|
640
|
+
export function getVotingHandler(mechanism: VotingMechanism): VotingHandler {
|
|
641
|
+
switch (mechanism) {
|
|
642
|
+
case 'majority':
|
|
643
|
+
return majorityHandler;
|
|
644
|
+
case 'supermajority':
|
|
645
|
+
return supermajorityHandler;
|
|
646
|
+
case 'weighted':
|
|
647
|
+
return weightedHandler;
|
|
648
|
+
case 'consensus':
|
|
649
|
+
return consensusHandler;
|
|
650
|
+
case 'ranked':
|
|
651
|
+
return rankedHandler;
|
|
652
|
+
case 'approval':
|
|
653
|
+
return approvalHandler;
|
|
654
|
+
case 'borda':
|
|
655
|
+
return bordaHandler;
|
|
656
|
+
case 'custom':
|
|
657
|
+
default:
|
|
658
|
+
return majorityHandler;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Check if quorum is met
|
|
664
|
+
*/
|
|
665
|
+
export function checkQuorum(
|
|
666
|
+
votes: Vote[],
|
|
667
|
+
participants: number,
|
|
668
|
+
config: NegotiationConfig,
|
|
669
|
+
mechanism: VotingMechanism
|
|
670
|
+
): boolean {
|
|
671
|
+
const handler = getVotingHandler(mechanism);
|
|
672
|
+
const requiredQuorum = handler.getRequiredQuorum(config);
|
|
673
|
+
const participation = participants > 0 ? votes.length / participants : 0;
|
|
674
|
+
return participation >= requiredQuorum;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* Get trust-based vote weight
|
|
679
|
+
*/
|
|
680
|
+
export function getTrustWeight(trustLevel: 'local' | 'verified' | 'external'): number {
|
|
681
|
+
switch (trustLevel) {
|
|
682
|
+
case 'local':
|
|
683
|
+
return 1.0;
|
|
684
|
+
case 'verified':
|
|
685
|
+
return 0.8;
|
|
686
|
+
case 'external':
|
|
687
|
+
return 0.5;
|
|
688
|
+
default:
|
|
689
|
+
return 0.5;
|
|
690
|
+
}
|
|
691
|
+
}
|