feat: evidence composition — compile-time reference resolution for evidence sub-rules
An evidence may now reference another derived evidence as a sub-rule (WHEN can_read(user, doc) where can_read is itself an evidence). Resolution is a compile-time linker pass: after every evidence config is generated, each direct reference to an evidence is inlined with that evidence's own (resolved) config, so the engine evaluates a fully-resolved, acyclic config tree. - resolveEvidenceReferences(): post-generation pass over evidence configs, recursing into logical/defeasible containers (when/unless/never/always/ requires/union/intersection), always.direct nests, and comparator operands. - Forward references resolve (all configs exist before the pass runs). - Cycles and self-references are compile-time errors. - _subjectAsObject scoping is preserved through inlining. - dependsOn is recomputed after resolution, so partial-graph requirements reach transitively through composed evidence. - buildDirectRule/buildPredicateRule now apply subject-scoping to top-level PredicateCall evidence bodies (latent gap, previously missed). - validation: reject relation names shared across facts/sources/evidence/ measures (a collision silently overwrote configs and read as a false cycle). Tests: EvidenceComposition (9), DSLRuntime transitive requiredFacts, oracle campaign composition construct, illegal-mutations cycle + cross-kind cases.
This commit is contained in:
@@ -81,6 +81,21 @@ const MUTATIONS = {
|
||||
desc: 'evidence declared with mismatched parameter arity',
|
||||
mustFail: true,
|
||||
apply: () => VALID_DSL.replace('evidence can_read(user: Employee, doc: Doc) { owns(user, doc) }', 'evidence can_read(user: Employee) { owns(user, doc) }')
|
||||
},
|
||||
cyclic_evidence_ref: {
|
||||
desc: 'two evidences referencing each other (cycle)',
|
||||
mustFail: true,
|
||||
apply: () => VALID_DSL + `
|
||||
evidence can_cyc_a(user: Employee, doc: Doc) { can_cyc_b(user, doc) }
|
||||
evidence can_cyc_b(user: Employee, doc: Doc) { can_cyc_a(user, doc) }`
|
||||
},
|
||||
cross_kind_collision: {
|
||||
desc: 'fact and evidence sharing a relation name',
|
||||
mustFail: true,
|
||||
apply: () => VALID_DSL.replace(
|
||||
'evidence can_read(user: Employee, doc: Doc) { owns(user, doc) }',
|
||||
'fact can_read(user: Employee, doc: Doc)\n evidence can_read(user: Employee, doc: Doc) { owns(user, doc) }'
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user