Skip to content

Latest commit

 

History

History
220 lines (173 loc) · 8.14 KB

write-policy-pack.md

File metadata and controls

220 lines (173 loc) · 8.14 KB
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.

Start with one source root

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.

Name and version the Policy Pack

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.

Define target

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.

Evaluate locally

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/baseline
Policy 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 always

The 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.

Package and publish a Policy Pack

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.0

The 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-packs

V0 has no mutable Policy Pack index. Existing version tag with different digest is rejected.

Use published Policy Pack

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-input

After 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.