Skip to main content

Template Authoring

This guide explains how to write policy templates and prepare inputs for them.

It uses the clothing purchase example as the main thread, then pulls in smaller snippets from the laptop and order-shipment examples where they teach a feature better.

Template shape

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

The clothing purchase example looks like this:

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;
}

The name identifies the kind of template in human-readable form. It is part of the compiled template, so changing it changes the resulting template ID.

Choosing intent, evidence, and requirements

Use intent for policy inputs supplied by the user. These are the user's preferences, limits, permissions, or other policy terms.

Use evidence for runtime facts supplied at evaluation time. These facts describe the concrete operation, item, or situation being checked.

Use requires for the decision logic. Each statement in requires should be a business rule that must hold for the evidence to be acceptable under the intent.

As a rule of thumb, put stable user policy in intent, put observed runtime facts in evidence, and put comparisons between them in requires.

Declaring fields

The intent and evidence blocks are schemas. They declare the fields and types that runtime input values must provide.

Supported field types:

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

Important rules:

  • Only intent fields may be marked optional.
  • evidence fields are always required.
  • intent may be omitted if the template does not need caller-supplied preferences.
  • Field names are simple ASCII identifiers such as price_cents or approved_brands.
  • Field references always use a namespace: intent.max_price_cents, evidence.brand.

From the laptop example:

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

This means:

  • max_price_cents must be present in intent and must be an integer
  • latest_delivery must be present in intent and must be a calendar date
  • approved_brands may be omitted, but if it is present it must be a set of strings

Writing constraints

Each statement in requires { ... } must evaluate to a boolean result. Constraints are separated with semicolons.

Common patterns from the clothing example:

evidence.category in intent.acceptable_categories;
evidence.size == intent.size;
evidence.price_cents <= intent.max_price_cents;

These cover three of the most common checks:

  • membership in a set
  • equality between matching types
  • ordering comparisons on integers or dates

All constraints are evaluated in the order they appear. A policy passes only when every constraint is satisfied.

Optional constraints

Optional intent fields must be used inside an optional: constraint.

From the clothing example:

optional: evidence.brand in intent.acceptable_brands;

This behaves as follows:

  • if intent.acceptable_brands is missing, the constraint is treated as satisfied and is skipped
  • if intent.acceptable_brands is present, the expression is evaluated normally

Two practical rules follow from that:

  • if a constraint references an optional intent field, it must use the optional: prefix
  • if a constraint uses optional:, it must reference at least one optional intent field

Dates, ranges, and grouped logic

The laptop example shows date comparison and grouped boolean logic:

requires {
evidence.delivery_date <= intent.latest_delivery;
(
not evidence.refurbished
or evidence.warranty_months >= 12
);
}

The order-shipment example shows chained comparisons and set relations:

requires {
1 <= evidence.item_count <= intent.max_items;
intent.earliest_ship <= evidence.ship_date <= intent.latest_ship;
(
evidence.item_count < 10
or evidence.skus superset of intent.premium_skus
);
}

This language supports:

  • ==, <, <=, >, >=
  • in and not in
  • subset of and superset of
  • not, and, or
  • +, -, * on integers
  • chained comparisons such as 8 <= evidence.memory_gb <= 32

Authoring workflow

A practical authoring loop looks like this:

  1. Write or edit the template source.
  2. Compile it with policy-engine compile.
  3. Optionally, inspect the compiled result with policy-engine print.
  4. Prepare representative intent and evidence inputs for the schemas.
  5. Evaluate with policy-engine eval.

The print step is not required, but it is useful for confirming what actually got compiled.

When the CLI prints a policy_template_id, keep it with the policy or test case you compiled. You can later pass it to policy-engine eval --check-id to make sure the bytecode being evaluated is the one you expect.

Common authoring mistakes

Using an optional field without optional:

If a constraint references an optional intent field, prefix the constraint with optional:.

Mixing and and or without parentheses

Write a and (b or c) or (a and b) or c rather than a and b or c.

Using the wrong type

The two sides of == must have the same type, ordering only works on int and date, and membership must be scalar-in-set with matching element types.

Assuming print preserves source formatting

print shows a readable representation of the compiled template. It is for inspection, not exact source reproduction.

Using unsupported features

This language does not support nested input objects, arrays, floating-point numbers, functions, user-defined types, loops, implicit coercions, date arithmetic, timestamps, or complex conditional schemas.

Next steps