Skip to content

noetl events

Kadyapam edited this page Jun 4, 2026 · 1 revision

noetl-events workspace crate

noetl-events is the dedicated home for the wire-format event envelope every NoETL Rust binary emits. Carved out of noetl-executor::events in cli#49

  • crates.io: noetl-events 0.1.0 (first release, 2026-06-05).
  • Source: repos/cli/events/.
  • Re-export contract: noetl-executor::events re-exports every public symbol verbatim, so existing call sites in the cli + noetl-worker keep working through the executor's re-export.
  • Adopted directly by: noetl-server (server v2.9.0, via noetl/server#38).

Why this crate exists

Until EE-4, the envelope lived in noetl-executor::events. That worked while only producers needed it (cli local-mode runner, noetl-worker NATS pull consumer). Once noetl-server started producing events itself in Phase D R2 (noetl/server#31) the server had to mirror the envelope by hand in its EventRequest struct + doc comments, kept in sync by code review. That alignment is the kind of thing that quietly drifts on the next round of changes.

The fix: a small, dependency-free crate that every component can link without dragging in the rest of the executor's surface (playbook parser, runtime trait, tools bridge, …). Server now takes a direct dep on noetl-events and pins the shared subset via From / TryFrom conversion impls — drift fails the build instead of the kind-validation cycle.

Public API

pub struct ExecutorEvent {
    pub execution_id: i64,
    pub event_type: String,
    pub step: String,
    pub status: String,
    pub created_at: DateTime<Utc>,
    #[serde(alias = "payload")]
    pub context: serde_json::Value,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub event_id: Option<i64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub worker_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub meta: Option<serde_json::Value>,
}

#[async_trait]
pub trait EventSink: Send + Sync {
    async fn emit(&self, event: ExecutorEvent) -> Result<()>;
}

pub struct EventEmitter {
    pub sink: Arc<dyn EventSink>,
    pub execution_id: i64,
}

pub struct NoopSink;  // Default sink for tests / skeleton runs.

Wire-format alignment

Field naming intentionally mirrors the Python EventEmitRequest shape so events produced by either stack project against the same noetl.event columns. See the noetl/noetl wiki page handle_event_timing for the field catalogue and per-field semantics.

  • context (the field on the envelope) accepts both context and the legacy payload key during deserialization (#[serde(alias = "payload")]) — pre-PR-EE-2 producers that still write payload keep working.
  • execution_id is i64 (matches the Python noetl.event bigint column + the executor's BridgeContext.execution_id).
  • event_id is Option<i64>; producers that don't fill it get the server-side noetl.snowflake_id() default.
  • worker_id + meta are optional and skip serialization when None (older wire-format consumers don't see unfamiliar keys).
  • created_at is stamped at emit time so per-component ordering is preserved across server-clock skew.

Re-export contract

noetl-executor 0.4.0 (the first executor release after EE-4) ships an events module that is a 1-line re-export:

// noetl-executor/src/events.rs
pub use noetl_events::{EventEmitter, EventSink, ExecutorEvent, NoopSink};

Every existing call site (use noetl_executor::events::ExecutorEvent;) keeps working unchanged. Direct callers (noetl-server, future crates) can take noetl-events = "0.1" and skip the executor entirely.

Adoption: noetl-server

noetl/server#38 wired the server-side adoption. The server's EventRequest struct stays distinct — it legitimately carries five server-only fields (result_kind, result_uri, event_ids, actionable, informative) and uses String wire format for execution_id / event_id (browser JSON-number precision). The SHARED SUBSET is anchored to noetl_events::ExecutorEvent via:

impl From<noetl_events::ExecutorEvent> for EventRequest { ... }
impl TryFrom<&EventRequest> for noetl_events::ExecutorEvent { ... }

with four wire-compat tests pinning the round-trip semantics.

See the noetl-server wiki page event-envelope for the server-side detail.

Path + version dep recipe

Workspace members that depend on noetl-events from inside the cli workspace should set both path and version per Cargo's recommendation:

# In a sibling workspace member's Cargo.toml
noetl-events = { version = "0.1.0", path = "../events" }

Local dev uses the path; cargo publish resolves against the crates.io version constraint. External consumers (noetl-server, future bins) take the version dep only:

noetl-events = "0.1"

Release history

Version Date Headline
0.1.0 2026-06-05 First release. Wire-format envelope + EventSink trait + EventEmitter + NoopSink carved out of noetl-executor::events. Six tests preserved verbatim (envelope round-trip, alias compat, optional-field omit-when-none, full-fidelity all-fields-set).

Related

Clone this wiki locally