# permits-lib Permits Lib provides a declarative way to describe Bukkit/Paper permission hierarchies using a Kotlin DSL. Once a tree is built you can hand it to `PermissionRegistry` (or the higher-level `MutationSession`) and the library will register/unregister the relevant `org.bukkit.permissions.Permission` instances and keep `PermissionAttachment`s in sync. ## Usage ```kotlin class ExamplePlugin : JavaPlugin() { private val permits = PermitsLib.session(this) override fun onEnable() { val tree = permissionTree("example") { node("command") { description = "Access to all example commands" defaultValue = PermissionDefault.OP node("reload") { description = "Allows /example reload (permission example.command.reload)" } // Link to a helper node defined elsewhere under the command branch: child("helper") // Link to a permission outside the current branch (must be fully-qualified): childAbsolute("example.tools.repair") node("cooldown") { description = "Allows /example cooldown tweaks" wildcard = false // opt-out if you do not want example.command.* to include it } } node("command.helper") { description = "Allows /example helper (referenced via child(\"helper\"))" } node("tools.repair") { description = "Allows /example tools repair (linked with childAbsolute(\"example.tools.repair\"))" } } permits.applyTree(tree) // The tree above materializes as permissions such as: // example.command, example.command.reload, example.command.helper, example.command.cooldown, // example.tools.repair, // plus the auto-generated example.command.* wildcard that references every child (visible if you // export to plugin.yml or inspect Bukkit's /permissions output). configureRuntimePermissions() } fun grantHelper(player: Player) { permits.attachments.grant(player, PermissionId.of("example.command.reload")) } private fun configureRuntimePermissions() { // Later in runtime you can mutate the previously applied structure without rebuilding it: permits.edit("example") { // Update an existing node and link it to new children node("command") { description = "Admins for every command path" node("debug") { description = "Allows /example debug" defaultValue = PermissionDefault.OP } } // Remove deprecated permissions entirely remove("command.cooldown") } } } ``` ### Procedural Edits with `MutablePermissionTree` If you prefer an imperative style before handing the structure to Bukkit, you can clone any existing tree, mutate it procedurally, and then apply the result: ```kotlin val baseTree = permissionTree("example") { node("command") { node("reload") } } val mutable = MutablePermissionTree.from(baseTree) mutable.node("command") { node("debug") { description = "Allows /example debug" defaultValue = PermissionDefault.OP } child("helper", value = false) // unlink helper if present } mutable.removeNode("command.legacy") permits.applyTree(mutable.build()) ``` The mutable API mirrors the DSL (`node`, `child`, `childAbsolute`, `removeNode`, `renameNode`, etc.) so you can stage edits procedurally before ever touching `MutationSession`. ### Concepts - **Permission tree** – immutable graph of `PermissionNode`s. Nodes specify description, default value, boolean children map, optional tags, and the `wildcard` flag (enabled by default) that makes the library create/update `namespace.path.*` aggregate permissions automatically. - **DSL** – `permissionTree("namespace") { ... }` ensures consistent prefixes and validation (no cycles). Top-level `node("command")` / `remove("command")` treat `command` as relative to that namespace, so you never include the namespace manually at the root. - **Nested nodes** – `node("command") { node("reload") { ... } }` automatically produces `namespace.command` and `namespace.command.reload` plus wires the parent/child relationship so you don't have to repeat the full id. - **Flexible references** – `child("reload")`, `node("command") { node("reload") { ... } }`, or even `node("command.reload")` inside `edit` all resolve to the same node; children are auto-created on first reference but you can demand explicit nodes by adding a `node` block later, and you can unlink specific children via `node("command") { removeNode("cooldown") }` and the entire subtree disappears. Nested `child(...)` calls are relative to the current node by default, while `childAbsolute(...)` now expects a fully-qualified permission ID (e.g., `example.tools.repair`) so you can also point at nodes in other namespaces. - **PermissionRegistry** – calculates a diff between snapshots and performs the minimum additions, removals, or updates via Bukkit's `PluginManager`. - **Wildcards** – with `wildcard = true`, the generated `namespace.command.*` child always exists and stays in sync so granting `example.command.*` automatically grants every nested node; set it to `false` to opt out for specific permissions. - **Mutable edits** – `permits.edit { ... }` clones the currently registered tree, lets you mutate nodes imperatively, re-validates, and only pushes the structural diff to Bukkit. - **AttachmentSynchronizer** – manages identity-based `PermissionAttachment`s and exposes high-level helpers (`grant`, `revoke`, `applyPatch`). - **MutationSession** – ties everything together for plugins that just want to push new trees and manage attachments without worrying about the lower-level services.