Data model¶
Toise tracks entities (things that exist), the attributes that describe them, and the relations that connect them — plus the dimension OTel signals don't carry on their own: how that topology changes over time. Its model aligns with the OpenTelemetry entity data model so Toise entities slot into an existing OTel deployment rather than introducing a parallel vocabulary.
The authoritative contract is the Protobuf definition
proto/toise/v1/events.proto;
this page is the conceptual overview.
Entities¶
An entity has four parts:
- a
type— a string id such ashostorprocess; - an identity — a set of identifying key/value attributes whose values together uniquely identify the entity;
- a set of descriptive attributes — informational, non-identifying metadata;
- a
schema_url— versions the entity definition.
Attribute values are a typed Value: one of string, int64, double, or
bool — a deliberate subset of OTel's AnyValue that keeps the internal model
independent of OTLP wire types. Translation happens only at the
ingest boundary.
Two identities, not one¶
Toise carries two distinct identity concepts that must not be confused:
- Logical entity ID — the stable identifier of an entity across its whole
life, even as its identifying attributes evolve. It is a surrogate ULID
assigned by Toise on first sight, and it survives identity changes. This is
what consumers reference and what relation endpoints (
from/to) point at. - Identity hash — a deterministic fingerprint of the current identifying
attributes (SHA-256 truncated to 128 bits, type-prefixed, e.g.
host:1a2b...). It powers O(1) idempotent ingest and changes when an identifying attribute changes; the logical ID does not.
Put volatile facts in descriptive attributes
Identity should be stable for the entity's lifetime. If a host's identity includes its current leased IP, a DHCP renewal forks it into a brand-new entity. Keep changing facts (current address, usage, last-seen state) as descriptive attributes so a re-address is an update on the same entity, not a silent split.
Relations¶
A relation is a typed, directed edge between two entities, identified by
their logical entity IDs (from, to). It carries a structural flag
marking whether its appearance or disappearance is significant (alertable)
rather than merely descriptive.
Relations are attribute-free on the wire: anything that would describe how
two things relate becomes an entity instead (a port is a network.interface,
a route is a network.route). See Ingesting data.
Bi-temporality¶
Every event carries two timestamps, and they are not interchangeable:
event_time— when the fact became true in the real world, supplied by the producer.recorded_at— when Toise recorded the event, stamped at ingestion. Never taken from the producer.
For a late or retroactively corrected event, event_time is significantly
earlier than recorded_at; that gap is the signal, not noise. Queries default to
event_time space (the reality view); the audit view ("what did we know at
instant T?") is an opt-in via asKnownAt — see
GraphQL bi-temporality.
The change taxonomy¶
As events are classified, each change is tagged with one of:
ENTITY_CREATED · ENTITY_DELETED · ENTITY_IDENTITY_CHANGED ·
ENTITY_ATTRIBUTE_UPDATED · ENTITY_STATE_CHANGED · ENTITY_UNCHANGED ·
RELATION_ADDED · RELATION_REMOVED · RELATION_ATTRIBUTE_CHANGED.
This taxonomy — plus bi-temporality — is Toise's contribution on top of the OTel facts: the engine stores only what producers assert and classifies how it changed, never deriving or guessing state (ADR 0022).
For the full OTel mapping, see
docs/data-model/otel-mapping.md.