# Write a Policy Pack

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

```tree title="Policy Pack source"
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.

```rf title="policy-packs/baseline/pack.rf.hcl"
policy_pack "baseline" {
  version = "0.1.0"
}
```

```rf title="policy-packs/baseline/policies/cluster-network-context.rf.hcl"
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."
}
```

```rf title="policy-packs/baseline/policies/managed-database-network-context.rf.hcl"
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](https://docs.rootform.dev/language/learn/policies/).

<!-- rootform:steps -->

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

```rf title="AWS-only target variant"
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
supplies the traversals needed to decide these network contexts.
[Plan inputs](https://docs.rootform.dev/inputs/plans/) 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.

<!-- docs-check:docs-language-write-policy-pack-1 -->
```sh
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
```

<!-- docs-output:docs-language-write-policy-pack-1 -->
```text title="Passing result, excerpt"
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](https://docs.rootform.dev/guides/check-architecture/)
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:

<!-- docs-check:docs-language-write-policy-pack-2 -->
```sh
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:

<!-- docs-check:docs-language-write-policy-pack-3 -->
```sh
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:

<!-- docs-check:docs-language-write-policy-pack-4 -->
```sh
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:

<!-- docs-check:policy-authoring-add-published -->
```sh
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](https://docs.rootform.dev/reference/storage/) for paths.

<!-- rootform:endsteps -->

See [Policy Pack reference](https://docs.rootform.dev/language/reference/policy-packs/),
[evaluation](https://docs.rootform.dev/language/reference/evaluation/), and
[`compiled-policy-pack.schema.json`](https://github.com/rootform-dev/rootform/blob/dev/schemas/compiled-policy-pack.schema.json).
