---
title: "Plan and review role bindings"
description: "Turn acls.json into plan.json and report.txt, review mapped, unmapped and rejected ACLs, and edit the plan safely before applying it."
url: https://monedula.dev/flock/docs/acl-rbac-converter/how-to/plan-and-review/
---

`plan` turns the ACL snapshot from [`extract`](/flock/docs/acl-rbac-converter/how-to/extract-acls/) into the role bindings to create. It writes `plan.json`, which every later step reads, and `report.txt`, which shows what each ACL became and why; reading the report is the main review point of a migration, before anything changes.

## Before you start

- A `scopes.yaml` with your cluster IDs. See [Find the cluster IDs for scopes.yaml](/flock/docs/acl-rbac-converter/how-to/find-cluster-ids/).
- Optional: a `rules.yaml` that overrides mapping rules, and a `principals.yaml` that maps source principals to the forms MDS expects. [Config files](/flock/docs/acl-rbac-converter/reference/config-files/) describes both.

## Steps

### 1. Run plan

```bash
monedula-acl-rbac plan \
  --acls runs/2026-05-21T10-00-00Z/acls.json \
  --rules rules.yaml \
  --principals principals.yaml \
  --scopes scopes.yaml \
  --out runs/2026-05-21T10-00-00Z/plan.json
```

`plan` writes three files into the run directory: `plan.json`, its checksum `plan.json.sha256`, and `report.txt`. It prints a one-line summary to stderr, such as `plan: 1 binding(s), 0 unmapped, 0 rejected, 0 warning(s)`. When the input has DENY ACLs, the line also counts the results of the DENY analysis.

### 2. Read report.txt

The report has one section per list of the plan, and two more:

- **Bindings**: the role bindings to create, marked `[CREATE]`, or `[SKIP_EXISTS]` when an existing binding already covers them. Each lists its role, principal, cluster scope, resource patterns and the `source_acl_ids` of the ACLs it replaces.
- **Unmapped**: ACLs the rules could not translate, with a reason: `NO_RULE_MATCH`, `HOST_RESTRICTED` or `CONFLICTING_EXISTING_BINDING`.
- **Rejected**: ACLs the tool will not convert. DENY ACLs are rejected with `DENY_PERMISSION`.
- **DENY analysis**: for each DENY ACL, `SAFE_TO_REMOVE`, `WOULD_GRANT_ACCESS` or `UNKNOWN`; [Remove DENY ACLs](/flock/docs/acl-rbac-converter/how-to/remove-deny-acls/) explains the classes.
- **Warnings**: things to check, such as `PARTIAL_RULE_COVERAGE`, when the chosen roles do not grant every operation of the source ACLs, or `MTLS_DN_PASS_THROUGH` for a principal that looks like a TLS distinguished name and is passed through unmapped.

This is a binding from the [text-dump](https://github.com/monedula-dev/monedula-acl-rbac-converter/tree/main/examples/text-dump) example's [report](https://github.com/monedula-dev/monedula-acl-rbac-converter/blob/main/examples/text-dump/expected/report.txt), which also shows a rejected DENY:

```text
  [CREATE] DeveloperRead for User:svc-analytics -> kafka=lkc-demo-1 on:
    - Topic:events. (PREFIXED)
    source_acl_ids: [11 12]
```

[How ACLs map to RBAC roles](/flock/docs/acl-rbac-converter/concepts/how-acls-map-to-rbac/) explains why an ACL ends up in each list.

### 3. Resolve unmapped and rejected ACLs

`plan` exits 3 when the plan has any unmapped or rejected ACL. It still writes `plan.json` and `report.txt`, so you can read them. Then either fix the input and plan again, or accept the result:

- Add a rule to `rules.yaml` for operations the defaults do not cover, or map principals in `principals.yaml`, and run `plan` again.
- Accept ACLs that stay unconverted, for example DENY ACLs, which never convert:

  ```bash
  monedula-acl-rbac plan ... --allow-unmapped --allow-rejected
  ```

The two flags do not convert anything. They let `plan` exit 0 with those ACLs recorded as unmapped or rejected, and the ACLs stay in place on the cluster: `delete-acls` only deletes ACLs that a verified binding replaces.

### 4. Share the report

To view the report of a run again, or in another format, use `report`:

```bash
monedula-acl-rbac report --plan runs/2026-05-21T10-00-00Z/plan.json --format markdown
```

`--format` takes `text`, `markdown` or `json` ([report.v1.json](/flock/docs/acl-rbac-converter/reference/formats/report-v1/)), and `--out` writes to a file instead of stdout. `report` exits 0 even when the plan has unmapped or rejected ACLs; add `--strict` to make it exit 3 instead, for example as a gate in a CI job.

### 5. Edit the plan before applying, if you need to

You can edit `plan.json` by hand, for example to drop a binding you are not ready to apply or to fix a principal. `apply` then refuses the plan with exit code 5, because its checksum no longer matches. After editing, re-validate the plan to refresh the checksum:

```bash
monedula-acl-rbac plan --revalidate runs/2026-05-21T10-00-00Z/plan.json
```

`--revalidate` checks the structure of the edited file, its schema version and the fields of every binding, and rewrites `plan.json.sha256`.

<Callout type="warning" title="Revalidation trusts your edit">
`--revalidate` does not derive the plan again from `acls.json` and the rules, and does not repeat the DENY or unmapped analysis. If you add a binding that the rules would never produce, you get exactly that binding back, with a fresh checksum that `apply`, `verify` and the delete commands trust. Review the diff of your edit, not only the checksum.
</Callout>

## Check it worked

The `plan` line of `monedula-acl-rbac status runs/2026-05-21T10-00-00Z` shows the number of bindings, unmapped and rejected ACLs, and `checksum_ok=true` once the checksum matches the file.

## Related

- [Apply role bindings to MDS](/flock/docs/acl-rbac-converter/how-to/apply-to-mds/) is the next step, or [Hand bindings to a CD pipeline](/flock/docs/acl-rbac-converter/how-to/emit-for-a-cd-pipeline/) without MDS access.
- The [plan command reference](/flock/docs/acl-rbac-converter/reference/cli/plan/) lists every flag.