flow-ir-core 0.0.2

flow.ir Pure Rust schema + sync interpreter — Node / Expr / Dispatcher / eval (no mlua, no async)
Documentation
# flow-ir-core

flow.ir Pure Rust schema + sync interpreter. The core substrate (layer 2 of the 4-layer flow.ir stack).

No mlua, no async, no I/O — pure schema + `eval` + `Dispatcher` trait.

## Stack position

1. `flow-ir-lua` — Pure Lua DSL (separate repo, ecosystem-neutral)
2. **`flow-ir-core`** — this crate: Pure Rust schema + sync interpreter
3. [`mlua-flow-ir`]https://crates.io/crates/mlua-flow-ir — async runtime + mlua binding (re-exports this crate)
4. `mlua-swarm-engine` — host concerns (Spawner / Worker / Loop / AuthzPolicy / cp_state persist)

## What's in

- **3 Node kinds**`Step { ref, in, out }`, `Seq { children }`, `Branch { cond, then, else }` (+ `Fanout` / `Loop` / `Try`)
- **3 Expr ops**`Path { at }`, `Lit { value }`, `Eq { lhs, rhs }`
- **Discriminated unions**`#[serde(tag = "kind")]` / `#[serde(tag = "op")]` + `deny_unknown_fields`
- **`Dispatcher` trait** — host provides concrete `dispatch(&str, Value) -> Result<Value>` implementations

## Quick start

```rust
use flow_ir_core::{eval, Dispatcher, EvalError, Node};
use serde_json::{json, Value};

let node: Node = serde_json::from_value(json!({
    "kind": "step",
    "ref": "uppercase",
    "in": { "op": "path", "at": "$.input" },
    "out": { "op": "path", "at": "$.output" },
})).unwrap();

struct Fixture;
impl Dispatcher for Fixture {
    fn dispatch(&self, _r: &str, input: Value) -> Result<Value, EvalError> {
        if let Value::String(s) = input {
            Ok(Value::String(s.to_uppercase()))
        } else {
            Ok(input)
        }
    }
}

let result = eval(&node, json!({ "input": "hello" }), &Fixture).unwrap();
assert_eq!(result, json!({ "input": "hello", "output": "HELLO" }));
```

## License

MIT OR Apache-2.0