import { BaseRule } from './BaseRule.js'; import { Arbiter } from '../../core/Arbiter.js'; /** * DirectRule - Checks for direct relationships between user and object * * This rule evaluates direct relations in the graph, optionally in reverse direction. * It supports efficient batch processing and early exit optimizations. * * Returns standardized results with raw possibility values: * - possibility_allow: The strength/possibility of the relation (0.0 to 1.0) * - possibility_deny: Always 0 (DirectRule only reports relation strength, not polarity) * - Collected Values: Values from relations with full path metadata * * Note: This rule returns raw relation strength. The logical context (defeater, strict, * defeasible, etc.) determines how this strength is interpreted as positive or negative evidence. * * Configuration: * { * type: 'direct', * relation: string, // Optional: relation to check (uses currentRelation if not specified) * reverse: boolean, // Optional: check in reverse direction (default: false) * collectValues: boolean // Optional: collect values from relations (default: true if relation has values) * } */ export class DirectRule extends BaseRule { constructor(arbiter) { super(arbiter); } /** * Evaluate method returns raw relation strength */ evaluate(userId, userKey, objectId, objectKey, rule, visited, currentRelation, options = {}) { const { fastPath = false, minPossibility = null, collectValues: collectValuesOption, includeMeta = true } = options; const relName = rule.relation || rule.rel || rule.label || rule.name || currentRelation; const reverse = rule.reverse; const collectValues = collectValuesOption !== undefined ? collectValuesOption : rule.collectValues !== false; let directRel; if (reverse) { directRel = this.arbiter.relationManager.getDirectRelation(objectId, relName, userId, options); } else { directRel = this.arbiter.relationManager.getDirectRelation(userId, relName, objectId, options); } if (!directRel) { return this._createStandardResult({ possibility: 0, ...(includeMeta && { meta: { ruleType: 'direct', reason: 'no_relation' } }) }, []); } const relationStrength = directRel.possibility; const _source = directRel.source || 'persistent'; const _allowMeta = { ruleType: 'direct', reason: 'direct', source: _source, layer_name: directRel.layer_name || null, source_class: directRel.source_class || null, reducer_applied: directRel.reducer_applied || null }; const authResult = { possibility: relationStrength, reliability: directRel.reliability !== undefined ? directRel.reliability : 1.0, validity: this._validity('identity', [relName], [this._relationValidity(directRel)], 1, relationStrength), possibility_allow: relationStrength, // For binary mode possibility_deny: 0, // DirectRule doesn't deny ...(includeMeta && { meta: { ruleType: 'direct', reason: 'relation_exists', rule, relation: relName, reverse: reverse || false, strength: relationStrength, reliability: directRel.reliability !== undefined ? directRel.reliability : 1.0, source: _source, allow: _allowMeta }, meta_allow: _allowMeta }), reason: 'exists' }; // Collect values if relation has them and collection is enabled let collectedValues = []; if (collectValues && directRel.value !== undefined) { const sourceEntity = reverse ? objectKey : userKey; const targetEntity = reverse ? userKey : objectKey; const path = [sourceEntity, targetEntity]; collectedValues.push(this._createCollectedValue( directRel.value, directRel.possibility, path, { entityKey: sourceEntity, relation: relName, step: 0 }, { timestamp: directRel.changed_last_at || directRel.updated_last_at || (options && options.now !== undefined && options.now !== null ? options.now : Date.now()), reliability: 1.0, source: directRel.source || 'persistent' } )); } // Apply early exit logic if thresholds are enabled if (fastPath) { // Early exit based on relation strength threshold if (minPossibility !== null && authResult.possibility >= minPossibility) { if (authResult.meta) { authResult.meta.earlyExit = true; authResult.meta.earlyExitReason = 'strength_threshold_met'; } } } return this._createStandardResult(authResult, collectedValues); } }