# `Lemma`
[🔗](https://github.com/lemma/lemma/blob/main/lib/lemma.ex#L1)

Lemma rules engine for Elixir.

Wraps the Lemma engine (Rust) via NIFs. Create an engine, load specs from
string or paths, run evaluations, and introspect schemas.

## Example

    {:ok, engine} = Lemma.new()
    :ok = Lemma.load(engine, "spec foo\nfact x: 1\nrule y: x + 1", "my_spec.lemma")
    {:ok, response} = Lemma.run(engine, "foo", [])
    # response is a map from decoded JSON

## Engine lifecycle

Each engine is an opaque resource. Do not share the same engine ref across
processes unless you serialize access (e.g. via a GenServer).

# `engine`

```elixir
@type engine() :: reference()
```

# `limits_map`

```elixir
@type limits_map() :: %{required(String.t()) =&gt; pos_integer()} | nil
```

# `spec_name`

```elixir
@type spec_name() :: String.t()
```

# `execution_plan`

```elixir
@spec execution_plan(engine(), spec_name(), keyword()) ::
  {:ok, map()} | {:error, term()}
```

Returns the serialized execution plan for a spec as a map.

Options: `:effective` (datetime string or nil).

# `format`

```elixir
@spec format(String.t()) :: {:ok, String.t()} | {:error, term()}
```

Formats Lemma source code. Does not require an engine instance.

## Example

    {:ok, formatted} = Lemma.format("spec foo\nfact   x:  1\nrule y: x +  1")

# `format_repository`

```elixir
@spec format_repository(engine(), String.t()) :: {:ok, String.t()} | {:error, term()}
```

Returns canonical Lemma source for a loaded repository (formatted from the in-engine AST).

Use `"lemma"` for the embedded SI stdlib (`spec si`).

# `invert`

```elixir
@spec invert(engine(), spec_name(), String.t() | nil, String.t(), map(), map()) ::
  {:ok, map()} | {:error, term()}
```

Inverts a rule to find input domains that produce a desired outcome.

`effective` is a datetime string or nil.

Target is a map with `:outcome` ("value" | "veto" | "any_value" | "any_veto"),
optionally `:op` ("eq" | "neq" | "lt" | etc.), and for "value"/"veto": `:value` or `:message`.

# `list`

```elixir
@spec list(engine()) :: {:ok, [map()]} | {:error, term()}
```

Lists loaded specs grouped by repository (same order as the engine:
workspace first, then dependencies).

Each element is `%{repository: %{name: ..., dependency: ..., start_line: ..., attribute: ...}, specs: [...]}`
where `:name` / `:dependency` are strings or `nil` (workspace has `:name` nil),
`:start_line` is a non-negative integer, and `:attribute` is the load-source label string
or `nil` (same display as `SourceType` in Rust — path, `volatile`, …).
Each entry in `:specs` has `:name`, `:effective_from`, `:effective_to`, `:start_line`,
`:attribute`, and `:schema` (decoded JSON object).

Temporal versions form a half-open `[effective_from, effective_to)` range:

- `:effective_from` is `nil` when the spec has no declared start date (the
  first version is unbounded at the start).
- `:effective_to` is `nil` when the spec has no later version (this row is
  the latest and stays valid forward indefinitely).
- Otherwise `:effective_to` equals the next version's `:effective_from`
  (exclusive end of this row's validity).

`:schema` is the decoded [`Lemma.schema/3`] envelope for this version so
callers never need a second round-trip.

Always includes the embedded `lemma` repository (`spec si`) on a fresh engine.
Returns `{:ok, []}` only when no repositories have specs (should not happen on `Lemma.new/1`).

# `load`

```elixir
@spec load(engine(), String.t(), String.t()) :: :ok | {:error, [map()]}
```

Loads a spec from a string. Source label is used for error reporting (e.g. "my_spec.lemma").
Use "inline" when no path.

# `load_batch`

```elixir
@spec load_batch(engine(), %{required(String.t()) =&gt; String.t()}, String.t() | nil) ::
  :ok | {:error, [map()]}
```

Loads multiple Lemma sources in one planning pass.

`sources` is a map of path label (for errors) to source text. Use `""` as key for volatile/inline.

`dependency` is optional; when non-empty, repositories in this batch are tagged with that dependency id (same as the Rust `Engine.load_batch/2` second argument).

# `load_from_paths`

```elixir
@spec load_from_paths(engine(), [String.t()]) :: :ok | {:error, [map()]}
```

Loads specs from paths (files and/or directories). Directories are expanded one level;
only .lemma files are loaded.

# `new`

```elixir
@spec new(limits_map()) :: {:ok, engine()} | {:error, term()}
```

Creates a new engine. Optionally pass a map of resource limits; omitted keys use defaults.

## Options (limits map keys)

- `max_sources` - max sources per load_from_paths (after expanding paths)
- `max_loaded_bytes` - max total bytes to load
- `max_source_size_bytes` - max single source text size in bytes
- `max_total_expression_count` - max expression nodes
- `max_expression_depth` - max nesting depth
- `max_expression_count` - max expressions per source (parser)
- `max_data_value_bytes` - max data value size

## Examples

    {:ok, engine} = Lemma.new()
    {:ok, engine} = Lemma.new(%{max_sources: 100})

# `remove_spec`

```elixir
@spec remove_spec(engine(), spec_name(), String.t()) :: :ok | {:error, term()}
```

Removes a spec from the engine by name and effective datetime.

# `repositories`

```elixir
@spec repositories(engine()) :: {:ok, [map()]} | {:error, term()}
```

Loaded repositories (workspace and dependencies). Each map has string keys `"name"` and `"dependency"`.

# `run`

```elixir
@spec run(engine(), spec_name(), keyword()) :: {:ok, map()} | {:error, term()}
```

Runs a spec. Options: `:effective` (datetime string or nil), `:data` (map).

Returns decoded JSON response.

# `schema`

```elixir
@spec schema(engine(), spec_name(), keyword()) :: {:ok, map()} | {:error, term()}
```

Returns the schema for a spec.

Options: `:effective` (datetime string or nil).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
