etdl-core 0.1.1

ETDL runtime: BranchMonitor, retry policies, SLA anomaly detection, chaos injection, and telemetry for reliability-aware event-driven services
Documentation

ETDL — Event Tree Definition Language Compiler

Crates.io Docs.rs License crates.io downloads

Compile reliability models into code. ETDL (Event Tree Definition Language) is a declarative, design-time domain-specific language (DSL) that turns event tree analysis (IEC 62502) and fault tree analysis (IEC 61025) into a single .etdl document — and compiles that document into production-ready Rust, fully generated, with probability-driven SLAs, retry policies, and chaos injection built in.

No central workflow engine. No orchestration servers. No runtime interpreters. Your event tree becomes your code.


Why ETDL?

Most event-driven systems describe what should happen (topics, channels, message schemas) but never why, when, and with what reliability the system must respond. Failures are discovered in production, under load, by pager alerts.

ETDL moves reliability engineering to design time:

Concern Typical approach ETDL
Failure sequences Implicit in code, scattered across services Explicit event trees (IEC 62502)
Failure probability Guessed, or measured after incidents Exact fault trees (IEC 61025), resolved at compile time
SLA behavior Hand-rolled retry/timeout/backoff in every service Generated RetryPolicy + SLA tracker
Failure injection Custom scripts, feature flags, breakage in prod Declared chaos probability, scoped per event tree
Contract drift AsyncAPI docs rot, code diverges AsyncAPI 3.0 references resolved at compile time
Process flow BPMN diagrams no one reads Causal event trees that are the code

ETDL vs. alternatives

ETDL Temporal / Cadence Camunda / BPMN AWS Step Functions Hand-rolled sagas
Central runtime engine None — compiles to code Yes (workers + server) Yes (engine) Yes (AWS-managed) N/A
Reliability modeling First-class (fault trees) Manual Manual Manual Manual
IEC standards 61025 + 62502 No No No No
Probability-driven SLAs Compile-time constant No No No No
Deployable Anywhere (library code) Temporal cluster Camunda cluster AWS
Type-checked contracts AsyncAPI + ECEL at build Runtime Runtime Runtime No

ETDL follows the Smart Endpoints, Dumb Pipes philosophy: the intelligence lives in your services (as generated, testable Rust functions), not in a centralized brain that becomes a single point of failure.


How it works

An .etdl document declares event trees (causal sequences of barriers, operations, and consequences) and fault trees (how basic events combine into a top-level failure). The compiler validates everything — including probabilistic math, ECEL conditions, and AsyncAPI references — and emits a single Rust function per initiating event.

flowchart LR
    A[".etdl document<br/>(event trees + fault trees)"] --> B["etdl parse<br/>(etdl-parser)"]
    B --> C["etdl validate<br/>(etdl-compiler)"]
    C --> D["fault tree resolution<br/>top-event probability"]
    D --> E["code generation<br/>(etdl-compiler)"]
    E --> F["Generated Rust<br/>+ etdl-core runtime"]
    A -.-> G["AsyncAPI 3.0<br/>(YAML/JSON)"]
    G --> B
    style F fill:#4caf50,color:#fff

What a fault tree becomes at build time

In order-fulfillment.etdl, the top event PaymentGatewayFailure is an OR of two basic events. The compiler computes the exact probability:

// Computed from faultTrees.PaymentGatewayFailure.topEvent at build time (Section 5.16)
const PROCESS_PAYMENT_OPERATION_FAILURE_PROBABILITY: f64 = 0.012987;

What an event tree becomes at run time

pub async fn handle_order_placed_trigger(message: OrderPlaced) -> Result<(), WorkflowError> {
    let mut inventory_check_barrier = BranchMonitor::new("InventoryCheckBarrier");

    if message.payload.items.iter().all(|item| item.qty > 0) {
        inventory_check_barrier.record_branch("SUCCESS", 0.950000);
        let retry = RetryPolicy {
            max_attempts: 3,
            backoff_ms: 250,
            strategy: BackoffStrategy::Exponential,
        };
        match retry.execute(|| stripe_charge_handler(&message), Duration::from_millis(5000)).await {
            Ok(_result) => {
                publish_to_channel("FulfillmentChannel", _result).await?;
            }
            Err(err) => {
                inventory_check_barrier.record_failure("ProcessPaymentOperation", &err, Some(PROCESS_PAYMENT_OPERATION_FAILURE_PROBABILITY));
                publish_to_channel("DeadLetterChannel", message).await?;
            }
        }
    } else {
        inventory_check_barrier.record_branch("FAILURE", 0.050000);
        publish_to_channel("DeadLetterChannel", message).await?;
    }
    Ok(())
}

Quick Start

# 1. Install the CLI
cargo install etdl-cli

# 2. Write an .etdl document (or clone the example below)
# 3. Compile it to Rust
etdl compile order-fulfillment.etdl --target rust --out-dir ./generated

# 4. Validate without generating code
etdl validate order-fulfillment.etdl

