feat: intermediate chain condition steps + _subjectIsObject unary scoping
- _expandChainSteps: a logical/defeasible evidence referenced by a chain step
is now a condition step ({ rule, conditionStep }) at ANY position. As the
FINAL step the engine verifies it at (intermediate, object); as an
INTERMEDIATE step the engine expands it from the current node (rule-based
reachability: base edges' destinations filtered by the rule's
defeaters/requirements) and continues traversal from each discovered node.
- buildPredicateRule / buildDirectRule: unary predicate calls whose subject
entity IS the evidence's object parameter (trusted(other) inside
peer_trusted(user, other)) are marked _subjectIsObject (was: only subject-var
calls got _subjectAsObject). Requires @arbiter/core@^1.0.4.
Tests: ChainConditionStep intermediate expansion; oracle campaign gains a
chain_intermediate_condition construct (oracle = min(peer*(1-trusted), read)).
This commit is contained in:
@@ -486,14 +486,21 @@ export class RuleGenerator {
|
||||
reverse: false
|
||||
};
|
||||
|
||||
// Subject-scoped (unary) call: the predicate call's args omit the
|
||||
// evidence's object parameter (user_risk(user, 1) inside a binary
|
||||
// evidence) → check the relation on the subject itself.
|
||||
// Unary predicate calls check the relation as a self-edge on the call's
|
||||
// subject entity (the graph stores unary facts as self-edges). The subject
|
||||
// entity may be the evidence's SUBJECT or its OBJECT parameter — mark the
|
||||
// matching rewrite flag.
|
||||
const evidenceParams = (evidence && evidence.params) || [];
|
||||
const objectVar = evidenceParams[1] && evidenceParams[1].name;
|
||||
const argName = a => a && (a.name !== undefined ? a.name : a.value);
|
||||
if (objectVar !== undefined && !(predicate.arguments || []).some(a => argName(a) === objectVar)) {
|
||||
rule._subjectAsObject = true;
|
||||
const args = predicate.arguments || [];
|
||||
if (objectVar !== undefined) {
|
||||
const hasObjectArg = args.some(a => argName(a) === objectVar);
|
||||
if (args.length === 1 && argName(args[0]) === objectVar) {
|
||||
rule._subjectIsObject = true;
|
||||
} else if (!hasObjectArg) {
|
||||
rule._subjectAsObject = true;
|
||||
}
|
||||
}
|
||||
|
||||
return rule;
|
||||
@@ -1082,7 +1089,6 @@ export class RuleGenerator {
|
||||
const out = [];
|
||||
for (let idx = 0; idx < steps.length; idx++) {
|
||||
const step = steps[idx];
|
||||
const isLast = idx === steps.length - 1;
|
||||
const stepName = typeof step === 'string' ? step : step.relation;
|
||||
if (stepName && this.evidenceNames.has(stepName)) {
|
||||
if (stack.has(stepName)) {
|
||||
@@ -1105,15 +1111,11 @@ export class RuleGenerator {
|
||||
out.push(...this._expandChainSteps(resolved.steps, refStack));
|
||||
continue;
|
||||
}
|
||||
if (isLast) {
|
||||
// Condition-gated final hop: inline the evidence's config as a
|
||||
// rule step the engine evaluates at (intermediate, object).
|
||||
out.push({ rule: this._deepCloneRule(resolved), conditionStep: true });
|
||||
continue;
|
||||
}
|
||||
this.errors.push(`Chain step '${stepName}' references an evidence with type '${resolved.type || 'logical'}'. ` +
|
||||
'Only the final chain step may reference a defeasible/logical evidence (a condition-gated hop); intermediate steps must be edge traversals.');
|
||||
out.push(step);
|
||||
// Condition step: inline the evidence's config as a rule step. As the
|
||||
// FINAL step the engine verifies it at (intermediate, object); as an
|
||||
// INTERMEDIATE step the engine EXPANDS it from the current node
|
||||
// (rule-based reachability) and continues from each discovered node.
|
||||
out.push({ rule: this._deepCloneRule(resolved), conditionStep: true });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1280,16 +1282,23 @@ export class RuleGenerator {
|
||||
reverse: false
|
||||
};
|
||||
|
||||
// Subject-scoped (unary) predicate call: the call's variable args omit the
|
||||
// evidence's object parameter (banned(user) inside can_open(user, doc)).
|
||||
// Mark _subjectAsObject so the engine checks the relation on the subject
|
||||
// itself — the unary fact's self-edge — instead of (subject, object).
|
||||
// Unary predicate calls check the relation as a self-edge on the call's
|
||||
// subject entity (the graph stores unary facts as self-edges). The subject
|
||||
// entity may be the evidence's SUBJECT or its OBJECT parameter:
|
||||
// banned(user) in can_open(user, doc) -> self-edge on the user
|
||||
// trusted(other) in peer_trusted(user, other) -> self-edge on the other
|
||||
// Mark _subjectAsObject (subject-as-object on the subject entity) or
|
||||
// _subjectIsObject (the subject entity IS the object parameter) so the
|
||||
// engine rewrites the pair accordingly.
|
||||
const evidenceParams = (evidence && evidence.params) || [];
|
||||
const objectVar = evidenceParams[1] && evidenceParams[1].name;
|
||||
const argName = a => a && (a.name !== undefined ? a.name : a.value);
|
||||
if (objectVar !== undefined) {
|
||||
const hasObjectArg = (expression.args || []).some(a =>
|
||||
a && a.type === 'Variable' && a.name === objectVar);
|
||||
if (!hasObjectArg) {
|
||||
const args = expression.args || [];
|
||||
const hasObjectArg = args.some(a => a && a.type === 'Variable' && a.name === objectVar);
|
||||
if (args.length === 1 && argName(args[0]) === objectVar) {
|
||||
rule._subjectIsObject = true;
|
||||
} else if (!hasObjectArg) {
|
||||
rule._subjectAsObject = true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user