ExMaude

Elixir bindings for the Maude formal verification system

Hex.pmDocsCICoverageLicense: MIT

Installation | Quick Start | Documentation


Overview

ExMaude provides a high-level Elixir API for interacting with Maude, a powerful formal specification language based on rewriting logic. Use ExMaude for:


Features

FeatureDescription
Port-based IPCEfficient communication via Erlang Ports
Worker PoolConcurrent operations via Poolboy
High-level APIreduce/3, rewrite/3, search/4, load_file/1
Output ParsingStructured parsing of Maude results
TelemetryBuilt-in observability events
IoT ModuleFormal conflict detection for physical-IoT automation rules
AI ModuleFormal conflict detection for AI agent policies (capability, sovereignty, authority, approval)

Installation

Requirements

Add ex_maude to your dependencies in mix.exs:

def deps do
[
{:ex_maude, "~> 0.2.0"}
]
end

Then install the Maude binary:

mix deps.get
mix maude.install

Quick Start

# Start the worker pool
{:ok, _} = ExMaude.Pool.start_link()
# Reduce a term to normal form
{:ok, "6"} = ExMaude.reduce("NAT", "1 + 2 + 3")
# Search state space
{:ok, solutions} = ExMaude.search("MY-MODULE", "initial", "goal", max_depth: 10)
# Load a custom module
:ok = ExMaude.load_file("/path/to/my-module.maude")

Configuration

config :ex_maude,
backend: :port, # :port | :cnode | :nif
maude_path: nil, # nil = auto-detect bundled binary
pool_size: 4, # Number of worker processes
pool_max_overflow: 2, # Extra workers under load
timeout: 5_000, # Default command timeout (ms)
start_pool: false, # Auto-start pool on application start
use_pty: true # Use PTY wrapper (Port backend only)

Configuration Options

OptionTypeDefaultDescription
backendatom():portCommunication backend (:port, :cnode, :nif)
maude_pathString.t()nilPath to Maude executable (nil = bundled)
pool_sizeinteger()4Number of Maude worker processes
pool_max_overflowinteger()2Extra workers allowed under load
timeoutinteger()5000Default command timeout in ms
start_poolboolean()falseAuto-start pool on application boot
use_ptyboolean()trueUse PTY wrapper for Maude prompts

Set use_pty: false if you encounter script: openpty: Device not configured errors (common in Docker/CI environments).

Backend Selection

ExMaude bundles Maude binaries for common platforms. No installation step needed for most users.

# Check available backends
ExMaude.Backend.available_backends()
#=> [:port] # plus :cnode if maude_bridge is compiled, :nif if the precompiled NIF loaded
# Switch backend at runtime (for testing)
Application.put_env(:ex_maude, :backend, :cnode)

API Reference

Term Operations

# Reduce using equations (deterministic)
ExMaude.reduce(module, term, opts \\ [])
# Rewrite using rules and equations
ExMaude.rewrite(module, term, opts \\ [])
# Search state space
ExMaude.search(module, initial, pattern, opts \\ [])

Module Loading

# Load from file
ExMaude.load_file("/path/to/module.maude")
# Load from string
ExMaude.load_module("""
fmod MY-NAT is
sort MyNat .
op zero : -> MyNat .
op s : MyNat -> MyNat .
endfm
""")

Direct Execution

# Execute raw Maude commands
{:ok, output} = ExMaude.execute("show modules .")
# Get Maude version
{:ok, version} = ExMaude.version()

IoT Rule Conflict Detection

ExMaude includes a Maude module implementing formal conflict detection for IoT automation rules, based on the AutoIoT paper.

rules = [
%{
id: "motion-light",
thing_id: "light-1",
trigger: {:prop_eq, "motion", true},
actions: [{:set_prop, "light-1", "state", "on"}],
priority: 1
},
%{
id: "night-mode",
thing_id: "light-1",
trigger: {:prop_gt, "time", 2300},
actions: [{:set_prop, "light-1", "state", "off"}],
priority: 1
}
]
{:ok, conflicts} = ExMaude.IoT.detect_conflicts(rules)

