# 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 import net.hareworks.permits_lib.domain.NodeRegistration class ExamplePlugin : JavaPlugin() { private val permits = PermitsLib.session(this) override fun onEnable() { val tree = permissionTree("example") { node("command", NodeRegistration.STRUCTURAL) { description = "Access to all example commands" defaultValue = PermissionDefault.OP wildcard = true // create example.command.* node("reload", NodeRegistration.PERMISSION) { 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", NodeRegistration.PERMISSION) { description = "Allows /example cooldown tweaks" wildcard = false // keep cooldown out of example.command.* } } node("command.helper", NodeRegistration.PERMISSION) { description = "Allows /example helper (referenced via child(\"helper\"))" } node("tools.repair", NodeRegistration.PERMISSION) { 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 (command opted in while cooldown did not). // 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", NodeRegistration.STRUCTURAL) { description = "Admins for every command path" wildcard = true node("debug", NodeRegistration.PERMISSION) { description = "Allows /example debug" defaultValue = PermissionDefault.OP wildcard = true } } // Remove deprecated permissions entirely removeNode("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", NodeRegistration.STRUCTURAL) { wildcard = true node("reload", NodeRegistration.PERMISSION) } } val mutable = MutablePermissionTree.from(baseTree) mutable.node("command", NodeRegistration.STRUCTURAL) { wildcard = true node("debug", NodeRegistration.PERMISSION) { description = "Allows /example debug" defaultValue = PermissionDefault.OP wildcard = true } 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, and the `wildcard` flag (disabled by default) that, when enabled per node, keeps `namespace.path.*` aggregate permissions in sync automatically. - **DSL** – `permissionTree("namespace") { ... }` ensures consistent prefixes and validation (no cycles). Every `node("command", NodeRegistration.PERMISSION)` (or `.STRUCTURAL`) is relative to that namespace, so you never include the namespace manually at the root. - **Nested nodes** – `node("command", NodeRegistration.STRUCTURAL) { node("reload", NodeRegistration.PERMISSION) { ... } }` 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", NodeRegistration.STRUCTURAL) { node("reload", NodeRegistration.PERMISSION) { ... } }`, or even `node("command.reload", NodeRegistration.PERMISSION)` 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", NodeRegistration.STRUCTURAL) { removeNode("cooldown") }` and the entire subtree disappears. - **Node registration** – `NodeRegistration.PERMISSION` materializes the node as a Bukkit permission, while `NodeRegistration.STRUCTURAL` keeps it purely for grouping (still participates in wildcard aggregation) so you can avoid ambiguous intermediate permissions like `hoge.command`. 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** – disabled by default; opt in by setting `wildcard = true` on any permission you want pulled into its parent `namespace.command.*`. Enabled nodes automatically add their wildcard descendants (e.g., `example.command.debug.*`) so granting a parent wildcard cascades through the tree. - **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.