RF expressions are a strict subset of HCL expressions. The accepted shape depends on position:
General HCL expression evaluation is not available in where or assert.
Static string fields use separate constant evaluation described below.
RF validates numeric value, not spelling: 1.0 and 1e3 are accepted
because their values are exact integers. 1.5 is not. Although runtime scalar
evidence can contain signed integers, native RF source has no unary minus
operator. Authorable number literals are therefore non-negative. -1 is
rejected as an unsupported unary expression.
Static metadata strings may be empty only where the block-specific table permits
it. For example, definition description may be empty, while Policy
message may not.
A match.where predicate tests the instance currently considered by its
surrounding Rule or member match. Unknown or sensitive instance values stay
unknown, so a predicate cannot prove that such a candidate matches.
predicate = boolean-literal | "!", predicate | "(", predicate, ")" | predicate, ("&&" | "||"), predicate | comparison ;
comparison = predicate-operand, comparison-operator, predicate-operand ;
predicate-operand = string-literal | boolean-literal | integer-valued-number-literal | source-traversal ;
comparison-operator = "==" | "!=" | "<" | "<=" | ">" | ">=" ;Examples:
where = source.enabled == truewhere = source.mode == "ACTIVE"where = source.replicas >= 2where = source.enabled == true && source.replicas >= 2where = !(source.mode == "DISABLED")Rules:
- only
source.*traversal is accepted; - a bare Boolean literal is accepted;
- bare traversal is not a predicate;
- equality and inequality require the same runtime scalar type;
- ordering requires numbers;
- missing, unknown, collection-valued, or type-incompatible evidence makes the comparison unknown;
- only a known
trueaccepts a Rule candidate.
The compiler cannot always know a traversal's result type. A syntactically accepted
comparison such as source.enabled > true becomes unknown at evaluation,
rather than inventing ordering for Booleans.
Policy assertions operate on queries over facts in the selected architecture stage of a Form. They cannot traverse raw plan or state values.
assertion = boolean-value ;
boolean-value = boolean-literal | exists-call | "!", boolean-value | "(", boolean-value, ")" | boolean-value, ("&&" | "||"), boolean-value | boolean-value, ("==" | "!="), boolean-value | number-value, numeric-operator, number-value ;
number-value = integer-valued-number-literal | length-call | "(", number-value, ")" ;
numeric-operator = "==" | "!=" | "<" | "<=" | ">" | ">=" ;
exists-call = "exists", "(", query, ")" ;length-call = "length", "(", query, ")" ;query = contexts-call | relations-call | contributions-call ;Grammar is type-directed and precedence follows table below. Because a comparison produces a Boolean, its result can participate in another Boolean equality or logical operation. HCL left associativity makes both expressions valid:
assert = true == false == falseassert = 1 < 2 == truePractical examples:
assert = trueassert = exists(contexts(rf.context.network))assert = length(contexts(rf.context.network)) >= 1assert = !exists(relations(aws.relation.subscribes-to))assert = ( exists(contexts(rf.context.network)) && length(contributions(aws.rule.s3-bucket-versioning)) == 0)This complete Pack verifies recursive Boolean results:
policy_pack "expression-results" { version = "0.1.0"}
policy "recursive-booleans" { target { rules = [aws.rule.subnet] }
assert = ( true == false == false && 1 < 2 == true )
message = "Recursive Boolean expressions must remain true."}exists and length each take exactly one query call. A query call cannot be
stored, compared directly, nested in another function, or passed as a general
collection. See Built-ins for exact signatures.
No implicit conversion exists. "3" == 3 is never true. In Policy source it
is rejected by static typing; in predicate evaluation mismatched runtime types
produce unknown.
From highest to lowest:
Binary operators are left-associative. Use parentheses when mixing comparisons
or Boolean operators. A chained comparison such as 1 < source.count < 5 is
invalid; write source.count > 1 && source.count < 5.
Parentheses do not add an expression node to the compiled RF artifact; they only control grouping.
where = (source.enabled == true) && (source.replicas >= 2)Fields such as version, description, message, kind, type,
provider constraints, and fact-match strategy use HCL constant evaluation
separately from predicate/assertion grammar.
Result must be known, non-null string in empty evaluation context. Therefore:
- quoted strings and static heredocs are accepted;
- parentheses and constant string conditionals are accepted;
- a pure
"${expression}"wrapper is accepted only when wrapped constant result is string; - variables and runtime traversals are unavailable;
- function calls are unavailable;
- multi-part interpolation is rejected.
For native .rf.hcl, acceptance is result-based: any HCL expression meeting
that rule is accepted, except a multi-part template. This constant-expression
surface is separate from closed where and assert grammars. In
.rf.json, static string fields are ordinary JSON strings.
description = "Production workload"
kind = true ? "resource" : "data"
message = <<-EOT Workloads must declare a runtime.EOTDirect literals are canonical and recommended. Dynamic templates are rejected:
description = "Workload for ${source.environment}"This complete Dialect demonstrates accepted constant string conditionals:
dialect "constant-example" { version = true ? "0.1.0" : "9.9.9"
provider "hashicorp/example" { version = true ? ">= 1.0.0" : "< 1.0.0" }}
concept "application" { description = true ? "A deployable application." : "Unused"}
rule "application" { match { kind = true ? "resource" : "data" type = true ? "example_application" : "other" }
as = concept.application}HCL JSON stores expression-valued fields in strings using "${...}":
{ "match": { "type": "example_service", "where": "${source.enabled == true}" }, "as": "${concept.application}"}Native .rf.hcl also accepts a pure "${expression}" wrapper for
full-expression fields and unwraps it to the enclosed value. Direct native syntax is
canonical:
where = source.enabled == trueBoolean and numeric JSON values can directly carry literal expressions. Raw
string fields such as message remain ordinary JSON strings. Rootform does
not parse arbitrary JSON strings as RF expressions.
In where and assert, Rootform language 0.1.0 rejects:
- arithmetic:
+,-,*,/,%; - unary minus;
- conditional expressions in
whereandassert; - multi-part templates and interpolation mixed with literal text;
- tuple, list, set, map, and object values outside dedicated list fields;
- comprehensions and
forexpressions; - splats;
- dynamic or string indexes;
- relative traversals;
- arbitrary function calls;
- expanded function arguments;
- bare architecture queries.
Examples:
where = source.enabledwhere = source.replicas >= -1where = source.cpu > 1.5assert = contexts(rf.context.network)assert = "yes"assert = length(contexts(rf.context.network))assert = length(contexts(rf.context.network)) + 1 > 1Depending on position, these produce INVALID_EXPRESSION,
PREDICATE_UNRESOLVED, or POLICY_INVALID.
Exceeded source shape fails compilation. Compiled Policy Pack has additional aggregate limits under Diagnostics and limits.