SIGNALS Documentation
API Reference

Permission Registry

Contract for registering administrative permissions, dependency metadata, and layer-aware grouping.

Overview

App\Services\PermissionRegistry is the framework registry for administrative permissions used by roles, authorization setup, and permission-aware UI grouping.

Core binds it as a singleton in AppServiceProvider and seeds it from PermissionSeeder::permissions(). The shipped catalogue is large and still evolving, so this page documents the metadata contract, dependency behavior, and discovery helpers rather than reproducing the entire seeder inline.

Public surface

Method Purpose
register() Register one permission definition
registerMany() Register multiple definitions atomically
get() Return metadata for one permission or null
all() Return the full keyed metadata map
keys() Return the registered permission keys
grouped() Group permissions by domain group
byLayer() Filter permissions by authorisation layer
groupedByLayer() Group permissions by layer, then group
dependenciesFor() Resolve the recursive dependency closure
validate() Reject unknown permission keys with ValidationException
has() Check whether a permission key exists

This metadata registry does not publish a separate registered-value interface. Its current framework contract is the typed public surface on this page plus the documented registration shape and runtime behavior below.

Accepted registration shape

Each permission is identified by a unique dotted key such as settings.view, settings.manage, or webhooks.manage.

The accepted metadata shape is:

[
    'label' => 'View Settings',
    'description' => 'View system settings',
    'group' => 'Settings',
    'sub_group' => null, // optional
    'layer' => 'action', // optional, defaults to action
    'dependencies' => ['settings.access'], // optional
]

The current shipped metadata uses three layers:

  • area
  • action
  • field

This metadata shape is a PHPDoc and static-analysis contract. The registry does not perform runtime non-empty or type validation on the metadata fields: empty strings and dependency entries are stored unchanged. Missing required keys or incompatible values fail through ordinary PHP or downstream consumer behaviour. Runtime registration validation is limited to duplicate permission keys.

Identity, collisions, and dependency behavior

Permission identity is the permission key itself.

  • register() throws InvalidArgumentException when a key is already registered
  • registerMany() performs duplicate checks before mutating state, so collisions leave the registry unchanged

Dependencies are additive metadata and are expected to be a list of strings, but the registry does not validate that shape or require those strings to identify registered permissions.

Unknown dependency keys are accepted. When dependenciesFor() reaches one, the unknown key remains in the returned closure and traversal of that branch stops. Calling dependenciesFor() with an unknown starting key still returns an empty array.

Dependency cycles are also accepted. Resolution tracks keys it has already added, so traversal terminates rather than throwing. Because the starting key is not pre-seeded into that set, a cycle back to the root includes the starting permission key in the returned closure.

Grouping, ordering, and discovery guidance

The registry exposes two discovery views:

  • grouped() returns group => permissions
  • groupedByLayer() returns layer => group => permissions

That second shape is what the role and permissions editor uses to render area, action, and field sections.

The registry does not apply an extra sort pass. Group and permission order follow the order in which PermissionSeeder::permissions() registered them.

Representative shipped permissions include:

Permission Layer Group Current meaning
settings.access area Settings Entry gate for the settings area
settings.view action Settings Read system settings
settings.manage action Settings Update settings
costs.view field Global Field-level visibility for cost data
webhooks.manage action System Manage outbound webhooks

Use keys(), groupedByLayer(), and dependenciesFor() for discovery instead of copying the full seeder output into callers.

Lookup and failure behavior

  • get($key) returns the metadata array or null
  • has($key) is the non-throwing existence check
  • validate($permissions) throws ValidationException when any supplied key is unknown
  • dependenciesFor($key) returns [] for unknown keys and for keys without dependencies

The registry itself does not authorize anything. It publishes the canonical metadata and dependency graph that higher-level authorization flows consume.

Runtime consumption

Current consumers include:

  1. CreateRole and UpdateRole, which call validate() before persisting permission selections
  2. the Livewire role editor, which uses groupedByLayer() for the area/action/field UI
  3. PermissionSimulator, which resolves metadata and dependencies to explain effective grants

Worked example

The current core shape for a dependent permission looks like this:

$registry->register('settings.manage', [
    'label' => 'Manage Settings',
    'description' => 'Create, update, and delete system settings',
    'group' => 'Settings',
    'dependencies' => ['settings.view'],
]);

With the companion settings.view and settings.access definitions registered:

  • validate(['settings.manage']) succeeds
  • dependenciesFor('settings.manage') returns settings.view and settings.access
  • groupedByLayer()['action']['Settings'] includes settings.manage