Event System
This document details the event system that powers connections between cards and collections in Commonplace, including the inlet/outlet architecture and event processing model.
Core Architecture
Automatic Inlet/Outlet Model
Every card and collection in Commonplace automatically receives a GenericInlet and GenericOutlet upon creation. This provides a foundation for links that requires no schema definition or configuration:
- GenericInlet: Accepts events from any connected outlet, regardless of event type or source schema
- GenericOutlet: Emits events generated by the card (user actions, data changes, lifecycle events)
- Universal Compatibility: Any card can connect to any other card through these generic endpoints
- No User Configuration Required: Links work immediately without schema setup
Named Inlets (Advanced)
Beyond the automatic generic endpoints, Commonscript attached to card schemas can define additional named inlets:
- Schema-Defined: Named inlets are specified in the card's schema via Commonscript
- Typed Constraints: Can specify accepted event types and source schema requirements
- Selective Processing: Enable targeted event handling for specific workflows
- Coexistence: Named inlets work alongside the GenericInlet (unless explicitly overridden)
Example named inlets:
data-source
: Accepts onlycard_changed
events from cards with numeric fieldsapproval-workflow
: Accepts customapproval_request
events from specific card typesnotification-feed
: Accepts any events but filters by priority level
Event Propagation Model
The system implements a graph-based event propagation model that automatically routes events through connected cards. Events are processed server-side in the "master" view of each collection, with connections being collection-local between card references (not card instances directly):
Fan-Out Behavior
When a card's outlet connects to multiple inlets, events are automatically copied and delivered to all connected destinations:
Card A (outlet) → Card B (inlet)
↘ Card C (inlet)
↘ Card D (inlet)
A single event from Card A generates three separate deliveries.
Convergence and Duplicate Prevention
When multiple event paths converge on the same inlet, only the first event to arrive is processed:
Card A → Card C → Card E (inlet)
Card B → Card D ↗
If events from both paths A→C→E and B→D→E arrive at Card E's inlet, only the first event is retained. Subsequent duplicate events are silently dropped.
Event Consumption and Propagation
Cards with Commonscript can intercept and process events, affecting propagation:
- Pass-Through (Default): Cards without event handlers relay events unchanged to their outlets
- Event Consumption: Commonscript can consume an event, stopping its propagation
- Re-Emission: After consuming an event, Commonscript can either:
- Propagate the original event onwards, OR
- Emit a new custom event
- (Only one of these actions is allowed per consumed event)
Event Processing Architecture
Events flow through the system's layered architecture:
- Server-Side Processing: All event propagation occurs server-side in the collection's master state
- Commonscript Execution: Event handlers execute in the server-side Commonscript VM
- Collection-Local Scope: Links and event propagation are contained within individual collections
- Card Reference Links: link card references within a collection, not card instances directly
Link Model
User Linking Experience
Users create links through a simplified interface that abstracts event complexity:
- Visual Connection: Drag from any card's outlet to any other card's inlet
- No Event Type Matching Required: Users don't specify which events should flow
- Optional Schema Hints: UI may suggest compatible connections but doesn't enforce them
- Immediate Functionality: Links work immediately upon creation
System-Level Linking Rules
The system enforces several link constraints automatically:
Circular Reference Prevention:
- Links cannot create loops where a card eventually connects back to itself
- The system detects circular references through graph analysis
- Circular link attempts are rejected with user-friendly error messages
Collection Boundary Rules:
- Links within the same collection are always allowed (between card references in that collection)
- Links from a card reference to its parent collection's child collections are allowed
- Links from a child collection to card references in the parent collection are allowed
- Cross-collection Links between unrelated collections are not permitted
- Note: This aligns with the collection-local scope principle established in the foundational architecture
Type Constraints (Optional):
- Commonscript can define type requirements for named inlets
- Generic inlets accept links from any source schema by default
- Schema violations don't prevent links but may affect event processing
Event Types and Structure
Built-In Event Types
The system generates several standard event types automatically:
card_changed
: Field data modified in a card (user edit, commonscript update, federation sync)card_created
: New card added to a collectioncard_deleted
: Card removed from the systemcard_moved
: Card repositioned within or between collectionscollection_updated
: Cards added/removed from collection, metadata changedlink_added
: New inlet/outlet connection establishedlink_removed
: link deleteduser_action
: Custom user-triggered events (button clicks, form submissions)schema_modified
: Card or collection schema definition changed
Custom Event Types
Commonscript can define and emit custom event types following namespace conventions:
- Namespaced Names:
app.workflow.approval_request
,user.notification.high_priority
- Structured Payloads: Custom data relevant to the specific event type
- System Integration: Custom events propagate through the same graph model as built-in events
Event Structure
All events share a standardized structure. The following Rust structs illustrate the conceptual model (actual implementation uses optimized binary serialization):
use uuid::Uuid;
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
use std::time::SystemTime;
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Event {
pub event_id: Uuid,
pub event_type: EventType,
pub timestamp: DateTime<Utc>,
pub source: EventSource,
pub target: EventTarget,
pub payload: EventPayload,
pub propagation: PropagationMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventSource {
pub card_id: Uuid,
pub collection_id: Uuid,
pub outlet_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventTarget {
pub card_id: Uuid,
pub inlet_name: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropagationMetadata {
pub hop_count: u32,
pub path_id: Uuid,
pub original_source: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventType {
CardChanged,
CardCreated,
CardDeleted,
CardMoved,
CollectionUpdated,
LinkAdded,
LinkRemoved,
UserAction,
SchemaModified,
Custom(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EventPayload {
CardChanged {
field_name: String,
old_value: Option<FieldValue>,
new_value: FieldValue,
},
CardCreated {
card_data: CardData,
},
UserAction {
action_type: String,
action_data: Vec<u8>, // Binary data for custom user actions
},
Custom {
event_name: String,
data: Vec<u8>, // Binary data for extensibility
},
// Other payload variants...
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FieldValue {
Text(String),
Number(f64),
Boolean(bool),
Date(SystemTime),
Reference(CardId),
List(Vec<FieldValue>),
Object(HashMap<String, FieldValue>),
Binary(Vec<u8>), // For custom field types
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CardData {
pub id: CardId,
pub schema_id: SchemaId,
pub version: u64,
pub fields: HashMap<String, FieldValue>,
pub metadata: CardMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CardMetadata {
pub created: SystemTime,
pub modified: SystemTime,
pub tags: Vec<String>,
}
pub type CardId = Uuid;
pub type SchemaId = Uuid;
Event Processing Pipeline
Event Generation
Events are generated by several system components following a consistent pattern:
- User Action: User updates card data, drags card in UI, or triggers other interactions
- State Change: Card metadata, data, or schema is modified in the server's master view
- Event Emission: System generates appropriate events for the state change
- Server-Side Propagation: Events propagate through connection graph on server
Event Sources:
- User Interface: Direct user interactions with card references
- Commonscript Execution: Script-driven data changes and custom events
- System Operations: Card lifecycle, card type changes, link management
- Federation Sync: Remote changes synchronized from other instances
Event Routing
The routing engine processes events through several stages:
- Source Validation: Verify the event source exists and has permission to emit
- Link Discovery: Find all outbound links from the source outlet
- Fan-Out Generation: Create separate event instances for each connected inlet
- Path Tracking: Add propagation metadata to prevent loops and duplicates
- Delivery: Queue events for processing at target inlets
Event Delivery
At each target inlet, the system:
- Duplicate Check: Compare with recent events to prevent duplicate processing
- Schema Validation: Verify compatibility if inlet has schema constraints
- Handler Invocation: Execute Commonscript event handlers if present
- Propagation Decision: Determine if event continues to card's outlets
Error Handling
The event system handles failures gracefully:
- Invalid Events: Malformed events are logged and dropped
- Missing Targets: Events to deleted cards are silently discarded
- Handler Errors: Commonscript exceptions don't break event propagation
- Schema Violations: Events that don't match inlet constraints are ignored
- Link Failures: Temporary network issues trigger retry logic
Card Type Integration
Inlet Schema Constraints
Named inlets can specify requirements for connected sources:
The following examples show conceptual inlet definitions (actual syntax will be defined in the Commonscript specification):
No Constraints (Default):
// Conceptual representation - GenericInlet accepts any connection
struct InletConstraints {
name: String,
schema_requirements: None,
field_requirements: Vec::new(),
accepted_schemas: Vec::new(),
}
Field Requirements:
// Conceptual representation - specific field type requirements
struct InletConstraints {
name: "numeric-data".to_string(),
field_requirements: vec![
FieldRequirement { name: "value".to_string(), field_type: FieldType::Number },
FieldRequirement { name: "timestamp".to_string(), field_type: FieldType::DateTime },
],
// ...
}
Schema Matching:
// Conceptual representation - accept specific schema types
struct InletConstraints {
name: "sensor-reading".to_string(),
accepted_schemas: vec![
"TemperatureSensor".to_string(),
"PressureSensor".to_string(),
],
// ...
}
Duck Typing:
// Conceptual representation - require specific methods/interfaces
struct InletConstraints {
name: "measurable".to_string(),
required_methods: vec![
"getValue".to_string(),
"getUnit".to_string(),
],
// ...
}
Card Type Validation
Card type validation occurs at connection time, as this may be an expensive operation:
- Link-Time: Detailed validation when user creates links
- Event-Time: Event-specific validations if necessary (fast, lightweight)
- Graceful Degradation: Schema violations result in ignored events, not system errors
Performance Considerations
Event Routing Optimization
- Graph Caching: Link topology cached for fast event routing
- No-Op Detection: Link graph analysis to identify when event propagations have no effect
- Passive Link Optimization: Events through connections with no active Commonscript handlers can be optimized away
- Batch Processing: Multiple events processed together when possible
- Lazy Evaluation: Event payloads loaded only when accessed by handlers
- Circuit Breakers: Automatic disconnection of consistently failing links
Link Graph Optimization
The system maintains cached analysis of each collection's link graph to optimize event propagation:
- Handler Presence Detection: Track which card references have active Commonscript event handlers
- Propagation Path Analysis: Identify event paths that lead to cards with handlers vs. passive cards
- No-Op Path Elimination: Skip event propagation through link chains that lead nowhere active
- Graph State Invalidation: Refresh optimization cache when links change or Commonscript is updated
- Collection-Scoped Caching: Optimization caches are maintained per-collection, aligning with the reference-based architecture
- Future Enhancement: This optimization will be implemented alongside the Commonscript VM to detect when event propagations effectively result in no system state changes
Memory Management
- Event Pooling: Reuse event objects to reduce garbage collection
- Bounded Queues: Prevent memory exhaustion from event backlogs
- TTL Expiration: Old events automatically cleaned up
- Duplicate Detection Windows: Limited time window for duplicate checking
Scaling Considerations
- Horizontal Distribution: Events can be processed across multiple server instances
- Priority Queues: High-priority events processed before bulk updates
- Back-Pressure: System can throttle event generation under high load
- Monitoring: Event throughput and latency metrics for system health
Security Model
Event Security Boundaries
- Collection Isolation: Events cannot cross unauthorized collection boundaries
- Permission Inheritance: Event access follows existing card permission models
- Link Authorization: Users must have write access to create links between card references
- Handler Sandboxing: Commonscript event handlers run in restricted server-side environments
- Card Reference Scope: Events operate on card references within collections, respecting per-collection permission boundaries
Federation Security
- Cryptographic Signatures: Federated events include tamper-proof signatures
- Source Verification: Remote event sources validated through federation protocols
- Rate Limiting: Protection against event flooding from federated instances
- Content Filtering: Ability to block specific event types from external sources
Card Architecture Layers
The event system operates across multiple layers of card architecture, building on the foundational design described in 06-Card-Data-and-Collection-Streaming.md
:
Card Schema Level
- Type Definition: Defines the structure and behavior for all cards of this type
- Named Inlet Definitions: Schema-level inlet specifications via Commonscript
- Event Handler Templates: Commonscript event handling code attached to the schema
Card Instance Level
- Card Data: The actual field values and content of individual cards
- Instance Identity: Unique card ID that persists across collections
- Schema Reference: Link to the card's type schema
Card Reference Level
- Collection Context: How a card instance appears within a specific collection (as defined in the foundational architecture)
- Link Endpoints: GenericInlet/GenericOutlet for this card reference
- Position & Metadata: Collection-specific positioning and display properties
- Link Graph: Inlet/outlet connections are between card references, not instances
- Reference Counting: Links respect the existing reference counting system for card lifecycle management
Event Flow Across Layers
- User Action: Modifies card reference (position) or card instance (data)
- Schema Processing: Schema-level Commonscript processes events if present
- Reference Propagation: Events flow between connected card references
- Instance Updates: Successful processing may update underlying card instance data
Example Scenarios
Real-Time Dashboard
- Setup: Dashboard card reference with
data-feeds
named inlet accepting numeric data - Links: Multiple sensor card references connect their GenericOutlets to dashboard inlet
- Event Flow: Sensor
card_changed
events automatically propagate to dashboard reference - Processing: Dashboard schema's Commonscript aggregates data and updates card instance
- Fan-Out: Dashboard can forward summary events to other connected card references
Approval Workflow
- Request Creation: Employee creates expense request card reference in collection
- Automatic Routing: Request card reference connects to approval manager card reference
- Event Propagation:
card_created
event flows to approval manager reference - Custom Events: Manager schema's Commonscript emits
approval_request
custom event - Workflow Progression: Approval decision events flow back to original requester reference
Collaborative Document
- Document Structure: Main document card reference with multiple section card references
- Bidirectional Links: Section references connect to main document reference for coordination
- Change Propagation: Edits in section instances trigger events to main document reference
- Conflict Resolution: Main document schema's Commonscript handles simultaneous edits
- Notification Fan-Out: Change events propagate to all connected collaborator card references
Future Enhancements
Advanced Event Features
- Event Streaming: Long-running event streams for real-time data feeds
- Event Replay: Ability to replay historical events for debugging
- Event Transformation: Built-in functions for modifying events in transit
- Conditional Routing: Event routing based on payload content
Integration Capabilities
- Webhook Integration: External systems can send events to Commonplace cards
- API Event Sources: RESTful endpoints for receiving events from other services
- Message Queue Integration: Connection to enterprise message brokers
- Real-Time Subscriptions: Connections for live event streaming
Development Tools
- Event Debugger: Visual tools for tracing event propagation
- Performance Profiler: Analysis of event processing bottlenecks
- Link Visualizer: Graph views of card connection topology
- Test Event Generator: Tools for simulating events during development