Skip to content

13.6 What a rule set may assume about its facts

A rule body reads what is already in the graph. It cannot fetch.

That one sentence decides whether a rule set is sound, and it is the difference between a rule that answers and a rule that answers understatedly. Three situations, and only the third needs work:

Your facts Complete? What to do
Stored, seeded, promoted, or a materialized view yes nothing — write the rules
A producer returning a whole record per anchor yes nothing — one fetch is the entire fact set for that anchor
Gathered by WALKING — each hop is another fetch not by default declare requires:

The second case is the common producer shape and it is why most rule sets need no thought here: one call to a company register returns an officer’s entire appointment history, so a rule about that officer’s appointments has everything.

The third case is a dependency tree, a citation graph, an ownership chain — anything where reaching depth 3 means three rounds of fetching. Without a declared demand, such a rule set concludes over whatever prefix the consumer’s query happened to materialize, which its author never sees.

Declare what you need:

name: npm/reaches-unmaintained
derives: ReachesUnmaintained
requires:
- materialize: "MATCH (r:Release)-[:HAS_DEPENDENCY*1..$depth]->(:Dependency) RETURN 1"
description: "The dependency tree has to exist before 'reaches' means anything."
params:
depth: { type: int, default: 3 }
rules:
- derive: "(d:ReachesUnmaintained)"
from: |
MATCH (d:Dependency)-[:HAS_HEALTH]->(h:ProjectHealth) WHERE h.overallScore < 5

A demand is ordinary read-only Cypher, validated like a rule body. It runs before any rule evaluates, in the same transaction, and after the consumer’s query has materialized its own anchors — so it DEEPENS what is already there rather than re-anchoring it. It may reference the rule set’s declared params, so depth is tunable per invocation with a default you choose.

When the facts are incomplete anyway, the engine says so. A rule set that evaluates while any fetch was capped, refused, truncated or left a demand unmet reports PARTIAL_RESULT and states that its membership — and any count or total over it — is a lower bound, naming the unmet demand where there is one. Report it to your reader as a lower bound; never present a derived count as a total. This is not a failure and not “nothing was derived”.

The rule of thumb: make the fact set complete, and prefer making it complete cheaply. Loading the data (a local table, a promoted watchlist) beats demanding a deep walk, and a demand beats hoping the consumer’s query walked far enough.