Skip to main content

erdl-formal

A formal verifier for the ERDL expression kernel — proving rule safety over all inputs with Z3.

There are two kinds of determinism: the determinism tests cover, and the determinism mathematics proves. erdl-formal provides the latter.

"erdl-formal formally verifies ERDL v2.1's expression kernel and evaluation semantics (§5, §7, Appendix A), covering all 34 nodes and the E1–E12 constraints. The spec's other layers (document structure, gloss rendering, integration modes) are guaranteed by test vectors and engineering verification."

Why now: LLMs have brute force. They don't have direction.

LLMs are probabilistic: same input, different answers. Hand enterprise decisions to a probability distribution, and the auditors will eventually ask — "on what basis was this decision made?"

The industry is converging on an answer: let the LLM understand; let a deterministic rule engine decide. But a rule engine's "determinism" is usually underwritten by unit tests — and tests only prove the inputs you happened to write.

In regulated industries — finance, insurance, government — the audit question is singular:

Does this rule hold for every input?

Sampled tests can't answer that. Only a proof can. Cedar Analysis proved this approach works inside AWS (Lean formalization + SMT symbolic analysis). erdl-formal does the same for the ERDL expression kernel — lifting determinism from sampled testing to exhaustive proof.

  • Cedar Analysis: used inside AWS, Lean + SMT, not open to the public, serving only AWS's own policy language.
  • OPA/Rego: no formal semantics — "the implementation is the spec."
  • erdl-formal: open source, 34-node full coverage, ERDL-specific semantics (money, time, decision objects, bidirectional gloss).

One proof in 30 seconds

The G3 classification gate: file classification > operator classification → DENY. We want to prove the rule is both reachable and fail-closed:

from erdl_formal.field_contracts import FieldContract, Schema
from erdl_formal.properties import always_denies

schema = Schema()
schema.add(FieldContract(field="file_cls", type="int"))
schema.add(FieldContract(field="op_cls", type="int"))

# when: file_cls > op_cls  →  DENY
rule = ["gt", ["field", "file_cls"], ["field", "op_cls"]]

# One assertion, two properties at once:
# 1) reachable — with both fields present, a higher classification fires the block
# 2) fail-closed — with op_cls missing, the comparison collapses to false (E11),
#    and no permissive bypass can ever open
assert always_denies(rule, schema, premises=["file_cls", "op_cls"], missing_field="op_cls")

No test cases. No sampling. Z3 searches the space of all integers for an input violating the property: if one exists, you get a concrete, replayable counterexample (re-checkable against the real engine); if not, UNSAT — the property holds for every input. QED.

What you can prove

Property Meaning Origin
never-errors evaluation never raises an EvalError Cedar
always-denies fires whenever its guard can — including fail-closed: a missing field never opens a bypass Cedar + E11
always-allows / subsumption / equivalence / disjointness permissive / implication / equivalence / mutual exclusion Cedar
override-soundness overrides go DENY→ALLOW only (never toward a less-safe state) ERDL-specific
ring-respect without overrides, ring order is honored in the DENY direction ERDL-specific
emergency-shortcut EMERGENCY_HALT short-circuits the moment it fires ERDL-specific

Every property can synthesize a concrete counterexample, and every counterexample can be replayed against the real engine — proof plus differential testing, double insurance.

34/34 nodes, exact E1–E12 semantics

  • All 34 nodes have SMT encodings: value / logic / comparison / set / string / existence / quantifier / arithmetic / time / aggregate.
  • The hard semantics aren't "roughly right" — they are bit-exact:
    • E2 fixed-point decimals: scale=14 + half-even — money is not allowed 0.1 + 0.2 drift;
    • E8 quantifier empty-array folding: anti-vacuous-truth — all([]) is false, not true;
    • E11 three-valued logic: missing fields collapse to false at the leaves (not Kleene — no fail-open);
    • E12 tier folding, E10 NFC normalization.

Independent verifier: three-way, byte-for-byte

A verifier is only credible if it never peeks at the examinee's answers. erdl-formal encodes the spec alone (erdl-language-spec), with zero dependency on any ERDL engine implementation, forming a three-way independent cross-check with erdl (the TS engine) and erdl-vectors (frozen vectors):

Cross-check Pair Result
Fixed-point fixed_point.py ↔ erdl fixed-point.js byte-identical
Resolution resolution.py ↔ erdl Evaluator 4 scenarios
Arithmetic vectors ↔ erdl-vectors V-ENGINE 7/7
Calendar vectors ↔ erdl-vectors V-ENGINE 6/6
G3 counterexample replay tvl.py ↔ erdl engine scenario-identical