Detected Conflict Types

TypeDescription
State ConflictSame device, incompatible state changes
Environment ConflictOpposing environmental effects
State CascadingRule output triggers conflicting rule
State-Env CascadingCombined cascading effects

See ExMaude.IoT for the full rule schema, trigger types, and action types.


AI Rule Conflict Detection

ExMaude includes a Maude module for verifying AI-generated automation rules over Agents, Capabilities, ToolInvocations, and richer predicates (capability ontology, budget intervals, sovereignty, authority levels, approval gates). Use it to catch unsafe agent policies before they ship.

rules = [
%{
id: "approve-then-dose",
agent_id: {"acme", "ph-controller"},
trigger: {:prop_lt, "ph", {:int, 6}},
invocations: [
{:require_approval, "dosing_high_delta"},
{:invoke_tool, "dose", %{"ml" => 50}, "high_impact", :eu}
],
capability_grants: [{:cap, "ph_dosing", "v1"}],
authority_required: 2,
priority: 1
},
%{
id: "auto-dose",
agent_id: {"acme", "ph-controller"},
trigger: {:prop_lt, "ph", {:int, 5}},
invocations: [
{:invoke_tool, "dose", %{"ml" => 100}, "high_impact", :eu}
],
priority: 1
}
]
{:ok, conflicts} = ExMaude.AI.detect_conflicts(rules, jurisdictions: [:eu, :ch])
# => [%{type: :approval_gate_bypass, rule1: "auto-dose", rule2: nil, ...}]

Detected Conflict Types

TypeDetectionDescription
Tool Call ConflictpairwiseSame agent, same tool, conflicting required arguments
Capability ShadowingpairwiseTwo rules grant the same capability at equal priority within a tenant
Pack Tool Composition MismatchpairwiseSame capability name, mismatched type-shape signatures
Authority EscalationpairwiseRule grants a capability another rule requires at higher authority
Agent Loop CascadepairwiseOne rule's capability grants another rule's required capability
Sovereignty Violationsingle-ruleTool invocation routes through a forbidden jurisdiction
Approval Gate Bypasssingle-ruleHigh-impact invocation reachable without an approval gate
Budget Cascadesearch-basedChained rule firings exceed tenant budget (deferred to follow-up)
Cost Ceiling Infeasibilitysearch-basedTenant policy leaves empty cost-acceptable provider set (deferred)
Provider Routing Infeasibilitysearch-basedEmpty action space under tenant policy intersection (deferred)

When to choose AI rules over IoT rules

Use ExMaude.IoT for Things, Properties, and Actions in a single deployment (one building, one factory, one farm). Use ExMaude.AI for Agents with capability ontologies, tool-invocation arguments, tenant scoping, sovereignty, authority levels, or approval gates. Both can coexist — the templates and APIs are independent.

Unverifiable predicates

:contains and :matches (regex / string search) are not decidable in Maude's equational fragment. The validator returns {:error, "...unverifiable..."} for these — route them to a separate string-match safety net (sandbox, regex matcher, LLM-as-judge) rather than encoding them into the Maude template.

See ExMaude.AI for the full rule schema, predicate vocabulary, and invocation types.


Telemetry

ExMaude emits telemetry events compatible with Prometheus, OpenTelemetry, and other exporters. All measurements use native time units for precision.

Events

EventDescription
[:ex_maude, :command, :start]Command execution started
[:ex_maude, :command, :stop]Command execution completed
[:ex_maude, :command, :exception]Command raised an exception
[:ex_maude, :pool, :checkout, :start]Pool checkout started
[:ex_maude, :pool, :checkout, :stop]Pool checkout completed
[:ex_maude, :iot, :detect_conflicts, :start]IoT conflict detection started
[:ex_maude, :iot, :detect_conflicts, :stop]IoT conflict detection completed
[:ex_maude, :ai, :detect_conflicts, :start]AI conflict detection started
[:ex_maude, :ai, :detect_conflicts, :stop]AI conflict detection completed

