mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
108676e602
Phase 2 Implementation (95% complete): - All 12 planned deliverables implemented - 5/5 validation criteria passing (VC1-VC5) - 0 TypeScript errors, 0 critical bugs - Comprehensive documentation (14 files) Core Infrastructure: - Agent lifecycle instrumentation (agent.ts, task.ts) - Memory operation instrumentation (memory.ts) with SwarmMemoryManager integration - Token tracking and cost calculation (CostTracker) - 4 clause evaluators (AST, metric, pattern, semantic) - Multi-agent voting protocol with consensus algorithms - Distributed trace propagation with OTEL CLI Commands (Phase 3/4 completed early): - Telemetry: status, metrics, trace, export-prometheus - Constitution: validate, list, show, evaluate Validation: - VC1: Agent trace retrieval ✅ - VC2: Token tracking ✅ - VC3: Clause evaluation ✅ - VC4: Multi-agent voting ✅ - VC5: Memory operation instrumentation ✅ Tests: - 5 validation scripts (790+ LOC) - 13+ unit tests - Integration tests for SwarmMemoryManager - All tests passing Documentation: - MEMORY-TS-ANALYSIS.md (10+ pages deep analysis) - PHASE2-FINAL-STATUS.md (comprehensive status) - BRUTAL-HONESTY-REVIEW-PHASE2.md (technical review) - SESSION-SUMMARY.md (achievements summary) - CLI documentation (telemetry + constitution) - Memory instrumentation guides (integration + examples) Files Created (60+): - Instrumentation: 4 files (agent, task, memory, index) - Evaluators: 6 files (AST, metric, pattern, semantic, base, index) - Voting: 6 files (protocol, panel, consensus, orchestrator, types, index) - Validation: 5 scripts - Documentation: 14+ files - Tests: 10+ test files - CLI: 2 command files What's Left (5%): - CLI integration testing against real OTEL collector - Real-world validation with production agent workflows Ready for Phase 3: ✅ Dashboards & Visualization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
506 lines
20 KiB
TypeScript
506 lines
20 KiB
TypeScript
/**
|
|
* Unit Tests for Consensus Algorithms
|
|
*
|
|
* Tests majority, weighted, and Bayesian consensus
|
|
*/
|
|
|
|
import {
|
|
Vote,
|
|
VoteType,
|
|
VoteTally,
|
|
ConsensusConfig,
|
|
AgentExpertise,
|
|
BayesianPrior,
|
|
Clause
|
|
} from '../../src/voting/protocol';
|
|
|
|
import {
|
|
calculateVoteTally,
|
|
majorityConsensus,
|
|
weightedConsensus,
|
|
bayesianConsensus,
|
|
calculateAgreementMetrics
|
|
} from '../../src/voting/consensus';
|
|
|
|
describe('Consensus Algorithms', () => {
|
|
// Test data setup
|
|
const mockClause: Clause = {
|
|
id: 'clause-1',
|
|
text: 'Test clause',
|
|
category: 'test',
|
|
priority: 1,
|
|
requiredQuorum: 3
|
|
};
|
|
|
|
const defaultConfig: ConsensusConfig = {
|
|
algorithm: 'majority',
|
|
minimumQuorum: 3,
|
|
approvalThreshold: 0.5,
|
|
confidenceThreshold: 0.3,
|
|
tieBreaker: 'reject'
|
|
};
|
|
|
|
describe('calculateVoteTally', () => {
|
|
it('should correctly tally votes', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.7, timestamp: Date.now() },
|
|
{ agentId: 'agent-4', clauseId: 'clause-1', vote: VoteType.ABSTAIN, confidence: 0.5, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
|
|
expect(tally.approvals).toBe(2);
|
|
expect(tally.rejections).toBe(1);
|
|
expect(tally.abstentions).toBe(1);
|
|
expect(tally.totalVotes).toBe(4);
|
|
});
|
|
|
|
it('should handle empty votes', () => {
|
|
const tally = calculateVoteTally([], mockClause);
|
|
|
|
expect(tally.approvals).toBe(0);
|
|
expect(tally.rejections).toBe(0);
|
|
expect(tally.abstentions).toBe(0);
|
|
expect(tally.totalVotes).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('majorityConsensus', () => {
|
|
it('should approve with simple majority', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() },
|
|
{ agentId: 'agent-4', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.7, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
expect(result.metadata.quorumMet).toBe(true);
|
|
expect(result.confidence).toBeGreaterThan(0);
|
|
expect(result.agreement).toBeGreaterThan(0.5);
|
|
});
|
|
|
|
it('should reject with majority rejections', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.85, timestamp: Date.now() },
|
|
{ agentId: 'agent-4', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.7, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('rejected');
|
|
expect(result.disputedAgents).toContain('agent-4');
|
|
});
|
|
|
|
it('should handle quorum not met', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.8, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('disputed');
|
|
expect(result.metadata.quorumMet).toBe(false);
|
|
expect(result.confidence).toBe(0);
|
|
});
|
|
|
|
it('should filter low-confidence votes', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() },
|
|
{ agentId: 'agent-4', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.2, timestamp: Date.now() } // Below threshold
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
expect(result.disputedAgents).toBeUndefined(); // Low confidence vote ignored
|
|
});
|
|
|
|
it('should handle exact tie', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.85, timestamp: Date.now() },
|
|
{ agentId: 'agent-4', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.7, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
// With tie-breaker set to 'reject'
|
|
expect(result.decision).toBe('rejected');
|
|
});
|
|
|
|
it('should handle all abstentions', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.ABSTAIN, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.ABSTAIN, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.ABSTAIN, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('disputed');
|
|
});
|
|
});
|
|
|
|
describe('weightedConsensus', () => {
|
|
const expertiseMap = new Map<string, AgentExpertise>([
|
|
['expert-1', {
|
|
agentId: 'expert-1',
|
|
domain: 'security',
|
|
expertiseLevel: 0.9,
|
|
successRate: 0.95,
|
|
totalVotes: 100,
|
|
correctVotes: 95
|
|
}],
|
|
['expert-2', {
|
|
agentId: 'expert-2',
|
|
domain: 'security',
|
|
expertiseLevel: 0.7,
|
|
successRate: 0.8,
|
|
totalVotes: 50,
|
|
correctVotes: 40
|
|
}],
|
|
['novice-1', {
|
|
agentId: 'novice-1',
|
|
domain: 'security',
|
|
expertiseLevel: 0.3,
|
|
successRate: 0.6,
|
|
totalVotes: 10,
|
|
correctVotes: 6
|
|
}]
|
|
]);
|
|
|
|
const weightedConfig: ConsensusConfig = {
|
|
...defaultConfig,
|
|
algorithm: 'weighted',
|
|
weightingStrategy: 'linear'
|
|
};
|
|
|
|
it('should weight expert votes more heavily', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'expert-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.95, timestamp: Date.now() },
|
|
{ agentId: 'novice-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.7, timestamp: Date.now() },
|
|
{ agentId: 'expert-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = weightedConsensus(tally, expertiseMap, weightedConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
expect(result.algorithm).toBe('weighted');
|
|
});
|
|
|
|
it('should handle exponential weighting', () => {
|
|
const expConfig: ConsensusConfig = {
|
|
...weightedConfig,
|
|
weightingStrategy: 'exponential'
|
|
};
|
|
|
|
const votes: Vote[] = [
|
|
{ agentId: 'expert-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.95, timestamp: Date.now() },
|
|
{ agentId: 'novice-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'novice-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = weightedConsensus(tally, expertiseMap, expConfig);
|
|
|
|
// Expert vote should still dominate due to exponential weighting
|
|
expect(result.decision).toBe('approved');
|
|
});
|
|
|
|
it('should handle unknown agents with default weight', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'unknown-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'unknown-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.7, timestamp: Date.now() },
|
|
{ agentId: 'unknown-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = weightedConsensus(tally, new Map(), weightedConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
});
|
|
|
|
it('should handle sigmoid weighting strategy', () => {
|
|
const sigmoidConfig: ConsensusConfig = {
|
|
...weightedConfig,
|
|
weightingStrategy: 'sigmoid'
|
|
};
|
|
|
|
const votes: Vote[] = [
|
|
{ agentId: 'expert-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.95, timestamp: Date.now() },
|
|
{ agentId: 'expert-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() },
|
|
{ agentId: 'novice-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.7, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = weightedConsensus(tally, expertiseMap, sigmoidConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
expect(result.confidence).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
describe('bayesianConsensus', () => {
|
|
const expertiseMap = new Map<string, AgentExpertise>([
|
|
['reliable-1', {
|
|
agentId: 'reliable-1',
|
|
domain: 'security',
|
|
expertiseLevel: 0.9,
|
|
successRate: 0.9,
|
|
totalVotes: 100,
|
|
correctVotes: 90
|
|
}],
|
|
['reliable-2', {
|
|
agentId: 'reliable-2',
|
|
domain: 'security',
|
|
expertiseLevel: 0.8,
|
|
successRate: 0.85,
|
|
totalVotes: 80,
|
|
correctVotes: 68
|
|
}],
|
|
['unreliable-1', {
|
|
agentId: 'unreliable-1',
|
|
domain: 'security',
|
|
expertiseLevel: 0.4,
|
|
successRate: 0.5,
|
|
totalVotes: 20,
|
|
correctVotes: 10
|
|
}]
|
|
]);
|
|
|
|
const prior: BayesianPrior = {
|
|
clauseId: 'clause-1',
|
|
priorProbability: 0.5, // Neutral prior
|
|
priorConfidence: 0.3,
|
|
evidenceWeight: 0.7
|
|
};
|
|
|
|
const bayesianConfig: ConsensusConfig = {
|
|
...defaultConfig,
|
|
algorithm: 'bayesian'
|
|
};
|
|
|
|
it('should update belief with reliable agent votes', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'reliable-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.95, timestamp: Date.now() },
|
|
{ agentId: 'reliable-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'unreliable-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.6, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = bayesianConsensus(tally, prior, expertiseMap, bayesianConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
expect(result.confidence).toBeGreaterThan(0.5);
|
|
});
|
|
|
|
it('should handle strong prior belief', () => {
|
|
const strongPrior: BayesianPrior = {
|
|
clauseId: 'clause-1',
|
|
priorProbability: 0.9, // Strong belief it should be approved
|
|
priorConfidence: 0.8,
|
|
evidenceWeight: 0.3 // Low weight on new evidence
|
|
};
|
|
|
|
const votes: Vote[] = [
|
|
{ agentId: 'reliable-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'reliable-2', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = bayesianConsensus(tally, strongPrior, expertiseMap, bayesianConfig);
|
|
|
|
// Strong prior should resist evidence somewhat
|
|
expect(result.confidence).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should handle abstentions in Bayesian update', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'reliable-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'reliable-2', clauseId: 'clause-1', vote: VoteType.ABSTAIN, confidence: 0.5, timestamp: Date.now() },
|
|
{ agentId: 'unreliable-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.7, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = bayesianConsensus(tally, prior, expertiseMap, bayesianConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
});
|
|
|
|
it('should result in disputed when evidence is weak', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'unreliable-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.5, timestamp: Date.now() },
|
|
{ agentId: 'reliable-1', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.6, timestamp: Date.now() },
|
|
{ agentId: 'reliable-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.55, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
|
|
// Set higher approval threshold to make decision harder
|
|
const strictConfig: ConsensusConfig = {
|
|
...bayesianConfig,
|
|
approvalThreshold: 0.7
|
|
};
|
|
|
|
const result = bayesianConsensus(tally, prior, expertiseMap, strictConfig);
|
|
|
|
expect(result.decision).toBe('disputed');
|
|
});
|
|
|
|
it('should handle agents with no expertise data', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'unknown-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'unknown-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.75, timestamp: Date.now() },
|
|
{ agentId: 'unknown-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = bayesianConsensus(tally, prior, new Map(), bayesianConfig);
|
|
|
|
// Should default to 0.5 accuracy for unknown agents
|
|
expect(result.decision).toBe('approved');
|
|
});
|
|
});
|
|
|
|
describe('calculateAgreementMetrics', () => {
|
|
it('should calculate perfect agreement', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const metrics = calculateAgreementMetrics(votes);
|
|
|
|
expect(metrics.overallAgreement).toBe(1);
|
|
expect(metrics.unanimity).toBe(true);
|
|
expect(metrics.polarization).toBe(0);
|
|
});
|
|
|
|
it('should calculate split decision', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.85, timestamp: Date.now() },
|
|
{ agentId: 'agent-4', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.75, timestamp: Date.now() }
|
|
];
|
|
|
|
const metrics = calculateAgreementMetrics(votes);
|
|
|
|
expect(metrics.overallAgreement).toBeLessThan(1);
|
|
expect(metrics.unanimity).toBe(false);
|
|
expect(metrics.polarization).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should ignore abstentions in pairwise agreement', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.ABSTAIN, confidence: 0.5, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.85, timestamp: Date.now() }
|
|
];
|
|
|
|
const metrics = calculateAgreementMetrics(votes);
|
|
|
|
expect(metrics.unanimity).toBe(true); // Only non-abstaining votes count
|
|
});
|
|
|
|
it('should calculate average confidence', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.8, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.REJECT, confidence: 0.6, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 1.0, timestamp: Date.now() }
|
|
];
|
|
|
|
const metrics = calculateAgreementMetrics(votes);
|
|
|
|
expect(metrics.confidence).toBeCloseTo(0.8, 1);
|
|
});
|
|
});
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should handle single vote', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.9, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
// Quorum not met
|
|
expect(result.decision).toBe('disputed');
|
|
});
|
|
|
|
it('should handle zero votes', () => {
|
|
const votes: Vote[] = [];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('disputed');
|
|
expect(result.confidence).toBe(0);
|
|
});
|
|
|
|
it('should handle all votes below confidence threshold', () => {
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.2, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.1, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.15, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const result = majorityConsensus(tally, defaultConfig);
|
|
|
|
expect(result.decision).toBe('disputed');
|
|
});
|
|
|
|
it('should handle numerical precision in weighted voting', () => {
|
|
const expertiseMap = new Map<string, AgentExpertise>([
|
|
['agent-1', {
|
|
agentId: 'agent-1',
|
|
domain: 'test',
|
|
expertiseLevel: 0.33333333,
|
|
successRate: 0.66666666,
|
|
totalVotes: 3,
|
|
correctVotes: 2
|
|
}]
|
|
]);
|
|
|
|
const votes: Vote[] = [
|
|
{ agentId: 'agent-1', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.77777777, timestamp: Date.now() },
|
|
{ agentId: 'agent-2', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.88888888, timestamp: Date.now() },
|
|
{ agentId: 'agent-3', clauseId: 'clause-1', vote: VoteType.APPROVE, confidence: 0.99999999, timestamp: Date.now() }
|
|
];
|
|
|
|
const tally = calculateVoteTally(votes, mockClause);
|
|
const weightedConfig: ConsensusConfig = {
|
|
...defaultConfig,
|
|
algorithm: 'weighted'
|
|
};
|
|
|
|
const result = weightedConsensus(tally, expertiseMap, weightedConfig);
|
|
|
|
expect(result.decision).toBe('approved');
|
|
expect(result.confidence).toBeGreaterThanOrEqual(0);
|
|
expect(result.confidence).toBeLessThanOrEqual(1);
|
|
});
|
|
});
|
|
});
|