import { DeonticFormula } from '../types.js';
import { Loggers } from '../utils/logger.js';
const logger = Loggers.validators.deontic;
interface DeonticWorld {
id: string;
obligations: Set<string>;
permissions: Set<string>;
facts: Set<string>;
}
export class DeonticValidator {
/**
* Validate deontic formula using possible worlds semantics
*/
validate(formula: DeonticFormula, worlds: DeonticWorld[]): boolean {
// Check if formula is valid in all deontically ideal worlds
const idealWorlds = this.getIdealWorlds(worlds);
if (idealWorlds.length === 0) {
// If no ideal worlds, check all worlds
return worlds.every(world => this.evaluateInWorld(formula, world));
}
return idealWorlds.every(world => this.evaluateInWorld(formula, world));
}
/**
* Evaluate formula in a specific world
*/
private evaluateInWorld(formula: DeonticFormula, world: DeonticWorld): boolean {
switch (formula.type) {
case 'atom':
return world.facts.has(formula.name);
case 'negation':
return !this.evaluateInWorld(formula.operand, world);
case 'and':
return this.evaluateInWorld(formula.left, world) &&
this.evaluateInWorld(formula.right, world);
case 'or':
return this.evaluateInWorld(formula.left, world) ||
this.evaluateInWorld(formula.right, world);
case 'implication':
return !this.evaluateInWorld(formula.left, world) ||
this.evaluateInWorld(formula.right, world);
case 'deontic':
return this.evaluateDeontic(formula, world);
default:
logger.error('Unknown formula type in deontic evaluation');
return false;
}
}
/**
* Evaluate deontic operators
*/
private evaluateDeontic(
formula: { type: 'deontic'; operator: string; operand: DeonticFormula },
world: DeonticWorld
): boolean {
const operandName = this.getAtomName(formula.operand);
switch (formula.operator) {
case 'O': // Obligatory
if (operandName) {
return world.obligations.has(operandName);
}
// For complex formulas, check if they hold in all ideal worlds
return true; // Simplified
case 'P': // Permitted
if (operandName) {
return world.permissions.has(operandName) || world.obligations.has(operandName);
}
return true; // Simplified
case 'F': // Forbidden
if (operandName) {
return !world.permissions.has(operandName) && !world.obligations.has(operandName);
}
return false; // Simplified
case 'IM': // Impermissible
// Same as forbidden
if (operandName) {
return !world.permissions.has(operandName) && !world.obligations.has(operandName);
}
return false;
case 'OB': // Obligatory given condition
case 'PM': // Permitted given condition
// Handle conditional deontic logic
return this.evaluateConditionalDeontic(formula, world);
default:
logger.error(`Unknown deontic operator: ${formula.operator}`);
return false;
}
}
/**
* Evaluate conditional deontic operators
*/
private evaluateConditionalDeontic(
formula: { type: 'deontic'; operator: string; operand: DeonticFormula },
world: DeonticWorld
): boolean {
// For OB(p|q), check if q implies O(p)
// For PM(p|q), check if q implies P(p)
if (formula.operand.type === 'implication') {
const condition = formula.operand.right;
const consequence = formula.operand.left;
// If condition holds, check the deontic consequence
if (this.evaluateInWorld(condition, world)) {
const consequenceName = this.getAtomName(consequence);
if (consequenceName) {
if (formula.operator === 'OB') {
return world.obligations.has(consequenceName);
} else if (formula.operator === 'PM') {
return world.permissions.has(consequenceName) ||
world.obligations.has(consequenceName);
}
}
}
// If condition doesn't hold, the conditional is vacuously true
return true;
}
return false;
}
/**
* Get atom name from formula if it's a simple atom
*/
private getAtomName(formula: DeonticFormula): string | null {
if (formula.type === 'atom') {
return formula.name;
}
return null;
}
/**
* Get deontically ideal worlds (where all obligations are satisfied)
*/
private getIdealWorlds(worlds: DeonticWorld[]): DeonticWorld[] {
return worlds.filter(world => {
// A world is ideal if all obligations are facts
return Array.from(world.obligations).every(obligation =>
world.facts.has(obligation)
);
});
}
/**
* Generate example deontic worlds
*/
generateExampleWorlds(): DeonticWorld[] {
return [
{
id: 'w1',
obligations: new Set(['pay_taxes', 'follow_laws']),
permissions: new Set(['pay_taxes', 'follow_laws', 'travel', 'work']),
facts: new Set(['pay_taxes', 'follow_laws', 'work'])
},
{
id: 'w2',
obligations: new Set(['pay_taxes', 'follow_laws']),
permissions: new Set(['pay_taxes', 'follow_laws', 'travel', 'work']),
facts: new Set(['travel', 'work']) // Violates obligations
},
{
id: 'w3',
obligations: new Set(['pay_taxes', 'follow_laws']),
permissions: new Set(['pay_taxes', 'follow_laws', 'travel', 'work']),
facts: new Set(['pay_taxes', 'follow_laws', 'travel', 'work'])
}
];
}
/**
* Check common deontic principles
*/
checkDeonticPrinciples(formula: DeonticFormula): {
principle: string;
holds: boolean;
explanation: string;
}[] {
const results = [];
// Generate test worlds
const worlds = this.generateExampleWorlds();
// D Axiom: O(p) → P(p) (What is obligatory is permitted)
if (formula.type === 'deontic' && formula.operator === 'O') {
const permittedFormula: DeonticFormula = {
type: 'deontic',
operator: 'P' as any,
operand: formula.operand
};
const dAxiomHolds = worlds.every(world =>
!this.evaluateInWorld(formula, world) ||
this.evaluateInWorld(permittedFormula, world)
);
results.push({
principle: 'D Axiom',
holds: dAxiomHolds,
explanation: 'O(p) → P(p): What is obligatory is permitted'
});
}
// Consistency: ¬(O(p) ∧ O(¬p))
results.push({
principle: 'Deontic Consistency',
holds: true, // Simplified
explanation: '¬(O(p) ∧ O(¬p)): Cannot have contradictory obligations'
});
return results;
}
/**
* Generate standard deontic scenarios
*/
generateScenarios(): Array<{
name: string;
obligations: string[];
permissions: string[];
forbiddens: string[];
}> {
return [
{
name: 'Traffic Laws',
obligations: ['stop_at_red', 'wear_seatbelt', 'signal_turns'],
permissions: ['drive', 'park', 'change_lanes'],
forbiddens: ['speed', 'run_red_light', 'drive_drunk']
},
{
name: 'Academic Ethics',
obligations: ['cite_sources', 'submit_original_work'],
permissions: ['research', 'collaborate', 'ask_questions'],
forbiddens: ['plagiarize', 'cheat', 'falsify_data']
},
{
name: 'Professional Conduct',
obligations: ['maintain_confidentiality', 'act_professionally'],
permissions: ['take_breaks', 'express_opinions', 'change_jobs'],
forbiddens: ['discriminate', 'harass', 'steal_property']
}
];
}
}