ERDL Language Specification
Declarative semantic specification for AI governance · v2.1
One spec, one canonical tree, one hash — the same rule and input produce byte-for-byte identical results and hashes on any conforming implementation.
Core numbers
Why ERDL
| Problem | How ERDL solves it |
|---|---|
| LLM outputs are probabilistic | Deterministic when → then guardrails, evaluated outside the model — the prompt never holds the safety boundary |
| Rules drift across implementations | 318 JCS + SHA-256 vectors enforce byte-for-byte consistency |
| Compliance needs audit trails | Every evaluation produces a cryptographically verifiable hash |
| Business users can't read code | Three projection surfaces, compiled to one semantic tree |
Three projections: one rule, three ways
Business users write a decision table, integrators write Simple conditions, the kernel uses the expression tree — all compile to the same canonical tree and hash.
# Simple projection (30 operators, highest audit grade)
when:
logic: AND
conditions:
- field: "tool.name" operator: eq value: "issue_refund"
- field: "tool.args.amount" operator: gt value: 5000
then: REQUEST_HUMANOne rule, three languages — which can a non-engineer actually read?
The policy sentence (from legal, one line)
“Employees must not send customer data to any recipient outside the company’s domain list.”
Now, three languages turn it into an executable rule.
Version 1: ERDL (Simple form)
protocol: "erdl/v2"
metadata:
name: "customer-data-egress"
decision: ALLOW # allow when no rule matches
rules:
- name: "SEC-001"
description: "customer data must not be exfiltrated"
when:
logic: AND
conditions:
- field: "data.classification"
operator: eq
value: "customer_data"
- field: "recipient.domain"
operator: not_in
value: ["internal.example.com", "partner.example.com"]
then: DENY
message: "customer data must not be sent to external domains"
The engine-generated natural language (gloss projection):
When “data classification” equals “customer data”, and “recipient domain” is not in
[internal.example.com, partner.example.com], deny.
Version 2: Rego
package egress
import rego.v1
default allow := false # ← forget this line and it fails open
internal_domains := {"internal.example.com", "partner.example.com"}
internal_recipient if { # ← a "query", not a "verdict"
input.recipient.domain in internal_domains
}
deny if { # ← negate a query
input.data.classification == "customer_data"
not internal_recipient
}
allow if { # ← negate once more
not deny
}
Version 3: Cedar
forbid(
principal, # ← authorization triple, requires the entity model
action == Action::"send", # ← typed action, requires a schema
resource
)
when {
resource.classification == "customer_data" &&
!(resource.recipient.domain in ["internal.example.com", "partner.example.com"])
};
What must a non-engineer “extra-learn” to read each one correctly?
| ERDL | Rego | Cedar | |
|---|---|---|---|
| Verdict vs query | Verdict (DENY) | Query (allow is a set) | Verdict (forbid) |
| Default value | No (decision: ALLOW is written in the open) | Yes — omit default allow := false and it fails open | No (forbid is natively denying) |
| Double negation | No | Yes — not internal_recipient + allow if { not deny } | A little (!) |
| Entity model / schema | No (a field is a field) | No | Yes — principal/action/resource + typed |
| Three-kinds-of-equals trap | None | Yes (:= / == / =) | None |
The conclusion, at a glance:
- ERDL: a “field + operator + value” triple is the skeleton of natural language. Legal/compliance reads the two conditions in
whenand can restate “customer data + non-internal domain → deny” — word-for-word identical to the gloss the engine generated. - Rego: the reader must simultaneously grasp the “query model”, the “default-value boilerplate”, negation-as-failure, double negation, and the “three kinds of equals” — get any one of these five wrong and you can read “should deny” as “should allow”.
- Cedar: the reader must grasp “authorization triples + entity schema + forbid precedence” — an authorization model designed for engineers, not a rule table designed for legal.
Why ERDL “speaks human” and Rego/Cedar don’t
The root is not “syntactic sugar”; it is the semantic model:
- ERDL’s Simple form has “verdict + condition” semantics, isomorphic to the policy sentence — the policy says “must not”, the rule writes
then: DENY; the policy says “any”, the rule has nativeany/all. The rule is a structural rewrite of the policy, with no semantic translation. - Rego’s semantics are “query” — a rule “computes the set of satisfying cases”; a policy’s “forbid” must first become “compute the violating set”, then translate back to
allow = not deny. Two translations, and each is a drift point. - Cedar’s semantics are “authorization triples” — built for “who does what to which resource” access control, not for “enterprise policy sentences”. Stuffing “must not exfiltrate customer data” into the principal/action/resource frame is itself a semantic reconstruction.
So “a layperson gets it after a brief intro” isn’t marketing — it’s a choice of semantic model: when the rule’s semantic model is isomorphic to the policy’s semantic model, reading the rule ≈ reading the policy; when it isn’t, reading the rule = first learning an engineer’s abstraction language.
Note: in this comparison ERDL is real spec syntax (Simple form + gloss projection); Rego/Cedar are the “correct standard forms a proficient engineer would write”, with no straw-manning.
Quick Start (30 seconds)
$ npm install @openoba/erdl import { loadErdlFile, Evaluator } from '@openoba/erdl'
const { rules, metadata } = loadErdlFile('refund.erdl.yaml')
const result = new Evaluator().evaluate(
rules,
{ tool: { name: 'issue_refund', args: { amount: 8000 } } },
{ fallbackDecision: metadata.decision },
)
console.log(result.decision) // 'REQUEST_HUMAN'Full specification
Full spec: English (on-site) · 站内全文(中文) · npm @openoba/erdl