Measurements

Metadata

Example: Prometheus Metrics

# In your application's telemetry module
defp metrics do
[
counter("ex_maude.command.stop.count", tags: [:operation, :result]),
distribution("ex_maude.command.stop.duration",
unit: {:native, :millisecond},
tags: [:operation, :result]
),
last_value("ex_maude.iot.detect_conflicts.stop.conflict_count")
]
end

Example: Custom Handler

:telemetry.attach(
"my-logger",
[:ex_maude, :command, :stop],
fn _, %{duration: d}, %{operation: op, result: r}, _ ->
ms = System.convert_time_unit(d, :native, :millisecond)
Logger.info("ExMaude #{op}: #{r} in #{ms}ms")
end,
nil
)

For complete event documentation, see ExMaude.Telemetry.


Architecture

ExMaude uses a pluggable backend architecture, allowing different communication strategies:

ExMaude (Public API)
ExMaude.Backend (Behaviour)
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
ExMaude.Backend.Port ExMaude.Backend.CNode ExMaude.Backend.NIF
│ │ │
▼ ▼ ▼
PTY + Maude CLI Erlang Distribution Direct libmaude
+ maude_bridge via Rustler
BackendIsolationLatencyUse Case
PortFullHigherDefault, safe, works everywhere
C-NodeFullMediumProduction, structured data
NIFNoneLowestHot paths, after profiling

Module Overview

ExMaude
├── ExMaude.Backend Backend behaviour and selection
├── ExMaude.Binary Binary lookup, platform detection, bundled binaries
├── ExMaude.Maude High-level command builders (reduce, rewrite, search)
├── ExMaude.Pool Poolboy worker pool management
├── ExMaude.Server Per-worker GenServer wrapping a backend session
├── ExMaude.Parser Output parsing utilities
├── ExMaude.Telemetry Telemetry events and helpers
├── ExMaude.IoT IoT rule conflict detection (Things, Properties, Actions)
└── ExMaude.AI AI rule conflict detection (Agents, Capabilities, Invocations)

Development

mix setup # Setup
mix test # Run tests
mix check # Run all quality checks
mix docs # Generate documentation

Running Benchmarks

mix bench # Parser benchmarks
mix bench.backends # Backend benchmarks (Port backend only)
mix bench.backends.all # Backend benchmarks (All backends: Port + C-Node)

C-Node Testing:

mix test.cnode # Run C-Node integration tests

Note: C-Node requires:

  1. Compiled binary: cd c_src && make
  2. The mix bench.backends.all and mix test.cnode aliases automatically handle Erlang distribution

Performance

ExMaude includes comprehensive benchmarks to help you understand performance characteristics and choose the right backend for your workload.

Benchmark Results

Key Takeaways

BackendLatencyUse Case
Port~107μs/opDefault, works everywhere, full isolation
C-Node~100μs/opProduction, high-throughput workloads
NIF~40μs/opHot paths, latency-critical workloads

Latency figures are reduce in NAT : 1 + 1 on Apple Silicon (M-series), averaged over 200 warm iterations. Run the benchmarks locally for your hardware.

Recommendation: Start with Port backend, switch to C-Node if benchmarks show communication overhead is a bottleneck.

Running Benchmarks

See Development section for benchmark commands.


Interactive Notebooks

Explore ExMaude interactively with Livebook:

NotebookDescriptionLivebook
Quick StartBasic usage and examplesRun in Livebook
Advanced UsageIoT conflicts, custom modules, poolingRun in Livebook
AI RulesAI agent policy conflict detectionRun in Livebook
Term RewritingRewriting and search deep diveRun in Livebook
BenchmarksPerformance metricsRun in Livebook

Documentation


References


Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.


License

ExMaude is released under the MIT License. See LICENSE for details.