Facts and Entities
Kronroe's storage model is built on two primitives: **facts** and **entities**. Together they form a temporal property graph where every edge and property carries full bi-temporal metadata.
Kronroe’s storage model is built on two primitives: facts and entities. Together they form a temporal property graph where every edge and property carries full bi-temporal metadata.
What Is a Fact?
A Fact is a subject-predicate-object triple augmented with bi-temporal timestamps and optional metadata. It is the fundamental unit of storage in Kronroe.
pub struct Fact {
pub id: FactId,
pub subject: String,
pub predicate: String,
pub object: Value,
pub valid_from: KronroeTimestamp,
pub valid_to: Option<KronroeTimestamp>,
pub recorded_at: KronroeTimestamp,
pub expired_at: Option<KronroeTimestamp>,
pub confidence: f32,
pub source: Option<String>,
}
| Field | Description |
|---|---|
id |
Unique, lexicographically sortable identifier (kf_... format) |
subject |
The entity this fact is about (e.g. "alice") |
predicate |
The relationship or attribute name (e.g. "works_at", "has_role") |
object |
The value – a Value enum (see below) |
valid_from |
When this became true in the world |
valid_to |
When this stopped being true (None = still current) |
recorded_at |
When the database recorded this fact |
expired_at |
When this fact was superseded or invalidated (None = still active) |
confidence |
Confidence score in [0.0, 1.0], default 1.0 |
source |
Optional provenance marker |
Builder Methods
Facts support chaining via builder methods:
let fact = Fact::new("alice", "works_at", "Acme", KronroeTimestamp::now_utc())
.with_confidence(0.9)
.with_source("user:owner");
with_confidence(f32)– sets confidence, clamped to [0.0, 1.0]. Non-finite values are ignored.with_source(impl Into<String>)– sets the source provenance marker.
Validity Checks
is_currently_valid()– returnstruewhen bothvalid_toandexpired_atareNone.was_valid_at(at)– returnstruewhen the fact was valid at the given point in time on the valid-time axis.
The Value Enum
A fact’s object position holds a Value:
pub enum Value {
Text(String),
Number(f64),
Boolean(bool),
Entity(String),
}
| Variant | Use Case | Example |
|---|---|---|
Text(String) |
Free-form string values | "Senior Engineer", "London" |
Number(f64) |
Numeric values | 42.0, 3.14 |
Boolean(bool) |
Boolean flags | true, false |
Entity(String) |
Reference to another entity by canonical name | "acme-corp" |
Value implements From<&str>, From<String>, From<f64>, and From<bool>, so callers can pass these types directly where impl Into<Value> is accepted.
How Entity References Create Graph Edges
The Entity(String) variant is how Kronroe expresses graph edges. When a fact’s object is Value::Entity("acme-corp"), it creates a directed edge from the subject to the referenced entity.
alice --[works_at]--> acme-corp
This is represented as:
db.assert_fact("alice", "works_at", Value::Entity("acme-corp".into()), KronroeTimestamp::now_utc())?;
The predicate names the relationship. The subject and the Entity reference are both entity identifiers – there is no separate entity registration step. Entities come into existence implicitly when they appear as a subject or an Entity reference.
Multiple facts with different predicates form the full graph structure:
alice --[works_at]----> acme-corp
alice --[lives_in]----> london
alice --[reports_to]--> bob
bob --[works_at]----> acme-corp
FactId Format
Every fact gets a FactId on creation. The format is kf_ followed by 26 base32 characters:
kf_01jq5v8g7k3m2n4p6r8s0t1w2x
Properties:
- Lexicographically sortable – IDs sort in monotonic insertion order when compared as strings.
- Unique – generated with sufficient entropy to avoid collisions.
- Stable – once assigned, a fact’s ID never changes, even across corrections or invalidations.
- Parseable –
FactId::parse(s)validates the format. Invalid IDs produce aFactIdParseError.
FactIds are the primary handle for operations like fact_by_id, invalidate_fact, and correct_fact.
Thinking About the Graph Structure
Kronroe is a temporal property graph. The graph has these characteristics:
- Entities are nodes, identified by canonical name strings. They exist implicitly.
- Facts are typed, directed edges or properties on nodes. A fact with
Value::Entity(...)as its object is an edge; a fact withText,Number, orBooleanis a property. - Predicates name the relationship or attribute type (e.g.
works_at,job_title,has_ehcp). - Every edge and property is bi-temporal. The graph is not a single snapshot – it is a full history of what was true and when, queryable at any point in time.
To query the graph:
current_facts(subject, predicate)– get current edges/properties for a specific relationship.all_facts_about(subject)– get every fact ever recorded about an entity, across all predicates.facts_at(subject, predicate, at)– get edges/properties that were valid at a specific time.search(query, limit)– full-text search across all entities, predicates, and values.