Skip to main content

ADL v0.1 — Adjudon Decision Language

ADL (Adjudon Decision Language) is the typed, human-readable surface for authoring policies. It compiles to AWS Cedar (CNCF Sandbox, peer-reviewed formal semantics) for production evaluation. ADL exists because Cedar's syntax is optimized for IAM-shape problems, and AI-decision policies need a vocabulary that matches confidenceScore, outputDecision, humanOverride, and friends.

Why a separate language

ConcernCedar nativeADL
AI-decision predicatesHas to be expressed via context attributesFirst-class confidenceScore < 0.7
Decimal handlingMethod calls (a.lessThan(b))Operators (a < b) — auto-translated
Type errorsCaught at evaluationCaught at compile time
Customer authoringSteeper learning curveTuned for compliance teams
Substrate portabilitySingle substrate (Cedar)Substrate-agnostic — recompiles to OPA, Rego, or future engines

Lifting from JSON v1

Existing JSON v1 policies (the legacy surface that ships in templates and the dashboard editor) are lifted to ADL via adlLifter.liftV1ToAdl. The lift is loss-less: every JSON v1 condition + action maps to an ADL clause. Customers can read the lifted ADL view at GET /api/policies/:id/adl (Phase 1 Track E read-only ship; Phase 2 Track A.1–A.3 ships the Monaco editor).

Predicate registry (seed, Phase 1)

PredicateUnderlying fieldOperatorArgs
confidence_below(threshold)confidenceScore<threshold: decimal
confidence_below_or_equal(threshold)confidenceScore<=threshold: decimal
confidence_above(threshold)confidenceScore>threshold: decimal
confidence_above_or_equal(threshold)confidenceScore>=threshold: decimal
confidence_equals(value)confidenceScore==value: decimal
score_calibrated_below(threshold)score_calibrated<threshold: decimal
status_equals(value)status==value: string
output_contains(substring)outputDecisioncontainssubstring: string
output_matches_regex(pattern)outputDecisionmatches (RE2)pattern: string
agent_equals(agentId)agentId==agentId: string
human_override_enabled(value)humanOverride==value: bool

New predicates ship per release; the predicate registry is versioned with the language (adjudon-adl-v0.1). Customers writing custom predicates submit a request via the dashboard; Adjudon adds the type binding + Cedar compile target. The lifter (adlLifter.liftV1ToAdl) emits a generic <field>_<operator> predicate for legacy v1 conditions not yet promoted to the curated registry.

Surface syntax (textual)

ADL v0.1 uses a predicate-call surface — every condition is a named domain function from the predicate registry, applied to typed arguments. This is closer to Cedar's evaluation shape than infix operator notation and gives the editor a clean autocomplete + arity-check experience.

name "Block low-confidence outputs"
priority 10
enabled true

when confidence_below(0.7)
and human_override_enabled(false)
or status_equals("flagged")

then flag_for_review

Operators:

TokenMeaning
andShort-circuit AND
orShort-circuit OR
notBoolean negation (Phase 2 — no JSON v1 equivalent yet)
(...)Explicit grouping (flips default and > or precedence)

Reserved words: name, priority, enabled, when, then, and, or, not, true, false.

Grammar (EBNF)

POLICY        = HEADER? WHEN_CLAUSE THEN_CLAUSE
HEADER = ( "name" STRING | "priority" INTEGER | "enabled" BOOL )*
WHEN_CLAUSE = "when" EXPRESSION
EXPRESSION = OR_EXPR
OR_EXPR = AND_EXPR ( "or" AND_EXPR )*
AND_EXPR = UNARY ( "and" UNARY )*
UNARY = "not" UNARY | PRIMARY
PRIMARY = PRED_CALL | "(" EXPRESSION ")"
PRED_CALL = IDENT "(" ( ARG ( "," ARG )* )? ")"
ARG = NUMBER | STRING | BOOL
THEN_CLAUSE = "then" ACTION ( "," ACTION )*
ACTION = IDENT (* one of: block flag_for_review notify approve allow auto_approve *)

STRING = '"' ( ESC | <any-char-except-quote-or-backslash> )* '"'
| "'" ( ESC | <any-char-except-quote-or-backslash> )* "'"
ESC = "\\" ( "n" | "t" | "r" | "\"" | "'" | "\\" )
NUMBER = "-"? <digit>+ ( "." <digit>+ )?
BOOL = "true" | "false"
IDENT = <letter-or-underscore> ( <letter-or-digit-or-underscore> )*
INTEGER = "-"? <digit>+

