js-rigor: remove the compiled evaluator — the runtime compiler is gone
The compiled evaluation path was never a performance win and was a
double-implementation liability: every semantics fix had to land twice
(CompiledEvaluator + LogicalOperators/handlers), and several bugs lived
only in one copy. A warm benchmark shows the compiled path at parity at
best (the apparent 7x chain regression was cold-cache confound).
Removed the runtime compiled dispatch entirely: RuleEvaluator evaluates
every rule through the single fallback path (logical operators + rule
handlers). The RuleCompiler remains as the config VALIDATOR only
(_compileErrors/_compileWarnings + _needsValues + the _compiled metadata
carried by snapshots). CompiledEvaluator.js deleted.
Fixes surfaced by removing the mask:
- The defeasible fallback wrap produced the wrong component shape
({rules} instead of {union:{rules}}/{intersection:{rules}}) — the
compiled path always ran for defeasible configs, so the fallback had
never executed; now wrapped correctly.
- Defeasible configs had no normal-dispatch routing (the compiled
evaluator handled them); routed to evaluateDefeasible.
- The binary defeasible path forced the binary mode's internal 0.5
threshold, while the compiled path always ran normal mode — the binary
decision is now the thresholded normal combination (preserving the
pinned contract).
- The fallback union/intersection/exclusion results now carry the
validity blocks (previously only the compiled versions did).
This commit is contained in:
@@ -8,7 +8,6 @@ import { RelationalComparatorRouter } from './rules/RelationalComparatorRouter.j
|
||||
import { ChainRule } from './rules/ChainRule.js';
|
||||
import { ChallengeRule } from './rules/ChallengeRule.js';
|
||||
import { ValueContext } from './ValueContext.js';
|
||||
import { CompiledEvaluator } from './CompiledEvaluator.js';
|
||||
|
||||
export class RuleEvaluator {
|
||||
constructor(arbiter) {
|
||||
@@ -26,7 +25,6 @@ export class RuleEvaluator {
|
||||
};
|
||||
|
||||
this.logicalOperators = new LogicalOperators(arbiter, this);
|
||||
this.compiledEvaluator = new CompiledEvaluator(arbiter, this, this.logicalOperators);
|
||||
this._valueRequirementCache = new Map();
|
||||
}
|
||||
|
||||
@@ -70,15 +68,6 @@ export class RuleEvaluator {
|
||||
return this._evaluateRuleBinary(numericUserId, userKey, numericObjectId, objectKey, rule, visited, currentRelation, enhancedOptions);
|
||||
}
|
||||
|
||||
if (rule && rule._compiled && options.useCompiled !== false && !(rule._compileErrors && rule._compileErrors.length)) {
|
||||
const result = this.compiledEvaluator.evaluate(rule._compiled, userId, userKey, objectId, objectKey, visited, currentRelation, enhancedOptions);
|
||||
if (finalValueContext && result.collectedValues && result.collectedValues.length > 0) {
|
||||
const ruleType = rule?.type || (rule?.union ? 'union' : rule?.intersection ? 'intersection' : rule?.exclusion ? 'exclusion' : 'rule');
|
||||
finalValueContext.addCollectedValues(result.collectedValues, ruleType, rule);
|
||||
}
|
||||
return this._maybeCacheRuleResult(result, currentRelation, ruleCacheKey, canCacheRuleResult);
|
||||
}
|
||||
|
||||
if (rule.union) {
|
||||
const result = this.logicalOperators.evaluateUnion(numericUserId, userKey, numericObjectId, objectKey, rule, visited, currentRelation, enhancedOptions);
|
||||
return this._maybeCacheRuleResult(result, currentRelation, ruleCacheKey, canCacheRuleResult);
|
||||
@@ -94,6 +83,17 @@ export class RuleEvaluator {
|
||||
return this._maybeCacheRuleResult(result, currentRelation, ruleCacheKey, canCacheRuleResult);
|
||||
}
|
||||
|
||||
// Defeasible configs ({ type: 'defeasible', when/unless/never/always })
|
||||
// have no rule-handler entry; route them to the defeasible evaluator
|
||||
// with the components normalized to the union/intersection format.
|
||||
if (this.logicalOperators._isDefeasibleLogic(rule)) {
|
||||
const result = this.logicalOperators.evaluateDefeasible(
|
||||
numericUserId, userKey, numericObjectId, objectKey,
|
||||
wrapDefeasibleComponents(rule), visited, currentRelation, enhancedOptions
|
||||
);
|
||||
return this._maybeCacheRuleResult(result, currentRelation, ruleCacheKey, canCacheRuleResult);
|
||||
}
|
||||
|
||||
// For other rule types, evaluate normally first
|
||||
// Shorthand operand objects ({ relation: 'owner' } inside logical rules,
|
||||
// or caller-supplied raw configs) carry no type: treat them as direct
|
||||
@@ -292,17 +292,17 @@ export class RuleEvaluator {
|
||||
// defeasible config). Route them through the defeasible evaluator,
|
||||
// which implements binary/threshold modes natively.
|
||||
if (this.logicalOperators._isDefeasibleLogic(rule)) {
|
||||
if (rule._compiled && !(rule._compileErrors && rule._compileErrors.length)) {
|
||||
return this.compiledEvaluator.evaluate(rule._compiled, userId, userKey, objectId, objectKey, visited, currentRelation, {
|
||||
...enhancedOptions,
|
||||
binary: true,
|
||||
fastPath: true
|
||||
});
|
||||
}
|
||||
return this.logicalOperators.evaluateDefeasible(userId, userKey, objectId, objectKey, wrapDefeasibleComponents(rule), visited, currentRelation, {
|
||||
// The compiled path ran defeasible rules in normal mode regardless of
|
||||
// the binary flag (compiled.mode defaulted to 'normal'), so the binary
|
||||
// decision was always the thresholded normal combination. Preserve
|
||||
// that contract: force normal mode; the checker applies the binary
|
||||
// thresholds to the result.
|
||||
const wrapped = wrapDefeasibleComponents(rule);
|
||||
wrapped.mode = 'normal';
|
||||
return this.logicalOperators.evaluateDefeasible(userId, userKey, objectId, objectKey, wrapped, visited, currentRelation, {
|
||||
...enhancedOptions,
|
||||
binary: true,
|
||||
fastPath: true
|
||||
binary: false,
|
||||
fastPath: false
|
||||
});
|
||||
}
|
||||
|
||||
@@ -433,18 +433,19 @@ export class RuleEvaluator {
|
||||
* Logical (union/intersection/exclusion) components pass through.
|
||||
*/
|
||||
function wrapDefeasibleComponents(rule) {
|
||||
const wrapSingle = (component) => ({ rules: [component] });
|
||||
// The defeasible evaluator's normalize expects the component format
|
||||
// when.intersection.rules[] / unless.union.rules[] (not bare {rules}).
|
||||
const wrapUnion = (component) => {
|
||||
if (!component) return component;
|
||||
if (Array.isArray(component)) return { rules: component };
|
||||
if (component.union || component.intersection || component.exclusion) return component;
|
||||
return wrapSingle(component);
|
||||
if (component.union) return component;
|
||||
if (Array.isArray(component)) return { union: { rules: component } };
|
||||
return { union: { rules: [component] } };
|
||||
};
|
||||
const wrapIntersection = (component) => {
|
||||
if (!component) return component;
|
||||
if (Array.isArray(component)) return { rules: component };
|
||||
if (component.union || component.intersection || component.exclusion) return component;
|
||||
return wrapSingle(component);
|
||||
if (component.intersection) return component;
|
||||
if (Array.isArray(component)) return { intersection: { rules: component } };
|
||||
return { intersection: { rules: [component] } };
|
||||
};
|
||||
return {
|
||||
...rule,
|
||||
|
||||
Reference in New Issue
Block a user