Implemented namespace/project/plan/skill permission model with role bindings (owner/admin/editor/viewer) and default deny policy. Added enforcement hooks at CLI/service boundaries that are server-only; local mode returns permissive defaults. Includes role enums, permission check service, role matrix documentation. Includes Behave BDD scenarios, Robot integration tests, ASV benchmarks, and reference documentation. ISSUES CLOSED: #344
5.4 KiB
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().
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:
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:
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:
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:
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()returnsTrue- All permission checks return
result=True get_role_bindings()returns a synthetic OWNER binding- The
DEFAULT_LOCAL_ROLE_MAPPINGmaps"*"→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.