# 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.
