---
title: "Safety guarantees"
description: "The guarantees monedula-acl-rbac makes: no mutation without confirmation, script-only deletion, no dropped ACLs, no widened scope, stale plans refused."
url: https://monedula.dev/flock/docs/acl-rbac-converter/concepts/safety-guarantees/
---
A permission that disappears breaks production; one that grows too wide opens it. Each guarantee below is enforced by the tool and closes one way for either to happen silently.

## Nothing changes without confirmation

A command that changes external state does nothing unless you pass `--confirm` or type `yes` at an interactive prompt. Outside a terminal, `--confirm` is the only way. `apply --dry-run` is the explicit preview: it reads MDS, records every call it would make and writes nothing. Every other command only reads, or writes local files such as the run directory's artifacts.

## Deletion happens only through scripts

The tool never runs a destructive Kafka call itself. `delete-acls` and `delete-deny-acls` write a script that you can read, edit or abandon, and the matching rollback script at the same time; to automate deletion, your pipeline runs the generated script after review. `delete-acls` also needs `--i-understand-this-is-destructive` before it writes anything.

`apply` only creates role bindings, and the tool never deletes or modifies them: you roll back `apply` with the Confluent CLI or the MDS API, as [Recover from a failed run](/flock/docs/acl-rbac-converter/how-to/recover-from-a-failed-run/) shows.

## No ACL is silently dropped

Every input ACL appears in exactly one list of the plan, `bindings`, `unmapped` or `rejected`, with a reason for each one not converted ([How ACLs map to RBAC roles](/flock/docs/acl-rbac-converter/concepts/how-acls-map-to-rbac/#where-every-acl-ends-up)). `plan` exits 3 until you resolve or accept the unconverted ones, and a role that does not grant every operation of its source ACLs raises a `PARTIAL_RULE_COVERAGE` warning.

## Scope is never widened

A binding keeps the resource pattern of the ACLs it replaces, as [the mapping rules](/flock/docs/acl-rbac-converter/concepts/how-acls-map-to-rbac/#scope-is-never-widened) describe. An ACL that could only be converted by widening it, such as one limited to a host, is left unmapped instead.

## Stale plans are refused

Every artifact that later steps trust is fingerprinted:

- `plan` writes `plan.json.sha256` next to `plan.json`. `apply`, `verify`, `delete-acls` and `delete-deny-acls` check it and refuse a plan that changed since, with exit code 5.
- The generated deletion scripts hash `plan.json` again when they run, and refuse to run on a mismatch. They fail closed: if `plan.json` cannot be hashed at all, because neither `sha256sum` nor `shasum` exists or the file is not readable there, they refuse too. The only escape hatch is `MONEDULA_SKIP_PLAN_HASH_CHECK=1`, for a script you run on purpose where its run directory is not present, such as inside a broker container.
- `plan` stamps the SHA-256 of `acls.json` into the plan. The delete commands select the ACLs to remove from `acls.json` by `id`, so they refuse an `acls.json` that changed after planning.
- `verify` stamps the plan's SHA-256 into `verify.json`. The delete commands refuse a `verify.json` that checked a different plan, so an old verification cannot vouch for a regenerated plan: after a plan change, apply and verify again.

To apply a plan you edited by hand, `plan --revalidate` checks its structure and refreshes the checksum. It does not run the mapping or the DENY analysis again, so the new checksum vouches only for the plan you reviewed ([Plan and review role bindings](/flock/docs/acl-rbac-converter/how-to/plan-and-review/)).

## Effective access, verified before deletion

By default, `verify` asks MDS, for each source ACL, whether the original access is now granted, so a binding that exists but grants nothing shows up as `EFFECTIVE_MISSING` ([Verify effective access](/flock/docs/acl-rbac-converter/how-to/verify-effective-access/)).

`delete-acls` only deletes source ACLs whose result is `EFFECTIVE_OK`, or `BINDING_EXISTS` when `verify.json` comes from `--mode bindings-exist`, which only checks that the binding rows exist. Results that MDS could not answer, `EFFECTIVE_UNKNOWN`, block deletion too, unless you ran `verify --accept-unknown-verify` for an MDS that lacks the lookup endpoint. That override is opt-in, recorded in the `detail` field of every result it changed, and never weakens the block on `EFFECTIVE_MISSING`.

## DENY ACLs get extra care

DENY ACLs are never converted. Removing one can grant access, so it takes a separate command, `delete-deny-acls`, which needs `--i-understand-this-may-grant-access` and a target principal, and refuses unless every `verify` result is confirmed. Its script checks each DENY against MDS again at the moment it runs, and skips a DENY whose removal would now grant access, with no override. DENY ACLs for a wildcard principal are never removable by the tool, and the script never puts credentials on a command line or in the script itself ([Remove DENY ACLs](/flock/docs/acl-rbac-converter/how-to/remove-deny-acls/)).

## One apply per run directory

`apply` takes a lock file in its run directory, so two `apply` runs against that directory cannot race; a stale lock left by a dead process must be cleared with `--force-unlock`. The lock covers only `apply`. It does not coordinate who runs the generated scripts, or two operators using different run directories against the same MDS: plan for one operator per cluster during a migration window.

## Related

- [Delete the converted ACLs](/flock/docs/acl-rbac-converter/how-to/delete-converted-acls/) shows the deletion gates in use.
- [Limitations and API stability](/flock/docs/acl-rbac-converter/concepts/limitations/) lists what the tool does not do.
- The [CLI overview](/flock/docs/acl-rbac-converter/reference/cli/) maps each refusal to its exit code.