Skip to main content

Language Reference

The policy engine uses a small domain-specific language for writing policy templates. This reference defines the template structure, schemas, type system, expression syntax, runtime input model, and evaluation rules.

What a template contains

A policy template has four parts:

  1. name <identifier>
  2. An optional intent { ... } block
  3. A required non-empty evidence { ... } block
  4. A required non-empty requires { ... } block

Their semantics are:

  • name is a human-readable template identifier and is part of the compiled template.
  • intent declares the schema for user-supplied policy inputs.
  • evidence declares the schema for runtime facts evaluated against the policy.
  • requires declares the boolean constraints evaluated against intent and evidence.

A schema is the set of field declarations in an intent or evidence block. A policy evaluation succeeds only if the runtime inputs match the declared schemas and every constraint in requires is satisfied.

Example:

name clothing_purchase_guard

intent {
acceptable_categories: set<string>
acceptable_colors: optional set<string>
acceptable_brands: optional set<string>
size: string
audience: string
max_price_cents: int
}

evidence {
category: string
color: string
brand: string
size: string
audience: string
price_cents: int
}

requires {
evidence.category in intent.acceptable_categories;
evidence.size == intent.size;
evidence.audience in {"men", "women", "unisex"};
evidence.audience == intent.audience;
evidence.price_cents <= intent.max_price_cents;
optional: evidence.color in intent.acceptable_colors;
optional: evidence.brand in intent.acceptable_brands;
}

Template structure rules:

  • intent may be omitted when the template does not need caller-supplied inputs.
  • intent {} is valid.
  • evidence must be present and must declare at least one field.
  • requires must be present and must contain at least one constraint.
  • each schema field name must be unique within its block.
  • top-level blocks may not be repeated.

Features not supported

This version of the language does not support:

  • nested input objects
  • arrays as first-class language values
  • range types
  • division
  • lexicographic string ordering
  • floating-point numbers
  • functions
  • user-defined types
  • loops or recursion
  • implicit type coercions
  • date arithmetic
  • timestamps
  • complex conditional schemas

Identifiers and field references

Identifiers are ASCII names matching this shape:

[A-Za-z_][A-Za-z0-9_]*

Reserved words

Reserved words cannot be used as the template name or as field names in intent or evidence schemas.

The reserved words are:

name
intent
evidence
requires
optional
bool
int
string
date
set
True
False
not
and
or
in
subset
superset
of

Field references are always namespace-qualified:

intent.max_price_cents
evidence.price_cents

The language has exactly two namespaces:

  • intent
  • evidence

Types

Supported types:

  • bool
  • int
  • string
  • date
  • set<int>
  • set<string>
  • set<date>

Important rules:

  • int is a signed 64-bit integer
  • string values are UTF-8 strings
  • date is a valid calendar date with a four-digit year, no time of day, and no timezone
  • sets are homogeneous
  • only intent fields may be marked optional

Example schema declarations:

intent {
approved_brands: optional set<string>
latest_delivery: date
}

evidence {
refurbished: bool
warranty_months: int
}

Literals

Literal forms:

True
False
0
18
-5
"CH"
date(2026-01-01)
{1, 2, 3}
{"CH", "DE", "FR"}
{date(2026-01-01), date(2026-06-01)}

String literals use double quotes. Recognized escapes are:

  • \\
  • \"
  • \n
  • \r
  • \t

Additional literal rules:

  • set literals must contain elements of one type only
  • duplicate elements in a template source set literal are rejected
  • {} is allowed only when its set type can be inferred from context

Dates use date(YYYY-MM-DD) in template source. The year must be four digits from 0000 through 9999, and the month/day combination must be a valid calendar date.

Expressions

Arithmetic

Arithmetic works only on integers.

Supported arithmetic operators:

  • +
  • -
  • *

Integer overflow and underflow are runtime evaluation errors.

Equality and ordering

Equality is allowed when both sides have the same type:

evidence.size == intent.size
evidence.audience == intent.audience

Ordering is allowed only on:

  • int
  • date

Examples:

evidence.price_cents <= intent.max_price_cents
evidence.delivery_date <= intent.latest_delivery

Membership and set relations

