Events
PartialAn event is our semantic assertion that something happened, in our vocabulary: a discrete, point-in-time occurrence the action layer reacts to, owned through the same exclusive-arc as a sample. It is not a sample (a sample records a value; an event records an occurrence, see the has-a-value-now razor). Samples are what rules read; events are what event rules produce. The rules that produce events live on calculations; the alarms paired events drive, and the actions that respond, live on alarms and actions.
The event_type registry
Section titled “The event_type registry”A sample and an event are different shapes (a sample has a value; an event is an occurrence), so each gets a registry named for what it holds. The sample half is property_type; the event half is event_type. We do not force them into one universal registry, that would be the false unification the rest of the model avoids.
event_type describes every event key: (name, display_name, payload_schema, official, ...). The built registry carries the official boolean (shipped-canonical versus org-local), like the sample registry; the fuller scope ladder (template / org / official, where a template can define a template-local event) is future design. Declaring event types (call.started, cable.unplugged, command.issued) is first-class and valuable: it gives events a known schema, makes them inspectable, and is what a derivation rule targets when it turns a raw log line into a registered event. Raw log lines live in a separate ingest lane (the log_line table), untyped, until a rule derives a registered event from one; most never become events at all.
The naming convention is consistent: a _type registry defines what a thing is, named for the thing (property_type, event_type, like location_type, interface_type). Events get their own registry because an event is a different shape from a sample. The designed scope axis would work the same way as for samples: see key scope.
Events: caught, caused, derived, scheduled
Section titled “Events: caught, caused, derived, scheduled”An event arrives one of four ways; none is auto-manufactured from a state flip (a transition is already two consecutive sample rows, derivable by query).
- caught: a component publishes a structured occurrence natively (an xAPI Event channel, a webhook, an SNMP trap).
- caused: we issued a command, recorded as an event; this is what opens an intended sample.
- derived: an event rule fuses signals into an operator-meaningful fact (“codec in-call + traffic spike + room booked, so meeting started”), or turns a raw log line from the
log_linelane into a registered event, inferred without instrumenting the control system. - scheduled: the clock fired a schedule. A schedule fire is an event with
origin=scheduled, manufactured by the clock (a leader-elected singleton held via a NATS KV CAS lock, exactly one active, failing over on death); there is no separate schedule log table. Soaction_rulesubscribes to events uniformly (schedule to event to action: digests, synthetic checks, SLA resets are all schedule fires an action subscribes to).
Caught/caused/derived/scheduled is the event’s origin, a small vocabulary on the event table; it is not the same enum as sample provenance. The discipline that keeps an event-driven system from rotting is that events are declared (registered event keys) and rules are inspectable (the blast-radius preview in the UI).
Storage
Section titled “Storage”The event row is the semantic-occurrence log; event_type is its key registry. The physical layout (partitioning, the owner arc, lineage) lives on storage.
An event is born in a Postgres transaction, on the record lane. When an event_rule fires, the consumer writes the event row and its paired alarm transition to PG in one transaction (the alarm edge is serialized per (event_rule, owner)); the event is the durable record, the alarm is the stateful edge. The event is not published directly from the rule (no dual-write): a leader-elected CDC publisher (logical decoding of the WAL) fans the committed change out to JetStream, where the action_rule consumers react. Postgres is the system of record; JetStream carries the committed event onward. This is the opposite lane from samples, which live on NATS and sink to PG asynchronously (see samples).
The event row is the semantic-occurrence log. There is no alarm_id column today; the
event-carries-its-alarm edge is design direction. The lineage columns
(ADR-0066) name what
produced the event, while the flat correlation_id threads the causal chain. A schedule fire is an
event with origin=scheduled; there is no separate schedule table.
| Column | Type | Constraints | Notes |
|---|---|---|---|
id | bigint | PK | |
ts | timestamp with time zone | not null, default now() | |
owner_kind | text | not null | |
instance | text | not null, default ''::text | The sub-entity the occurrence is about, when the owner is not enough |
message | text | not null, default ''::text | |
attributes | jsonb | ||
provenance | text | not null, default 'observed'::text | |
source | text | not null, default ''::text | |
source_rule | text | ||
source_rule_version | bigint | ||
component_id | uuid | FK → component.id | |
system_id | uuid | FK → system.id | |
location_id | uuid | FK → location.id | |
node_id | uuid | FK → node.principal_id | |
event_type_id | uuid | FK → event_type.id, not null | The registered key this occurrence asserts |
origin | text | not null, default 'caught'::text | How the event arrived: caught, caused, derived, or scheduled |
source_event_id | bigint | FK → event.id | Lineage: the parent event, when one event caused another |
correlation_id | text | Threads the causal chain flat, across lineage hops | |
source_log_line_id | bigint | FK → log_line.id | Lineage: the raw log line a derivation rule promoted |
derived_by_rule_id | uuid | Lineage: the rule that did the deriving; a natively-caught event has none |
CHECK constraints and unique indexes on event
-
event_origin_check:CHECK ((origin = ANY (ARRAY['caught'::text, 'caused'::text, 'derived'::text, 'scheduled'::text]))) -
event_owner_arc_check:CHECK ((((owner_kind = 'component'::text) AND (component_id IS NOT NULL) AND (system_id IS NULL) AND (location_id IS NULL) AND (node_id IS NULL)) OR ((owner_kind = 'system'::text) AND (system_id IS NOT NULL) AND (component_id IS NULL) AND (location_id IS NULL) AND (node_id IS NULL)) OR ((owner_kind = 'location'::text) AND (location_id IS NOT NULL) AND (component_id IS NULL) AND (system_id IS NULL) AND (node_id IS NULL)) OR ((owner_kind = 'node'::text) AND (node_id IS NOT NULL) AND (component_id IS NULL) AND (system_id IS NULL) AND (location_id IS NULL)))) -
event_owner_kind_check:CHECK ((owner_kind = ANY (ARRAY['component'::text, 'system'::text, 'location'::text, 'node'::text]))) -
event_provenance_check:CHECK ((provenance = ANY (ARRAY['observed'::text, 'calculated'::text, 'intended'::text, 'declared'::text])))
event_type is the event-key registry: what a derivation rule targets when turning a raw log line
(in the separate log_line lane) into a registered event. The built governance column is the
official boolean (shipped-canonical versus org-local); the scope ladder (template / org /
official) is future design, shared with property_type.
| Column | Type | Constraints | Notes |
|---|---|---|---|
id | uuid | PK, default uuidv7() | |
name | text | not null | |
display_name | text | ||
description | text | not null, default ''::text | |
payload_schema | jsonb | The JSON Schema an occurrence of this key promises its attributes satisfy | |
official | boolean | not null, default false | Shipped-canonical versus org-local; seed-owned rows are read-only |
registered_at | timestamp with time zone | not null, default now() |
CHECK constraints and unique indexes on event_type
-
event_type_name_key:CREATE UNIQUE INDEX event_type_name_key ON public.event_type USING btree (name)
Related: calculations (the event_rule that produces events), alarms and actions (alarms and the response layer), samples (the data events read), and the glossary.