Skip to content

RF has two equivalent source surfaces:

SuffixEncodingIntended use
.rf.hclHCL native syntaxHuman-authored Dialects and Policy Packs
.rf.jsonHCL JSON syntaxGenerated Dialects and Policy Packs

Both suffixes may coexist inside one source root. Files and directories organize source for people only. They do not create namespaces, imports, or evaluation order.

Source discovery

Rootform recursively walks each requested source root.

PathBehavior
Regular file ending in .rf.hcl or .rf.jsonParsed
Other file suffixIgnored
Dot-prefixed file or directoryIgnored
Symlink resolving to regular file inside source rootParsed
Symlink escaping source rootRejected with SYMLINK_OUTSIDE_ROOT
Matching file that cannot be read or is not regularRejected

Discovery and diagnostic ordering are deterministic. Declaration resolution is independent of filename and file order. Only composition members retain authored order.

Source units

One source root has exactly one responsibility.

UnitRequired manifestOther allowed top-level blocks
DialectExactly one dialectconcept, context, relation, rule
Policy PackExactly one policy_packpolicy

RF Vocabulary is embedded by Rootform and cannot be authored as a source unit. A Dialect cannot contain policy. A Policy Pack cannot contain Dialect blocks. RF has no import, include, module, requires, or cross-Dialect dependency block.

Structural grammar

This grammar describes RF block structure. Brackets mean optional, braces mean zero or more, and a trailing + means one or more. Commas and semicolons are grammar notation, not RF tokens. Attribute order and block order are not significant except for member order.

EBNF
dialect-unit = dialect-block,
{ concept-block | context-definition |
relation-definition | rule-block } ;
dialect-block = "dialect", label, "{",
version-attribute,
{ provider-block },
"}" ;
provider-block = "provider", provider-source, "{",
version-constraint-attribute,
"}" ;
concept-block = "concept", label, "{", [ description-attribute ], "}" ;
context-definition = "context", label, "{", [ description-attribute ], "}" ;
relation-definition
= "relation", label, "{", [ description-attribute ], "}" ;
rule-block = "rule", label, "{",
match-block,
[ as-attribute ],
[ identity-block ],
[ endpoint-block ],
{ context-emission | relation-emission |
contribution-emission },
[ composition-block ],
"}" ;
match-block = "match", "{",
[ kind-attribute ],
type-attribute,
[ where-attribute ],
"}" ;
identity-block = "identity", "{",
attributes-attribute,
[ scope-attribute ],
"}" ;
endpoint-block = "endpoint", "{",
attributes-attribute,
"}" ;
context-emission = ( "context", label | "context" ), "{",
[ as-attribute ],
to-attribute,
via-attribute,
on-null-attribute,
on-empty-attribute,
[ external-attribute ],
[ disclose-attribute ],
[ prefix-attribute ],
[ fact-match-block ],
"}" ;
relation-emission = ( "relation", label | "relation" ), "{",
[ as-attribute ],
to-attribute,
via-attribute,
on-null-attribute,
on-empty-attribute,
[ external-attribute ],
[ disclose-attribute ],
[ prefix-attribute ],
[ fact-match-block ],
"}" ;
contribution-emission
= "contribution", "{",
to-attribute,
via-attribute,
on-null-attribute,
on-empty-attribute,
[ external-attribute ],
[ disclose-attribute ],
[ prefix-attribute ],
[ fact-match-block ],
"}" ;
fact-match-block = "match", "{",
by-attribute,
strategy-attribute,
"}" ;
composition-block = "composition", "{", member-block+, "}" ;
member-block = "member", label, "{",
via-attribute,
match-block,
"}" ;
policy-pack-unit = policy-pack-block, { policy-block } ;
policy-pack-block = "policy_pack", label, "{",
version-attribute,
"}" ;
policy-block = "policy", label, "{",
target-block,
assert-attribute,
message-attribute,
"}" ;
target-block = "target", "{",
[ concept-attribute ],
[ rules-attribute ],
[ dialects-attribute ],
"}" ;

Detailed pages define every attribute type, default, exclusivity rule, and runtime effect.

Native syntax