Supported set operations:

  • int in set<int>
  • string in set<string>
  • date in set<date>
  • int not in set<int>
  • string not in set<string>
  • date not in set<date>
  • set<int> subset of set<int>
  • set<string> subset of set<string>
  • set<date> subset of set<date>
  • set<int> superset of set<int>
  • set<string> superset of set<string>
  • set<date> superset of set<date>

Examples:

evidence.category in intent.acceptable_categories
optional: evidence.brand in intent.approved_brands
evidence.skus superset of intent.premium_skus

Boolean logic

Supported boolean operators:

  • not
  • and
  • or

A boolean-valued field or expression may appear directly in a constraint:

requires {
not evidence.refurbished;
}

The language rejects mixed unparenthesized and and or chains.

Allowed:

a and b and c
a or b or c
a and (b or c)
(a and b) or c

Rejected:

a and b or c
a or b and c

Chained comparisons

The language supports chained comparison expressions:

8 <= evidence.memory_gb <= 32
intent.earliest_ship <= evidence.ship_date <= intent.latest_ship

Rules:

  • only ==, <, <=, >, >= participate in chained comparisons
  • in and not in are not part of chained-comparison syntax
  • direction reversals such as a < b >= c are rejected

Precedence

From tightest to loosest:

  1. parentheses, literals, and field references
  2. *
  3. + and -
  4. unary not
  5. comparisons, equality, membership, and set relations
  6. homogeneous chains of and or homogeneous chains of or

Because unary not binds more tightly than comparisons, not a == b parses as (not a) == b.

Optional constraints

Optional intent fields are part of the type system and the runtime behavior.

Example:

intent {
approved_brands: optional set<string>
}

requires {
optional: evidence.brand in intent.approved_brands;
}

Rules:

  • an optional intent field may be absent at runtime
  • if a constraint references an optional intent field, that constraint must use optional:
  • an optional: constraint must reference at least one optional intent field
  • if any referenced optional intent field is absent at runtime, the constraint is treated as satisfied without evaluating its expression
  • if all referenced optional fields are present, the expression is evaluated normally

Runtime input model

Templates are evaluated against two runtime inputs:

  • intent
  • evidence

Each input is a map of field names to typed values. The field names and value types must match the corresponding schema declared by the template.

When using the CLI, these input maps are encoded as JSON files. The JSON encoding rules are covered in CLI Reference.

Runtime input rules:

  • missing required fields are rejected
  • extra fields are rejected
  • optional intent fields may be omitted
  • if an optional field is present, it must still match the declared type
  • duplicate elements in set input values are ignored
  • dates must follow the same valid-calendar-date rule as template date literals

Evaluation model

Before constraint evaluation begins, the engine validates the runtime inputs against the declared schemas.

After validation succeeds:

  • constraints are evaluated in source order
  • every constraint is evaluated, even if earlier constraints fail
  • each constraint produces one of: pass, fail, or runtime error
  • a policy passes only if every constraint is satisfied

Runtime errors can still occur after successful input validation. Examples include arithmetic overflow and underflow.

Compiled form

Bytecode generation is deterministic: compiling a template always produces the same bytecode and the same template ID.

Whitespace, comments, schema field order, and set literal element order do not define the policy. The template name, declared fields, constraints, and literal values do.

When you run policy-engine print, you see a readable representation of the compiled template. It may differ from the original source in formatting or ordering, but it describes the compiled policy that will actually be evaluated.

Practical consequences:

  • comments are not preserved
  • equivalent source forms may be printed in a normalized way, e.g.:
    • schema fields may appear in a normalized order,
    • set literals may appear in a normalized order,
    • source a not in b may be printed as the equivalent not (a in b) form.

Formatting rules

  • # starts a line comment and runs to the end of the line
  • comments may appear on their own line or after code
  • newlines separate top-level items and schema field declarations
  • constraints inside requires { ... } are separated by ;
  • newlines do not separate constraints
  • # inside a string literal is not a comment

Compile-time and runtime failures

Templates can fail at different stages:

  • compile-time syntax errors, such as malformed structure or invalid literals
  • compile-time type errors, such as mismatched comparisons or invalid boolean operands
  • input validation errors, such as missing required fields or wrong input types
  • runtime evaluation errors, such as arithmetic overflow

For CLI usage details and output formats, see CLI Reference.