A complete example

etdl: "1.0.0"
info:
  title: "Order Fulfillment Event Tree"
  version: "2.0.0"
  domain: "FulfillmentContext"

asyncapi_imports:
  orders_api: "./asyncapi/orders.yaml"
  payment_api: "./asyncapi/payments.yaml"

eventTrees:
  OrderFulfillment:
    initiatingEvent:
      id: OrderPlacedTrigger
      message: "orders_api#/components/messages/OrderPlaced"
      next: InventoryCheckBarrier

    nodes:
      InventoryCheckBarrier:
        type: barrier
        branches:
          - outcome: SUCCESS
            condition: "message.payload.items[*].qty > 0"
            probability: 0.95
            next: ProcessPaymentOperation
          - outcome: FAILURE
            condition: "default"
            probability: 0.05
            next: OutOfStockConsequence

      ProcessPaymentOperation:
        type: operation
        action: execute
        handler: "stripe_charge_handler"
        emits: "payment_api#/components/messages/PaymentProcessed"
        next: FulfillmentConsequence
        onFailure: PaymentFailedConsequence
        onFailureProbabilitySource: "#/faultTrees/PaymentGatewayFailure/topEvent"
        retryPolicy:
          maxAttempts: 3
          backoffMs: 250
          backoffStrategy: exponential
        timeoutMs: 5000

      FulfillmentConsequence:
        type: consequence
        operation: send
        channel: "orders_api#/channels/FulfillmentChannel"
        message: "payment_api#/components/messages/PaymentProcessed"

faultTrees:
  PaymentGatewayFailure:
    topEvent:
      id: PaymentCaptureFailed
      rootCause: GatewayUnavailableOrRejected
    gates:
      GatewayUnavailableOrRejected:
        type: OR
        inputs:
          - GatewayUnreachable
          - ChargeRejected
    basicEvents:
      GatewayUnreachable:
        probability: 0.008
      ChargeRejected:
        failureRate: 0.00021
        missionTime: 24

Run:

etdl compile order-fulfillment.etdl --target rust --out-dir ./generated

This validates the document, resolves PaymentGatewayFailure to 0.012987, and emits a handle_order_placed_trigger async function that retries with exponential backoff, tracks branch probabilities in a BranchMonitor, and routes failures to a dead-letter channel.


Concepts

Event Trees (IEC 62502)

A tree of barriers (decision gates), operations (side-effecting actions with retry/backoff/timeout), and consequences (outcomes). Every barrier branch carries a probability, and every operation can reference a fault tree for its failure probability. ETDL event trees mirror the event tree analysis method used in nuclear safety, aerospace, and process industry risk assessment — applied to event-driven software.

Fault Trees (IEC 61025)

A Boolean model of how basic events combine through gates (AND, OR, NOT, XOR, VOTING) into a top event. Basic events carry probability or failureRate + missionTime (exponential failure model). The compiler evaluates the tree exactly at build time, so failure probabilities are constants in your generated code — no runtime estimation, no surprises.

ECEL — Event-tree Condition Expression Language

Conditions on barrier branches are written in ECEL, a typed expression language (inspired by CEL) over the AsyncAPI message payload. The compiler type-checks expressions against resolved schemas at build time, catching qty > "three" before it ships.

Probability-linking

onFailureProbabilitySource: "#/faultTrees/PaymentGatewayFailure/topEvent" connects an operation's failure to a fault tree. The generated code records the resolved probability against the actual failure in the BranchMonitor, enabling SLA anomaly detection and chaos injection with declared, deterministic probabilities.

Runtime (etdl-core)

Component Purpose
BranchMonitor Tracks taken branches, probabilities, and failures per event tree
RetryPolicy Async retry with exponential/fixed backoff and max attempts
SlaTracker Detects anomaly rates vs. declared probabilities (ETDL_SLA_WINDOW, ETDL_SLA_THRESHOLD)
ChaosController Declared, seeded, scoped failure injection (disabled in production via ETDL_ENV)
Telemetry inject_traceparent W3C trace context propagation

Crates

Crate crates.io Description
etdl-cli Crates.io CLI: compile and validate .etdl documents
etdl-parser Crates.io .etdl document parser, ECEL parser, AsyncAPI 3.0 resolution with JSON Pointer (RFC 6901)
etdl-compiler Crates.io Semantic validation (E/V/W diagnostics), fault tree evaluation, MOCUS cut sets, code generation
etdl-core Crates.io Runtime library: BranchMonitor, retry, SLA tracking, chaos injection, telemetry

Documentation

Examples

Roadmap

  • Additional code generation targets (TypeScript, Go) via the CodeGenerator trait
  • Minimal cut-set reporting CLI (enumerate_minimal_cut_sets)
  • AsyncAPI 3.0 operation generation (asyncapi-codegen integration)
  • Editor language support (syntax highlighting, schema validation)

Contributing

Contributions are welcome! Open an issue or pull request. See CONTRIBUTING.

License

Apache 2.0 — see LICENSE.