717ae1031e
Zanzibar-style authorization graph engine (direct/chain/TTU/defeasible/ binary modes, condensed snapshots, value relations) with 39 rigor test campaigns. Includes fixes for snapshot binary writer/reader format mismatch (snapshot-of-snapshot corruption), possibility write-boundary validation, empty-graph snapshot serialization, relation lookup cache direction collision, config-redefinition cache invalidation, binary threshold semantics, defeasible compiled routing, and comparator reason whitelisting.
350 lines
11 KiB
JavaScript
350 lines
11 KiB
JavaScript
/**
|
|
* Test Unified Evidence Fusion System
|
|
*
|
|
* This test demonstrates the new separation of concerns between aggregation
|
|
* (OWA) and reconciliation (bilattice/Dempster-Shafer) logic, supporting
|
|
* both qualitative and quantitative modes with the same lexicon.
|
|
*/
|
|
|
|
import { test, describe } from 'node:test';
|
|
import assert from 'node:assert';
|
|
import {
|
|
QualitativeScale,
|
|
UnifiedEvidenceFusion,
|
|
EvidenceAggregation,
|
|
EvidenceReconciliation,
|
|
NumericBilatticeOrderings
|
|
} from '../../src/qualitative/index.js';
|
|
|
|
describe('Unified Evidence Fusion System', () => {
|
|
|
|
test('should demonstrate aggregation vs reconciliation separation', () => {
|
|
// Test data with conflicting evidence
|
|
const collectedValues = [
|
|
{
|
|
value: 0.8,
|
|
possibility: 0.9,
|
|
path: ['evidence1'],
|
|
source: { type: 'direct', confidence: 0.95 },
|
|
metadata: { timestamp: Date.now(), reliability: 0.9 }
|
|
},
|
|
{
|
|
value: 0.3,
|
|
possibility: 0.4,
|
|
path: ['evidence2'],
|
|
source: { type: 'inferred', confidence: 0.6 },
|
|
metadata: { timestamp: Date.now(), reliability: 0.7 }
|
|
},
|
|
{
|
|
value: 0.7,
|
|
possibility: 0.6,
|
|
path: ['evidence3'],
|
|
source: { type: 'derived', confidence: 0.8 },
|
|
metadata: { timestamp: Date.now(), reliability: 0.8 }
|
|
}
|
|
];
|
|
|
|
// Test pure aggregation (no reconciliation)
|
|
const aggregationResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
aggregationMethod: 'majority',
|
|
useReconciliation: false
|
|
});
|
|
|
|
assert.ok(aggregationResult.hasValue);
|
|
assert.strictEqual(aggregationResult.fusionMethod, 'aggregation');
|
|
assert.strictEqual(aggregationResult.reconciliationMethod, 'none');
|
|
assert.ok(aggregationResult.epistemicAnalysis === null);
|
|
|
|
// Test reconciliation-based fusion
|
|
const reconciliationResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
aggregationMethod: 'max',
|
|
reconciliationMethod: 'dempster_shafer',
|
|
epistemicMode: 'hybrid',
|
|
useReconciliation: true
|
|
});
|
|
|
|
assert.ok(reconciliationResult.hasValue);
|
|
assert.strictEqual(reconciliationResult.fusionMethod, 'reconciliation');
|
|
assert.strictEqual(reconciliationResult.reconciliationMethod, 'dempster_shafer');
|
|
assert.ok(reconciliationResult.epistemicAnalysis !== null);
|
|
});
|
|
|
|
test('should support both qualitative and quantitative modes with same lexicon', () => {
|
|
const collectedValues = [
|
|
{
|
|
value: 0.75, // Valid fivePoint scale value
|
|
possibility: 0.75,
|
|
path: ['evidence1'],
|
|
source: { type: 'direct' },
|
|
metadata: { reliability: 0.9 }
|
|
},
|
|
{
|
|
value: 0.5, // Valid fivePoint scale value
|
|
possibility: 0.5,
|
|
path: ['evidence2'],
|
|
source: { type: 'inferred' },
|
|
metadata: { reliability: 0.7 }
|
|
}
|
|
];
|
|
|
|
const scale = QualitativeScale.fivePoint();
|
|
|
|
// Test quantitative mode
|
|
const quantitativeResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
aggregationMethod: 'average',
|
|
useReconciliation: false
|
|
});
|
|
|
|
// Test qualitative mode
|
|
const qualitativeResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'qualitative',
|
|
aggregationMethod: 'average',
|
|
scale: scale,
|
|
useReconciliation: false
|
|
});
|
|
|
|
assert.ok(quantitativeResult.hasValue);
|
|
assert.ok(qualitativeResult.hasValue);
|
|
assert.strictEqual(quantitativeResult.aggregationMethod, 'average');
|
|
assert.strictEqual(qualitativeResult.aggregationMethod, 'average');
|
|
|
|
// Both should use the same aggregation lexicon
|
|
assert.ok(quantitativeResult.value > 0);
|
|
assert.ok(qualitativeResult.value > 0);
|
|
});
|
|
|
|
test('should demonstrate numeric bilattice orderings', () => {
|
|
// Create a simple numeric capacity
|
|
const capacity = {
|
|
stateSpace: ['evidence1', 'evidence2', 'evidence3'],
|
|
getCapacity: (set) => {
|
|
// For single elements
|
|
if (set.size === 1) {
|
|
if (set.has('evidence1')) return 0.8;
|
|
if (set.has('evidence2')) return 0.6;
|
|
if (set.has('evidence3')) return 0.4;
|
|
}
|
|
// For complements (multiple elements)
|
|
if (set.size === 2) {
|
|
return 0.2; // Some belief in complements
|
|
}
|
|
// For empty set
|
|
if (set.size === 0) {
|
|
return 0;
|
|
}
|
|
// For full set
|
|
if (set.size === 3) {
|
|
return 1.0;
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
const propositions = [['evidence1'], ['evidence2'], ['evidence3']];
|
|
|
|
// Test information ordering
|
|
const mostInformative = NumericBilatticeOrderings.findMostInformative(propositions, capacity);
|
|
assert.ok(mostInformative);
|
|
assert.strictEqual(mostInformative.rank, 1);
|
|
|
|
// Test truth ordering
|
|
const mostTrue = NumericBilatticeOrderings.findMostTrue(propositions, capacity);
|
|
assert.ok(mostTrue);
|
|
assert.strictEqual(mostTrue.rank, 1);
|
|
|
|
// Test Dempster-Shafer measures
|
|
const belief = NumericBilatticeOrderings.dempsterShaferBelief(['evidence1'], capacity);
|
|
const plausibility = NumericBilatticeOrderings.dempsterShaferPlausibility(['evidence1'], capacity);
|
|
const uncertainty = NumericBilatticeOrderings.dempsterShaferUncertainty(['evidence1'], capacity);
|
|
|
|
assert.strictEqual(belief, 0.8);
|
|
// Plausibility should be 1 - capacity of complement
|
|
// Complement of ['evidence1'] is ['evidence2', 'evidence3']
|
|
// Capacity of ['evidence2', 'evidence3'] is 0.2
|
|
// So plausibility = 1 - 0.2 = 0.8
|
|
assert.strictEqual(plausibility, 0.8);
|
|
assert.strictEqual(uncertainty, 0); // 0.8 - 0.8 = 0
|
|
});
|
|
|
|
test('should demonstrate reconciliation methods comparison', () => {
|
|
const collectedValues = [
|
|
{
|
|
value: 0.8,
|
|
possibility: 0.9,
|
|
path: ['evidence1'],
|
|
source: { type: 'direct' },
|
|
metadata: { reliability: 0.9 }
|
|
},
|
|
{
|
|
value: 0.3,
|
|
possibility: 0.4,
|
|
path: ['evidence2'],
|
|
source: { type: 'inferred' },
|
|
metadata: { reliability: 0.7 }
|
|
}
|
|
];
|
|
|
|
// Test different reconciliation methods
|
|
const bilatticeResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
reconciliationMethod: 'bilattice',
|
|
epistemicMode: 'hybrid',
|
|
useReconciliation: true
|
|
});
|
|
|
|
const dempsterShaferResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
reconciliationMethod: 'dempster_shafer',
|
|
epistemicMode: 'hybrid',
|
|
useReconciliation: true
|
|
});
|
|
|
|
const subjectiveLogicResult = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
reconciliationMethod: 'subjective_logic',
|
|
epistemicMode: 'hybrid',
|
|
useReconciliation: true
|
|
});
|
|
|
|
// All should produce valid results
|
|
assert.ok(bilatticeResult.hasValue);
|
|
assert.ok(dempsterShaferResult.hasValue);
|
|
assert.ok(subjectiveLogicResult.hasValue);
|
|
|
|
// All should have epistemic analysis
|
|
assert.ok(bilatticeResult.epistemicAnalysis);
|
|
assert.ok(dempsterShaferResult.epistemicAnalysis);
|
|
assert.ok(subjectiveLogicResult.epistemicAnalysis);
|
|
|
|
// Different methods may produce different results
|
|
console.log('Bilattice result:', bilatticeResult.value);
|
|
console.log('Dempster-Shafer result:', dempsterShaferResult.value);
|
|
console.log('Subjective Logic result:', subjectiveLogicResult.value);
|
|
});
|
|
|
|
test('should demonstrate aggregation methods comparison', () => {
|
|
const collectedValues = [
|
|
{
|
|
value: 0.8,
|
|
possibility: 0.8,
|
|
path: ['evidence1'],
|
|
source: { type: 'direct' },
|
|
metadata: { reliability: 0.9 }
|
|
},
|
|
{
|
|
value: 0.6,
|
|
possibility: 0.6,
|
|
path: ['evidence2'],
|
|
source: { type: 'inferred' },
|
|
metadata: { reliability: 0.7 }
|
|
},
|
|
{
|
|
value: 0.4,
|
|
possibility: 0.4,
|
|
path: ['evidence3'],
|
|
source: { type: 'derived' },
|
|
metadata: { reliability: 0.8 }
|
|
}
|
|
];
|
|
|
|
const aggregationMethods = ['max', 'min', 'average', 'majority', 'median'];
|
|
|
|
const results = {};
|
|
for (const method of aggregationMethods) {
|
|
results[method] = UnifiedEvidenceFusion.fuse(collectedValues, {
|
|
mode: 'quantitative',
|
|
aggregationMethod: method,
|
|
useReconciliation: false
|
|
});
|
|
}
|
|
|
|
// All methods should produce valid results
|
|
for (const [method, result] of Object.entries(results)) {
|
|
assert.ok(result.hasValue, `Method ${method} should produce valid result`);
|
|
assert.strictEqual(result.aggregationMethod, method);
|
|
assert.ok(result.value >= 0 && result.value <= 1);
|
|
}
|
|
|
|
// Different methods should produce different results
|
|
assert.ok(results.max.value >= results.average.value);
|
|
assert.ok(results.average.value >= results.min.value);
|
|
});
|
|
|
|
test('should demonstrate method comparison functionality', () => {
|
|
const collectedValues = [
|
|
{
|
|
value: 0.7,
|
|
possibility: 0.7,
|
|
path: ['evidence1'],
|
|
source: { type: 'direct' },
|
|
metadata: { reliability: 0.9 }
|
|
},
|
|
{
|
|
value: 0.5,
|
|
possibility: 0.5,
|
|
path: ['evidence2'],
|
|
source: { type: 'inferred' },
|
|
metadata: { reliability: 0.7 }
|
|
}
|
|
];
|
|
|
|
const comparison = UnifiedEvidenceFusion.compareMethods(collectedValues, {
|
|
mode: 'quantitative',
|
|
aggregationMethods: ['max', 'average', 'majority'],
|
|
reconciliationMethods: ['none', 'dempster_shafer'],
|
|
epistemicModes: ['hybrid']
|
|
});
|
|
|
|
assert.ok(comparison.results);
|
|
assert.ok(comparison.summary);
|
|
assert.ok(comparison.summary.bestMethods.length > 0);
|
|
assert.ok(comparison.summary.worstMethods.length > 0);
|
|
assert.ok(comparison.summary.valueRange.min <= comparison.summary.valueRange.max);
|
|
});
|
|
|
|
test('should validate fusion options', () => {
|
|
const validation = UnifiedEvidenceFusion.validateOptions({
|
|
mode: 'quantitative',
|
|
aggregationMethod: 'max',
|
|
reconciliationMethod: 'bilattice',
|
|
epistemicMode: 'hybrid',
|
|
capacityType: 'simple_support'
|
|
});
|
|
|
|
assert.ok(validation.valid);
|
|
assert.strictEqual(validation.errors.length, 0);
|
|
|
|
// Test invalid options
|
|
const invalidValidation = UnifiedEvidenceFusion.validateOptions({
|
|
mode: 'invalid',
|
|
aggregationMethod: 'invalid',
|
|
reconciliationMethod: 'invalid'
|
|
});
|
|
|
|
assert.ok(!invalidValidation.valid);
|
|
assert.ok(invalidValidation.errors.length > 0);
|
|
});
|
|
|
|
test('should get available methods and descriptions', () => {
|
|
const availableMethods = UnifiedEvidenceFusion.getAvailableMethods();
|
|
assert.ok(availableMethods.aggregation);
|
|
assert.ok(availableMethods.reconciliation);
|
|
assert.ok(availableMethods.epistemicModes);
|
|
assert.ok(availableMethods.capacityTypes);
|
|
|
|
const descriptions = UnifiedEvidenceFusion.getMethodDescriptions();
|
|
assert.ok(descriptions.aggregation);
|
|
assert.ok(descriptions.reconciliation);
|
|
assert.ok(descriptions.epistemicModes);
|
|
assert.ok(descriptions.capacityTypes);
|
|
|
|
// Test specific descriptions
|
|
assert.ok(descriptions.aggregation.max);
|
|
assert.ok(descriptions.reconciliation.bilattice);
|
|
assert.ok(descriptions.epistemicModes.hybrid);
|
|
});
|
|
});
|