dialect.rf.hcl RF
dialect "example" {
version = "0.1.0"
provider "hashicorp/example" {
version = ">= 1.0.0, < 2.0.0"
}
}

Source text is UTF-8. Native RF follows HCL native lexical rules:

  • spaces and horizontal tabs act as whitespace where the grammar allows them. Newlines are distinct: they separate body attributes and end line comments, but HCL ignores them inside selected expression forms, including parenthesized and collection expressions;
  • line comments use # or //;
  • block comments use /* ... */;
  • string literals use quotes or static heredocs;
  • block labels are conventionally quoted; a bare HCL identifier is also accepted when resulting label satisfies RF label grammar;
  • rootform fmt writes canonical layout.

A field documented as a static string is evaluated in an empty HCL context. Result must be known, non-null string. Variables and functions are unavailable; multi-part interpolated templates are rejected. Constant string conditionals are accepted, although direct literals are canonical and recommended. Expression fields accept only RF subset documented under Expressions.

JSON syntax

HCL JSON represents labeled blocks as nested objects. This complete Dialect is equivalent to native RF:

dialect.rf.json JSON
{
"dialect": {
"example": {
"version": "0.1.0",
"provider": {
"hashicorp/example": {
"version": ">= 1.0.0, < 2.0.0"
}
}
}
},
"concept": {
"network": {
"description": "An example network."
}
},
"rule": {
"network": {
"match": {
"kind": "resource",
"type": "example_network"
},
"as": "${concept.network}"
}
}
}

Expression-valued JSON strings use HCL JSON expression carrier "${...}". Static string fields remain ordinary JSON strings in .rf.json and are never reparsed as expressions.

A Policy Pack manifest and its Policies are sibling top-level blocks:

pack.rf.json JSON
{
"policy_pack": {
"baseline": {
"version": "0.1.0"
}
},
"policy": {
"network-context": {
"target": {
"concept": "${rf.concept.subnet}",
"rules": [
"${aws.rule.subnet}"
],
"dialects": [
"aws"
]
},
"assert": "${exists(contexts(rf.context.network, rf.concept.virtual-network))}",
"message": "Subnets must declare their virtual network."
}
}
}

For repeated blocks with the same labels, HCL JSON uses arrays at the repeated body position. HCL JSON has no comment tokens; a "//" property carries comment text where HCL JSON mapping allows it. Generated authors should follow that mapping rather than translating native punctuation mechanically.

Identifiers

Most RF labels use this grammar:

EBNF
identifier = lower, { lower | digit | "-" }, with no trailing or doubled "-" ;
lower = "a" ... "z" ;
digit = "0" ... "9" ;

Equivalent regular expression:

Regex
[a-z][a-z0-9]*(?:-[a-z0-9]+)*

Identifiers are 1 to 64 UTF-8 bytes and ASCII lowercase kebab case. This applies to Dialect, definition, Rule, composition member, Policy Pack, and Policy labels. Provider source labels follow their own slash-separated grammar. Traversal attributes follow their own adapter-name grammar.

rf is reserved as the embedded vocabulary owner and cannot be a Dialect label.

Versions

Dialect and Policy Pack version use exact three-component decimal syntax:

EBNF
version = component, ".", component, ".", component ;

Each component is non-negative and has no leading zero unless it is exactly 0. Pre-release identifiers, build metadata, and ranges are invalid.

AcceptedRejected
0.1.0v0.1.0
2.0.142.0
10.4.001.4.0
1.0.0-beta.1

Provider version is a constraint string, not this exact-version field. See Provider block.

Source bounds

ValueBound
Structural nestingAt most 10,000 levels
RF identifier1 to 64 bytes
Optional definition descriptionAt most 1,024 UTF-8 bytes; empty is allowed
Required Policy message1 to 1,024 UTF-8 bytes
Expression sourceAt most 4,096 bytes

Additional compiled and evaluation bounds appear under Diagnostics and limits.

Rejected example

invalid-version.rf.hcl RF
dialect "example" {
version = "0.1"
}

version is required to use exact MAJOR.MINOR.PATCH, so this source produces INVALID_VALUE.