Pitfalls & Gotchas
1. Reserved payload field names
Never name a payload field type, ts, or entity_id. They are always shadowed by the event envelope. Rename to notification_type, created_at, record_id.
2. filter.where is a plain string
where: "it != x" — NOT where: { expr: "it != x" }. The { expr: ... } form is only valid under a rule’s when: clause. Using it under filter/remove/find causes ClassCastException: LinkedHashMap cannot be cast to String at compile time.
3. Column type inference
Omitting fields: on a projection infers every column as TEXT. eq: false queries break because Postgres won’t cast 'false' to boolean. Always declare fields: with explicit SQL types.
4. Aggregate queries need group_by + aggregate:
fields: - count: col is silently ignored. Use group_by: [scalar_key] + aggregate: { result: { count: col } } with limit: 1.
5. Deterministic IDs
Actions cannot call uuid() or random(). Build IDs deterministically:
value: "concat('checkin_', event.user_id, '_', event.show_id)"If multiple intents can reach the same key, upstream callers must pre-compute the same ID to avoid PK collisions.
6. Cross-stream writes in core
core forbids direct cross-stream set/add/sub. Use op: submit in side_effects to invoke the target entity’s own action, which then does the write in its own core.
Exception: relationship_create/relationship_remove ARE allowed in core because they maintain an edge table, not stream state.
7. Input fields with reserved names
The same reserved-name rule applies to action input: fields. Naming an input type makes it unreadable from rules because event.type is the event-type string. Use notification_type, record_type, etc.
8. Stale IR
Unexpected column types or null values after deploy usually mean the projection worker loaded an older IR. Recompile and redeploy the projection worker and query service whenever fields:, derive:, or event payloads change.
9. default: [] on arrays
Omitting default: gives null, not []. contains(null, x) throws; size(null) errors in some paths. Always set default: [] for array fields and default: {} for object fields.
10. unique: true on relationships
unique: true deduplicates edges at the engine level, but the caller sees a silent no-op rather than an error. Pair it with an explicit preflight rule that emits a domain-specific rejection code so callers get meaningful feedback.
Next steps
- Compiler Errors — full
VERR_*error code reference - Operations — per-op reference
- Intents — conceptual guide