Measurements, not endorsements. Three independently built systems agreeing byte-for-byte means the spec is precise enough to sustain exact independent reimplementation.

How it differs from Cedar / OPA

Dimension Cedar Analysis OPA / Rego erdl-formal
Formal verification ✅ Lean + SMT (the field's benchmark, closed) ❌ no formal semantics — the implementation is the spec ✅ SMT (Z3), open source
Fixed-point (money) decimals only via extension plugin float64 loses precision ✅ scale=14 + half-even
Time / calendar ✅ UTC calendar (days_between / date_add / date_part / month-end)
Aggregation ✅ aggregate (count / sum / avg / min / max)
Quantifiers ✅ all / any / none (E8 empty-array fold)
Decision object policy IDs only unsigned logs ✅ rich DO + hash chain
Natural language one-way (NL→policy) one-way ✅ deterministic gloss, anchored round-trip (two-way)

The difference is not "more formal" — Cedar's stack is the benchmark, and we say so. The difference is what gets formalized: ERDL is an enterprise rule kernel built for money, time, aggregation, quantifiers, decision objects, and two-way natural language — none of which exist in the Cedar / OPA world (the rows above).

Architecture

ERDL rule (S-expression)
   │
   ▼  compiler.py (symbolic compiler: schema-driven field typing + quantifier index unfolding)
Z3 TVL expressions (TVL(τ) = Def | Missing, leaf collapse)
   │
   ▼  properties.py (property checks) / resolution.py (resolution reference model)
sat / unsat + counterexample (replayed against the real engine)

Installation

Users (from PyPI, one command):

pip install erdl-formal

Developers (from source, editable install, changes take effect immediately):

git clone https://github.com/OpenOBA/erdl-formal.git
cd erdl-formal
python -m pip install -e ".[dev]"   # Python ≥3.11 (developed on 3.14), z3-solver ≥4.13 (verified on 5.1.0)

Build a distribution (wheel + sdist, for release / offline distribution):

python -m pip install build
python -m build        # produces dist/erdl_formal-0.1.1-py3-none-any.whl + .tar.gz

Quick start

pytest                               # 130 passing
python examples/verify_g3.py         # prove the G3 classification rule (reachable + fail-closed)
python replay/crosscheck-vectors.py  # cross-check against erdl-vectors frozen vectors

Documentation

  • docs/semantics.en.md — denotational semantics for all 34 nodes
  • docs/tvl-encoding.en.md — three-valued logic SMT encoding
  • docs/field-contracts.en.md — verification schema contracts
  • docs/DEVELOPER-GUIDE.en.md — developer guide (architecture / adding nodes / properties / API / build & publish)

Contributing & security

  • CONTRIBUTING.md — contribution process (issue-first, review)
  • SECURITY.md — report vulnerabilities privately, no public issue
  • CHANGELOG.md — changelog (Keep a Changelog)
  • CODE_OF_CONDUCT.md / style_guide.md

License

Apache-2.0 · © 2026 深圳市秒镜科技有限公司 (Shenzhen Miaojing Technology Co., Ltd.)

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

erdl_formal-0.1.2.tar.gz (34.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

erdl_formal-0.1.2-py3-none-any.whl (23.9 kB view details)

Uploaded Python 3

File details

Details for the file erdl_formal-0.1.2.tar.gz.

File metadata

  • Download URL: erdl_formal-0.1.2.tar.gz
  • Upload date:
  • Size: 34.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for erdl_formal-0.1.2.tar.gz
Algorithm Hash digest
SHA256 b40c3236311db0ae1e50b33d24b32b724e04ce3b92840e1d93b86bf50bdaed9c
MD5 6128863d2ce5e86bbd4186a46a903a4b
BLAKE2b-256 b0f4400ade25fb315172e057de14290a4784737df190eb1f3d701e4c1d979ac4

See more details on using hashes here.

File details

Details for the file erdl_formal-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: erdl_formal-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 23.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.14.4

File hashes

Hashes for erdl_formal-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 f6b1fd34adc06886f082dbedfebdd72b1a4d81b3467f5a46f209e55adb0a1b3c
MD5 d75f83a2fb5c0413c9b797175a08936e
BLAKE2b-256 e6bec48abc9f07263027d9f20616e8677fd5cbbd11aaf38a75cefec0b7f6c3b0

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.20

2 files

0.1.19

2 files

0.1.18

2 files

0.1.17

2 files

This release

0.1.2 This release

2 files

0.1.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page