# Permission System ## Overview CleverAgents implements a namespace/project/plan/skill permission model with role bindings and a default-deny policy. An ``enforce_permission`` decorator is available for gating function calls; however, the decorator is **not yet wired** into CLI or service call sites — that integration is deferred to a future pass. **Server-only enforcement**: in local mode the permission system returns permissive defaults (all actions allowed). Server-side role evaluation uses the role bindings registered on the ``PermissionService``. ## Roles | Role | Description | | -------- | ---------------------------------------- | | `owner` | Full control — all actions on all scopes | | `admin` | Administrative access — all actions | | `editor` | Read, write, and execute | | `viewer` | Read-only access | OWNER and ADMIN currently grant the same set of actions. The distinction is intentional — OWNER designates the resource creator or namespace owner, while ADMIN is a delegated administrative role. Future server-mode enforcement may differentiate them (e.g. ownership transfer, namespace deletion) without requiring a schema change. ## Role → Action Matrix | Action | Owner | Admin | Editor | Viewer | | --------- | :---: | :---: | :----: | :----: | | `read` | yes | yes | yes | yes | | `write` | yes | yes | yes | — | | `execute` | yes | yes | yes | — | | `delete` | yes | yes | — | — | | `admin` | yes | yes | — | — | ## Scopes Permissions can be scoped to: - **Namespace** — top-level organisational boundary - **Project** — a project within a namespace - **Plan** — an execution plan within a project - **Skill** — a skill registered in the system ### Scope Hierarchy (Known Limitation) Scopes are currently evaluated with **exact match** semantics. A binding at `namespace` scope does not automatically cascade to `project`, `plan`, or `skill` scopes within that namespace. Hierarchical scope resolution (namespace > project > plan > skill) is deferred to a future server-mode release. ## Role Bindings A `RoleBinding` associates a *principal* (user or service identifier) with a `PermissionRole` at a specific `PermissionScope` and scope ID. Duplicate bindings (same principal, role, scope, and scope_id) are rejected by `add_binding()`. ```python from cleveragents.domain.models.core.permission import ( PermissionRole, PermissionScope, RoleBinding, ) binding = RoleBinding( principal="alice", role=PermissionRole.EDITOR, scope=PermissionScope.PROJECT, scope_id="my-project", ) ``` ## Permission Policy The `PermissionPolicy` model controls the default-deny behaviour and optional allow-overrides: ```python from cleveragents.domain.models.core.permission import ( PermissionPolicy, RoleBinding, ) policy = PermissionPolicy( default_deny=True, allow_overrides=[binding], ) ``` ## Permission Service `PermissionService` is the entry point for checking permissions: ```python from cleveragents.application.services.permission_service import ( PermissionService, ) from cleveragents.domain.models.core.permission import ( PermissionAction, PermissionScope, ) svc = PermissionService() check = svc.check_permission( principal="alice", action=PermissionAction.WRITE, scope=PermissionScope.PROJECT, scope_id="my-project", ) print(check.result) # True in local mode ``` ### Module-Level Default Service A module-level default ``PermissionService`` is available so that the ``enforce_permission`` decorator (and other call sites) share a single service instance with pre-registered bindings: ```python from cleveragents.application.services.permission_service import ( get_default_permission_service, set_default_permission_service, PermissionService, ) # During application startup: svc = PermissionService(bindings=[...]) set_default_permission_service(svc) # Later — the decorator (and any code that calls # get_default_permission_service()) will use this instance. ``` ## Enforcement Decorator The `enforce_permission` decorator is available for gating function calls. It is not yet applied to any CLI or service entry point — call sites will be wired in a future integration pass: ```python from cleveragents.application.services.permission_service import ( enforce_permission, ) from cleveragents.domain.models.core.permission import ( PermissionAction, PermissionScope, ) @enforce_permission( PermissionAction.WRITE, PermissionScope.PROJECT, "my-project", ) def create_resource(): ... ``` In local mode the decorator is a no-op. In server mode it raises `PermissionError` when the check fails. When no explicit `service` is passed, the decorator uses the module-level default service (see above). ## Local Mode Behaviour When `CLEVERAGENTS_SERVER_MODE` is unset (or not `true`), the system operates in **local mode**: - `PermissionService.is_local_mode()` returns `True` - All permission checks return `result=True` - `get_role_bindings()` returns a synthetic OWNER binding - The `DEFAULT_LOCAL_ROLE_MAPPING` maps `"*"` → `PermissionRole.OWNER` ## Audit Logging Permission denials in server mode are logged as `permission_denied` events via structlog at the WARNING level. Binding additions and removals are logged as `binding_added` / `binding_removed` at INFO level.