Reasoning
OWLGraph performs reasoning at write time, not query time. Every mutation is intercepted by the materialization engine, which applies inference rules and appends additional edges before the data is committed.
The Mutation Pipeline
Section titled “The Mutation Pipeline”Client mutation ↓Parse JSON/RDF → Directed edges ↓OWLGraph Materializer ├── 0. Delete cascades ├── 1. Type hierarchy (subClassOf) ├── 2. Domain inference ├── 3. Range inference ├── 4. Property hierarchy (subPropertyOf) ├── 5. Inverse properties ├── 6. Symmetric properties ├── 7. Property chains ├── 8. Transitive closure └── 9. Disjointness validation ↓Validation, indexing, commit to storageThe property-hierarchy rule runs before inverse, symmetric, and chain rules so that materialized parent-property edges can themselves participate in those downstream rules. Transitive closure runs last so it sees the full enriched edge set.
Rules in Detail
Section titled “Rules in Detail”Rule 0: Delete Cascades
Section titled “Rule 0: Delete Cascades”When a type is removed from a node, all ancestor types are also removed:
Remove: <0x1> dgraph.type "GoldenRetriever"Effect: <0x1> dgraph.type "Dog" (removed) <0x1> dgraph.type "Mammal" (removed) <0x1> dgraph.type "Animal" (removed)Rule 1: Type Hierarchy
Section titled “Rule 1: Type Hierarchy”When dgraph.type is set, all superclass types are added:
Input: <0x1> dgraph.type "GoldenRetriever"Inferred: <0x1> dgraph.type "Dog" (owl.inferred=true) <0x1> dgraph.type "Mammal" (owl.inferred=true) <0x1> dgraph.type "Animal" (owl.inferred=true)Rule 2: Domain Inference
Section titled “Rule 2: Domain Inference”Using a property with rdfs:domain infers the domain type on the subject:
Ontology: hasOwner rdfs:domain AnimalInput: <0x1> hasOwner <0x2>Inferred: <0x1> dgraph.type "Animal" (owl.inferred=true)Rule 3: Range Inference
Section titled “Rule 3: Range Inference”Using a property with rdfs:range infers the range type on the object:
Ontology: hasOwner rdfs:range PersonInput: <0x1> hasOwner <0x2>Inferred: <0x2> dgraph.type "Person" (owl.inferred=true)Rule 4: Property Hierarchy
Section titled “Rule 4: Property Hierarchy”When an edge is written on a property declared as rdfs:subPropertyOf of one or more parent properties, an edge is materialized for every transitive ancestor property:
Ontology: quotes rdfs:subPropertyOf intertextuallyRelatedTo intertextuallyRelatedTo rdfs:subPropertyOf semanticLinkInput: <0x1> quotes <0x2>Inferred: <0x1> intertextuallyRelatedTo <0x2> (owl.inferred=true) <0x1> semanticLink <0x2> (owl.inferred=true)The materialized parent edges are then themselves fed into the inverse, symmetric, chain, and transitive rules, so a single sub-property write can produce a substantial fan-out when the parent properties are richly characterized.
Rule 5: Inverse Properties
Section titled “Rule 5: Inverse Properties”Ontology: isOwnerOf owl:inverseOf hasOwnerInput: <0x1> hasOwner <0x2>Inferred: <0x2> isOwnerOf <0x1> (owl.inferred=true)Rule 6: Symmetric Properties
Section titled “Rule 6: Symmetric Properties”Ontology: friendOf a owl:SymmetricPropertyInput: <0x1> friendOf <0x2>Inferred: <0x2> friendOf <0x1> (owl.inferred=true)Rule 7: Property Chains
Section titled “Rule 7: Property Chains”Ontology: hasGrandparent = hasParent o hasParentInput: <0x3> hasParent <0x2> <0x2> hasParent <0x1>Inferred: <0x3> hasGrandparent <0x1> (owl.inferred=true)Rule 8: Transitive Closure
Section titled “Rule 8: Transitive Closure”For any property declared as owl:TransitiveProperty, the engine computes the
closure of all reachable pairs and materializes any missing direct edges:
Ontology: precedesEvent a owl:TransitivePropertyInput: <Flood> precedesEvent <Fall> <Fall> precedesEvent <Creation>Inferred: <Flood> precedesEvent <Creation> (owl.inferred=true)Closure spans transactions: when the new edge connects to a chain that is
already persisted, the engine reads the existing graph (using the predicate’s
forward and @reverse indexes) and emits the additional links the new edge
implies. As a result, transitive writes cost slightly more than non-transitive
ones — the engine performs forward and backward BFS from each new edge, bounded
by the circuit breaker.
If you bulk-load a large historical graph and then declare a property transitive after the fact, run the retroactive reasoner to catch up; new writes alone will not back-fill arbitrary historical chains.
Rule 9: Disjointness Validation
Section titled “Rule 9: Disjointness Validation”After all inference, the engine checks for disjoint type violations. The check considers types from the current mutation, types inferred by upstream rules, and types already persisted on the entity, expanded with the full ancestor closure of every type. This means a violation is caught even when the conflicting types were written in separate transactions, and even when disjointness is declared on a superclass of the asserted type.
Ontology: Dog owl:disjointWith CatMutation A (committed): <0x1> dgraph.type "Dog"Mutation B (rejected): <0x1> dgraph.type "Cat"Error: "disjointness violation: type Dog is disjoint with type Cat"Inferred Edge Facet
Section titled “Inferred Edge Facet”All materialized edges carry owl.inferred=true as a facet. Query it to distinguish asserted from inferred facts:
{ q(func: type(Dog)) { name dgraph.type @facets(owl.inferred) }}Circuit Breaker
Section titled “Circuit Breaker”If a single mutation generates more than 10,000 inferred edges, materialization is aborted and the mutation is rejected. This prevents runaway inference in pathological ontologies.
Retroactive Materialization
Section titled “Retroactive Materialization”When an ontology is loaded into a cluster with existing data, a background process scans existing nodes and adds missing ancestor type edges. This runs asynchronously — the ontology load response returns immediately.