Files
core/src/authorization/rules/DirectRule.js
T

135 lines
4.8 KiB
JavaScript
Raw Normal View History

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: directRel.validity !== undefined
? this._validity('identity', [relName], [this._relationValidity(directRel)], 1, relationStrength)
: this._defaultValidity(),
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 || 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);
}
}