constraints: list of string expressions evaluated against
the call’s arguments. Every constraint must pass for the call to authorize
(implicit AND).
New here? Start with Writing conditions — a
guided, example-first walkthrough. This page is the terse reference to look
things up once you know the shape.
Types are compared strictly, matching Rego: a boolean is not a number, so
true == 1 is false and true in [1] is false (unlike Python’s True == 1).
Cross-type ordered comparisons ("x" > 10) never pass — they fail closed.
Each side of a comparison is an operand — a literal, a field path, or
count(...):
A bare unquoted word on the right is read as a field reference, so a
forgotten-quotes typo (
args.x == USD) compares against the field USD
(usually absent → deny) rather than the string "USD". Quote strings.
Whole-line string functions take a field and a string literal:
Quantifiers constrain the elements of a list-valued argument.
. is the
current element (.field for a sub-field), usable in any condition:
Element sub-fields and nesting work too:
. is only valid inside
a quantifier body.
Besides args.*, constraints can reference two call-scope facts: role
(the caller’s role) and tool (the tool being invoked) — e.g.
role == "admin" or tool == "refund_order". These mirror Rego’s
input.role / input.tool.
Caller attributes (ctx.*)
Constraints can also filter on the caller’s attributes — an open bag set on
the request scope (HexgateContext(attributes={...})) — through the ctx.<key>
namespace, alongside args.* / role / tool:
ctx.<key> fails closed (deny), like any absent reference.
ctx.* may gate allow/deny decisions — but it carries the same trust
contract as role: populate HexgateContext.attributes from trusted
server-side data (your auth/session/IdP), never from raw client input. An
attribute is exactly as trustworthy as the code that set it.
Named constants
Define reusable values once in aconsts: block and reference them as
consts.<name> — a number, string, or list:
inherits: like tools, so shared constants belong in
a mixin. A consts.<name> that isn’t defined is rejected at build (and denies
on the pydantic engine); across roles a name must map to a single value.
Boolean composition
Combine conditions withand, or, not, and parentheses:
or < and < not; use () to group. Multiple
constraint lines are still AND-ed, so and is only needed to combine with an
or. (Boolean ops inside a quantifier body — e.g.
every(args.x, .a == 1 or .b == 2) — aren’t supported yet.)
End to end
With thisbilling role policy and async with HexgateContext(user_id="alice", user_roles=["billing"]):
refund_order(amount=200, currency="USD")→ ✅ allowedrefund_order(amount=600, currency="USD")→ ❌ denied — constraintargs.amount <= 500refund_order(amount=200, currency="EUR")→ ❌ denied — constraintargs.currency == "USD"wire_transfer(amount=50000)→ ✋ requires approval (mode =approval_required)
user_roles=["default"] and refund_order is missing from that policy — it
falls through to default_policy.mode (deny).
Constraints are Rego-compatible by design: the WASM engine compiles them to OPA
Rego unchanged, and the pydantic engine evaluates the same strings in-process —
both produce identical decisions (there’s a parity test suite that proves it).