Event Validation
Causet validates event-related data at three layers: the compiler (before deployment), the runtime (at intent time), and preflight rules (application-defined business logic). Each layer catches different classes of errors.
Layer 1: Compiler validation
The causet-compiler validates your DSL files when you run compile. Compiler errors must be fixed before the IR artifact can be produced and deployed.
What the compiler validates:
- Event types exist: every
source_eventsreference in a projection or action’sside_effectsmust match a declared event - Field types: field types in event payloads must be valid DSL types (
string,int,boolean,float) - Derive expressions: expressions in projection
derive:blocks must reference valid event payload fields - Action schemas: input fields reference valid types, required flags are valid
- Relationships:
emit_eventsin relationship definitions reference declared events - Entity references:
entity_exprreferences must resolve to valid payload fields
A compile error looks like:
[ERROR] Projection 'user_following' references undefined event type 'ARTIST_FOLLOWED_TYPO'
at projections/follow.projections.causet:3Fix the error and recompile. There is no mechanism to deploy an IR with compiler errors.
Layer 2: Runtime input validation
When an intent is submitted, the runtime validates the payload against the action’s declared input schema before any rules run.
For an action defined as:
actions:
FOLLOW_ARTIST:
input:
user_id: { type: string, required: true }
artist_id: { type: string, required: true }A request with a missing artist_id is rejected before preflight rules run:
{
"error": "VALIDATION_ERROR",
"field": "artist_id",
"message": "Required field 'artist_id' is missing"
}Type mismatches (e.g., passing a number for a string field) are also caught at this layer.
Layer 3: Preflight rules
Preflight rules are application-defined business logic that run before core rules. They have access to the current entity state and can reject an intent with a domain-specific error code.
actions:
FOLLOW_ARTIST:
preflight:
rules:
- name: reject_self_follow
when: { expr: "intent.user_id == intent.artist_id" }
then:
- op: reject
code: CANNOT_FOLLOW_SELF
- name: reject_if_blocked
when: { expr: "state.blocked_users contains intent.artist_id" }
then:
- op: reject
code: USER_BLOCKEDPreflight rules execute deterministically inside the rules engine. They cannot make external I/O calls. If you need to validate against data in another entity’s state, use LOOKUP_FIELD:
preflight:
lookups:
- name: show_lookup
state: show
entity_expr: intent.show_id
rules:
- name: reject_if_show_not_found
when: { expr: "show_lookup == null" }
then:
- op: reject
code: SHOW_NOT_FOUNDWhat validation cannot catch
Compiler and runtime validation are structural — they check that types are correct and required fields are present. They cannot detect:
Semantically invalid but structurally valid payloads. If artist_id is a valid string but does not correspond to a real artist, runtime validation passes. Use preflight LOOKUP_FIELD to validate referential integrity.
Business rule violations that depend on external state. Preflight rules operate on entity state in the causet database. They cannot query external systems or your own application’s database.
Concurrent modification conflicts. If two intents for the same entity are submitted simultaneously, both may pass validation individually but produce conflicting state. Use optimistic locking or design your events to be commutative if this is a concern.
Cross-stream validation with LOOKUP_FIELD
Use LOOKUP_FIELD in preflight to validate that a referenced entity exists:
# Validate that a show exists before checking in a user
actions:
CHECK_IN:
state: user
input:
user_id: { type: string, required: true }
show_id: { type: string, required: true }
preflight:
lookups:
- name: show
state: show
entity_expr: intent.show_id
rules:
- name: show_must_exist
when: { expr: "show == null" }
then:
- op: reject
code: SHOW_NOT_FOUND
- name: show_must_be_announced
when: { expr: "show.status != 'announced'" }
then:
- op: reject
code: SHOW_NOT_ANNOUNCEDThis is the recommended pattern for referential integrity in Causet — not foreign keys in projection tables.
Reserved payload field names
The following field names are reserved and cannot be used in event payloads:
| Reserved name | Used for |
|---|---|
type | Event type discriminator |
ts | Event timestamp (microseconds) |
entity_id | Entity identifier |
event_id | Ledger event identifier |
fork_id | Fork context |
Attempting to use these names causes a compiler error.