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:
name <identifier>- An optional
intent { ... }block - A required non-empty
evidence { ... }block - 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:
boolintstringdateset<int>set<string>set<date>
Important rules:
- Only
intentfields may be markedoptional. evidencefields are always required.intentmay be omitted if the template does not need caller-supplied preferences.- Field names are simple ASCII identifiers such as
price_centsorapproved_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_centsmust be present in intent and must be an integerlatest_deliverymust be present in intent and must be a calendar dateapproved_brandsmay 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_brandsis missing, the constraint is treated as satisfied and is skipped - if
intent.acceptable_brandsis 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:
==,<,<=,>,>=inandnot insubset ofandsuperset ofnot,and,or+,-,*on integers- chained comparisons such as
8 <= evidence.memory_gb <= 32
Authoring workflow
A practical authoring loop looks like this:
- Write or edit the template source.
- Compile it with
policy-engine compile. - Optionally, inspect the compiled result with
policy-engine print. - Prepare representative intent and evidence inputs for the schemas.
- 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
andandorwithout 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 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
- Read CLI Reference for the operational command details.
- Read Language Reference for the full syntax and semantics.