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-unmaintainedderives: ReachesUnmaintainedrequires: - 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 < 5A 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.