Comments: // line and /* block */. Stripped at tokenization.

Predicate vocabulary (seed registry)

Action ladder

ADL supports the same 5 action types as JSON v1, with the same priority ordering:

block               (highest priority — never overridden by lower)
flag_for_review + auto_approve (Auto-Approval Engine merger semantic)
flag_for_review
notify
auto_approve standalone
approve (lowest priority — explicit allow)

The Auto-Approval Engine merger semantic (LD-9, implemented in policyEngine.evaluatePolicies): if flag_for_review and auto_approve both fire on the same trace from any combination of policies, auto_approve wins and the trace resolves as approved (the auto-approval marker is preserved on the transcript so the audit trail shows which pattern caused the resolution). auto_approve NEVER overrides a block from any policy on the same trace — the hard-rule contract holds at the evaluator level.

Compile chain

                       ┌──────────┐
ADL textual source ────▶ adlParser ─────────────▶ ADL IR (JSON tree)
└──────────┘ │
│ adlLowerer

JSON v1 policy ◀────[lowerAdlToV1]───── ADL IR ───[policyCompiler]──▶ Cedar
│ ▲
│ liftV1ToAdl (adlLifter) │
└──────────────────────────────────┘

Three transforms make ADL bidirectional + substrate-portable:

  • adlParser.parseAdl(source) — textual ADL → ADL IR
  • adlLowerer.lowerAdlToV1(ir) — ADL IR → JSON v1 (policyEngine + storage)
  • adlLifter.liftV1ToAdl(policy) — JSON v1 → ADL IR (round-trip + read view)
  • policyCompiler.compileV1ToCedar(policy) — JSON v1 → Cedar (production substrate)

A round-trip property test (backend/test/adlParser.unit.test.mjs) asserts that lowerAdlToV1(liftV1ToAdl(P)) is engine-equivalent to P for every random v1 policy in a 200-case fast-check suite. This is the kill-gate for any parser/lowerer change.

The IR is substrate-agnostic. Phase 3.1 ships an OPA/Rego compile target so customers who already operate OPA at scale can run Adjudon policies on their existing infrastructure (Cedar remains primary).

Compile errors

The compiler raises PolicyCompileError (backend/services/policyCompiler.js) with one of these structured codes:

CodeMeaning
POLICY_COMPILE_UNKNOWN_FIELDField not in the registry (FIELD_TYPES)
POLICY_COMPILE_TYPE_MISMATCHOperator argument type does not match field type
POLICY_COMPILE_BAD_OPERATOROperator not supported for this field's type
POLICY_COMPILE_REGEX_NOT_SUBSTRINGRE2 rejected the regex pattern
POLICY_COMPILE_NO_FIELDCondition object missing field property
POLICY_COMPILE_NO_CONDITIONSPolicy has no conditions to compile
POLICY_COMPILE_INTERNALCompiler invariant violated — file a bug
POLICY_COMPILE_ERRORGeneric compile failure (rare; specifics in .message)

The ADL parser raises its own error class (AdlParseError in backend/services/adlParser.js) for surface-level issues (ADL_PARSE_ERROR, ADL_MISSING_WHEN, ADL_MISSING_THEN, ADL_ARITY_MISMATCH, ADL_TYPE_MISMATCH, ADL_UNKNOWN_ACTION, etc.). Each error carries line + column so the dashboard editor can highlight the exact token.

Property-based equivalence

Every PR to the compiler runs a 1000-case property-based test (fast-check) that generates random ADL policies + random traces and asserts that:

  • The legacy JSON v1 evaluator and the Cedar substrate produce identical match results.
  • The compile chain is round-trip stable: liftV1ToAdl(p)compileAdlToCedar(...) matches compileV1ToCedar(p) cell-for-cell.

This is the kill-gate for any compiler change. CI fails on a single divergence.

Versioning

ADL is versioned independently from Adjudon's API. The current version is adjudon-adl-v0.1. Breaking changes require a new major version (v0.2, v1.0) and a migration path documented in the release notes. Existing PolicyVersion records pin the ADL version they were compiled under, so a regulator replaying a 2-year-old transcript gets the exact semantics that were in force at evaluation time — even if the language has since evolved.