I slept on it and I have something that works. What is needed to make some rule ordering happen (for the FORWARD mode) is a triple that indicates a rule has fired. e.g.
I now have a rule with this in the head: (?reportA ex:supercededBy ?reportB) (ex:SUPERCEDED_RULE ex:fired ex:true) Then another rule with this in the body: (ex:SUPERCEDED_RULE_FIRED ex:fired ex:true) noValue(?reportA ex:supercededBy ?reportB) That will ensure that is first rule runs before the second rule. I believe this was hinted at in the docs for the `hide(p)` primitive: ``` ... This is useful to enable non-monotonic forward rules to define flag predicates which are only used for inference control and do not "pollute" the inference results. ``` Using this approach you can also do things like find the `max()` by having a rule do pairwise comparisons and asserting a triple like (X lessThan Y) and then looking for the absence of (X lessThan) then you know it was the max. I am mostly doing this because I want explanations of inferred triples. One drawback to using noValue(), however, is that there isn't anything to print in the derivation trace when noValue() is satisfied. On Fri, Feb 6, 2026 at 4:23 AM Lorenz Buehmann < [email protected]> wrote: > Hi Justin, > > I'm not the developer of the rule engine, might I will try do answer > your question with my limited knowledge: > > > Using Generic rule reasoner (in FORWARD mode), say I have a rule with > this > > in the head: > > (?reportA ex:supercededBy ?reportB) > > > > Then another rule with this in the body: > > noValue(?reportA ex:supercededBy ?reportB) > > > > Shouldn't there be an implicit rule fire ordering since we can see that > the > > second rule depends on the first rule firing until completion. > > As far as I know, there is nothing for > > - guaranteed rule ordering > - stratification > - dependency analysis > - truth maintenance > > Under the hood, the forward GenericRuleReasoner is implemented using a > RETE network. What RETE does not imply rule ordering, stratification, or > safe negation. It just makes matching fast. > > Your specific example > > Rule A (producer): > > ... -> (?reportA ex:supercededBy ?reportB) > > > Rule B (consumer): > > noValue(?reportA ex:supercededBy ?reportB) ... -> ... > > > Your expectation is basically: > > “Rule B should wait until Rule A has fully fired.” > > That expectation assumes stratified negation (like in Datalog or SQL), > but Jena does not do that. > > > So, why do I think that Jena can’t safely do implicit ordering? > > Because the builtIn noValue is non-monotonic. > > At the moment Rule B fires: > > - The triple might not exist yet > - But it might be inferred later > - Jena has no way to know that in advance > > So from the engine’s perspective: > > "Absence of a triple right now is not stable information." > > That’s why: > > - noValue is evaluated against the current graph snapshot > - Results may become logically invalid later > - Jena will not retract them > > This is probably one reason why the documentation quietly treats noValue > as dangerous but useful. > > > And a related question... > > > > Say I have triples like: > > :thingA :hasValue 8 . > > :thingA :hasValue 4 . > > :thingA :hasValue 9 . > > ... > > > > Using FORWARD mode, is it possible to write a set of rules that do the > > equivalent of: > > ``` > > CONSTRUCT > > { > > ?thing :hasMaxValue ?max_value . > > } > > WHERE > > { SELECT ?thing (MAX(?value) AS ?max_value) > > WHERE > > { ?thing :hasValue ?value } > > GROUP BY ?thing > > } > > ``` > > > No, I don't think this is possible. The Generic Rule Reasoner cannot > express aggregation semantics like MAX in a sound way. > > Why aggregation doesn’t work in Jena forward rules, is basically because > those: > > - Fire on local pattern matches > - Have no global view > - Cannot “see all values” for a thing at once > - Cannot retract previously inferred triples > > > For example when we have > > :thingA :hasValue 8 . > :thingA :hasValue 4 . > :thingA :hasValue 9 . > > A forward rule might see: > > - 8 first -> infer hasMaxValue 8 > - then 9 later -> infer hasMaxValue 9 > > but cannot remove the 8 > > So we would end up with > > :thingA :hasMaxValue 8 . > :thingA :hasMaxValue 9 . > > > I know that other rule languages might have extensions for aggregates, > but we could maybe better think of Jena FORWARD rules as: > > “RDFS with custom Horn clauses — not SQL, not Datalog with negation, not > a rule engine with agenda control.” > > > > Hope this answers your questions to some extent, although that wouldn't > solve your problem. A combination of rules + SPARQL CONSTRUCT (INSERTs) > might solve your use-case > > > Cheers, > > Lorenz > > -- > Lorenz Bühmann > Research Associate/Scientific Developer > > [email protected] > > Institute for Applied Informatics e.V. (InfAI) | Goerdelerring 9 | 04109 > Leipzig | Germany >
