There is exactly one kind of data in Onto: a fact. A fact is an EAV (Entity-Attribute-Value) triple (a tuple that contains exactly 3 elements)- the same model as Datomic, DataScript, and RDF. Everything the system knows is expressed this way, in five categories:
# entity fact (domain data)
[:reservation/1 :reservation/date "2024-03-15"]
# event fact (history)
[:event/42 :event/type :reservation/requested]
# derived fact (rule output)
[:reservation/1 :reservation/status :confirmed]
# clock fact (time)
[:system/clock :clock/now 1706001000]
# intent fact (side effect signal)
[:reservation/1 :intent/send-email true]
<aside> 💡
The one-line version: everything is a fact, all facts have a cause, all state is a conclusion and Datalog is what makes those three properties guarantees instead of conventions.
</aside>
We never store state imperatively. Rules are Datalog implications - when these facts are true, these other facts follow - and the runtime keeps a live fact base, refiring rules whenever facts change. Derived facts cascade to a fixpoint automatically: no event handlers, no pub/sub, no wiring. A reservation's status isn't a column you update; it's a conclusion the rule base reaches from the facts. The state machine is emergent from the rules, not encoded anywhere.
Two immutability principles govern writes:
Every write goes through one loop:
guard → apply → derive → check → commit-or-reject
Preconditions guard; the batch applies to a candidate snapshot; derived facts and formulas recompute to a fixpoint; postconditions and invariants check the result. A rejection leaves the fact base exactly as it was - nothing bad becomes real.
The clock is a fact (:clock/now); a tick advances it, recomputes, and re-derives - so deadlines fire without anyone writing. Side effects stay out of the rules entirely: a rule asserts an :intent/* fact, and a platform executor performs the effect exactly once (keyed by a content-addressed intent identity), then closes it. Rules stay pure - same facts in, same facts out.
Because the guarantees are structural, not aspirational. Stratified Datalog gives us termination (rule sets always halt), determinism (same facts → same derived facts), and monotonicity (order-independent firing) by construction - entire bug classes eliminated without proving anything. And we deliberately restrict the fragment (linear arithmetic only, discrete integer timeline) so the same rules that run are also decidably checkable (QF-LIA + EUF) and readable as plain English. The design work is the restriction; the logic is imported.