| title | Write a Policy Pack |
|---|---|
| description | Group portable Policies, link them to exact architecture semantics, and package them deterministically. |
A Policy Pack is an independent source unit. It owns a name, version, and Policies. This guide uses the public baseline Pack with the Azure commerce plan, which contains a Kubernetes cluster and a managed database. A Policy observes architectural facts; it never creates them. Exact RF Vocabulary and Dialect identities are derived when the Pack is linked.
baseline/
├── pack.rf.hcl
├── policies/
│ ├── cluster-network-context.rf.hcl
│ └── managed-database-network-context.rf.hcl
├── LICENSE
└── NOTICE
Rootform discovers .rf.hcl and .rf.json recursively. Exactly one
policy_pack declaration owns every top-level policy below this root.
policy_pack "baseline" {
version = "0.1.0"
}
policy "cluster-network-context" {
target {
concept = rf.concept.kubernetes-cluster
}
assert = (
exists(contexts(rf.context.network, rf.concept.virtual-network)) ||
exists(contexts(rf.context.network, rf.concept.subnet))
)
message = "Kubernetes clusters must belong to a network context."
}
policy "managed-database-network-context" {
target {
concept = rf.concept.managed-database
}
assert = (
exists(contexts(rf.context.network, rf.concept.virtual-network)) ||
exists(contexts(rf.context.network, rf.concept.subnet))
)
message = "Managed databases must belong to a network context."
}
These two Policy blocks reproduce the baseline source.
For the evidence model behind an assertion, read Policies over facts.
policy_pack "baseline" establishes source identity. Policy IDs use
owner-first syntax, for example baseline.policy.cluster-network-context.
Names use lowercase kebab case. Version is exact MAJOR.MINOR.PATCH.
No requires block exists. Policies use qualified references only. Linking
resolves each referenced owner and symbol against the Form, then
records exact versions and digests in the compiled Pack.
Each Policy has one target block. The baseline targets shared Concepts, so they can select matching Rules from different provider Dialects. The commerce walkthrough uses Azure. The target below is an AWS-only variant of the cluster Policy; keep its provider filters separate from the baseline used in this walkthrough.
target {
concept = rf.concept.kubernetes-cluster
rules = [aws.rule.eks-cluster]
dialects = ["aws"]
}
At least concept or rules is required. Values within each list are OR;
present dimensions combine with AND. dialects filters owner of applied Rule.
One selected Representation is evaluated once. Base Representation without
matching Concept or applied Rule is not selected.
From the Rootform repository root, run the Azure commerce plan against the
baseline Pack. Its saved plan pairs with the plan JSON and adds the verified
traversals behind these network contexts.
Plan inputs shows how to export both files from your own
project with terraform or tofu. Saved plans and plan JSON can contain
secrets in clear text; keep yours out of Git and public artifacts. Rootform
reads them locally and keeps sensitive values out of its outputs.
rootform run examples/playground/commerce-platform/head/plan.json \
--plan-file examples/playground/commerce-platform/head/plan.tfplan \
--no-serve -o analysis.json
rootform check analysis.json --policy-pack ./policy-packs/baseline --color always
rootform list policies --policy-pack ./policy-packs/baseline
rootform show policy baseline.policy.cluster-network-context \
--policy-pack ./policy-packs/baselinePolicy check completed
Policies 2 selected
Evaluations 2
Passed 2
Verdict PASSED
The check summary reports both selected targets passing and exits 0. A
violation exits 1; indeterminate evidence or no selected decision exits 3.
The latter two are not passes. list names both qualified Policies, while show prints the
target and assertion without evaluating it. If a context is indeterminate,
inspect the instance closure and confirm that the saved plan matches the JSON.
The local override lasts one command and leaves rootform.lock unchanged.
rootform check result |
Status | What to do |
|---|---|---|
PASSED |
0 |
All selected Policies passed. Confirm that at least one target was evaluated. |
VIOLATED |
1 |
Read the named target and Policy message, then explain that Policy. |
INDETERMINATE |
3 |
Inspect its closure reason; missing or unknown evidence cannot prove a pass. |
NO DECISION |
3 |
At least one selected Policy had no target, or no Policy was selected, and no violation or indeterminate result takes priority. Another Policy passing does not change this. Check the Pack target and selected plan stage. |
The check walkthrough shows violations, indeterminate closures, and no-target results on small plans.
Save the linked Pack against the Form when replay must use that
exact semantic selection. analysis.json came from the preceding run:
rootform compile policy-pack ./policy-packs/baseline --semantics analysis.json \
--output baseline.compiled.json
rootform check analysis.json --policy-pack baseline.compiled.json --color alwaysThe compile command prints the Pack, semantic-pin count and destination. The
check loads the Form without recompiling the plan and reports Policy outcomes
with the check exit status. The compiled artifact records the authored
content digest, linked digest, language version, and exact semantic identities.
A mismatch fails closed. When the project should retain the source Pack, use
rootform add policy-packs ./policy-packs/baseline from that project root and
commit the Pack source with rootform.lock.
Packaging is local and offline:
rootform package policy-packs ./policy-packs/baseline \
--to ./artifacts/policies \
--source-url https://example.com/team/policies \
--documentation-url https://example.com/team/policies/docs \
--licenses Apache-2.0The result names the Pack and local destination. Record the reviewed source
revision with --revision when your publication process requires that
provenance. Packaging itself sends nothing to a registry. Publication is
separate and generic:
rootform publish policy-packs ./artifacts/policies \
--to registry.example.com/team/policy-packsV0 has no mutable Policy Pack index. Existing version tag with different digest is rejected.
From the project root, add the published reference, then prepare its exact selection:
cd ./infra
rootform add policy-packs \
registry.example.com/team/policy-packs:policy-pack-baseline-0.1.0
rootform init . --locked --no-inputAfter exporting a plan for this project, evaluate the selected Pack with rootform check plan.json --locked.
The registry reference is illustrative; replace it with the published one you
reviewed. add records digests without hand editing the lock. Set
DOCKER_CONFIG before acquisition if the registry needs credentials. See
External content storage for paths.
See Policy Pack reference,
evaluation, and
compiled-policy-pack.schema.json.