Feature: Permission system — role-based access control As a CleverAgents operator I want a namespace/project/plan/skill permission model So that access to resources is controlled by role bindings with default deny # ── Role enum values ───────────────────────────────────────────── Scenario: PermissionRole enum contains the expected members Given the PermissionRole enum Then the enum should contain "owner" And the enum should contain "admin" And the enum should contain "editor" And the enum should contain "viewer" Scenario: PermissionScope enum contains the expected members Given the PermissionScope enum Then the enum should contain "namespace" And the enum should contain "project" And the enum should contain "plan" And the enum should contain "skill" Scenario: PermissionAction enum contains the expected members Given the PermissionAction enum Then the enum should contain "read" And the enum should contain "write" And the enum should contain "execute" And the enum should contain "delete" And the enum should contain "admin" # ── ROLE_PERMISSIONS constant ──────────────────────────────────── Scenario: OWNER role has all actions Given the ROLE_PERMISSIONS constant Then the "owner" role should have 5 allowed actions Scenario: ADMIN role has all actions Given the ROLE_PERMISSIONS constant Then the "admin" role should have 5 allowed actions Scenario: EDITOR role has read, write, and execute Given the ROLE_PERMISSIONS constant Then the "editor" role should have 3 allowed actions Scenario: VIEWER role has only read Given the ROLE_PERMISSIONS constant Then the "viewer" role should have 1 allowed actions # ── RoleBinding model ──────────────────────────────────────────── Scenario: Create a valid RoleBinding Given a role binding for principal "alice" with role "editor" on "project" scope "proj-1" Then the binding principal should be "alice" And the binding role should be "editor" And the binding scope should be "project" And the binding scope_id should be "proj-1" # ── PermissionPolicy model ────────────────────────────────────── Scenario: Default policy has default_deny enabled Given a default PermissionPolicy Then default_deny should be true And allow_overrides should be empty Scenario: Policy with allow overrides Given a PermissionPolicy with an override for "bob" as "admin" on "namespace" scope "ns-1" Then the policy should have 1 allow override # ── Local mode permission check ───────────────────────────────── Scenario: Local mode always permits actions Given a PermissionService in local mode When I check permission for "alice" to "write" on "project" scope "proj-1" Then the permission check result should be true And the reason should contain "local mode" Scenario: Local mode permits admin action Given a PermissionService in local mode When I check permission for "alice" to "admin" on "namespace" scope "ns-1" Then the permission check result should be true Scenario: Local mode permits delete action Given a PermissionService in local mode When I check permission for "bob" to "delete" on "plan" scope "plan-42" Then the permission check result should be true # ── Server mode — default deny ────────────────────────────────── Scenario: Server mode denies action when no binding exists Given a PermissionService in server mode with no bindings When I check permission for "alice" to "write" on "project" scope "proj-1" Then the permission check result should be false And the reason should contain "denied by default-deny" Scenario: Server mode denies action for missing role Given a PermissionService in server mode with no bindings When I check permission for "unknown-user" to "read" on "skill" scope "sk-99" Then the permission check result should be false # ── Server mode — role binding grants ─────────────────────────── Scenario: Server mode allows action when binding grants the role Given a PermissionService in server mode And a role binding for principal "alice" with role "editor" on "project" scope "proj-1" When I check permission for "alice" to "read" on "project" scope "proj-1" Then the permission check result should be true And the reason should contain "allowed by role" Scenario: Server mode denies action not in role permissions Given a PermissionService in server mode And a role binding for principal "alice" with role "viewer" on "project" scope "proj-1" When I check permission for "alice" to "write" on "project" scope "proj-1" Then the permission check result should be false # ── Server mode — allow overrides ─────────────────────────────── Scenario: Allow override grants access despite no regular binding Given a PermissionService in server mode with no bindings And a policy override for "bob" as "admin" on "namespace" scope "ns-1" When I check permission for "bob" to "admin" on "namespace" scope "ns-1" Then the permission check result should be true And the reason should contain "override" # ── Default local role mapping ────────────────────────────────── Scenario: DEFAULT_LOCAL_ROLE_MAPPING grants OWNER to wildcard Given the DEFAULT_LOCAL_ROLE_MAPPING constant Then the wildcard entry should map to "owner" # ── PermissionCheck model ─────────────────────────────────────── Scenario: PermissionCheck captures all fields Given a PermissionCheck for "carol" action "execute" scope "plan" scope_id "plan-7" result true reason "ok" Then the check principal should be "carol" And the check action should be "execute" And the check scope should be "plan" And the check scope_id should be "plan-7" And the check result should be true And the check reason should be "ok" # ── enforce_permission decorator ──────────────────────────────── Scenario: enforce_permission decorator allows in local mode Given a function decorated with enforce_permission for "write" on "project" scope "proj-1" When I call the decorated function in local mode Then it should succeed without raising Scenario: enforce_permission decorator raises in server mode when denied Given a function decorated with enforce_permission for "delete" on "namespace" scope "ns-1" When I call the decorated function in server mode with no bindings Then it should raise a PermissionError # ── Binding management ────────────────────────────────────────── Scenario: Add and remove bindings on the service Given a PermissionService in server mode with no bindings And I add a binding for "dave" as "editor" on "skill" scope "sk-1" When I check permission for "dave" to "write" on "skill" scope "sk-1" Then the permission check result should be true When I remove the binding for "dave" as "editor" on "skill" scope "sk-1" And I check permission for "dave" to "write" on "skill" scope "sk-1" Then the permission check result should be false Scenario: Remove non-existent binding returns false Given a PermissionService in server mode with no bindings When I try to remove a binding for "ghost" as "viewer" on "plan" scope "p-0" Then the remove result should be false # ── get_role_bindings ─────────────────────────────────────────── Scenario: get_role_bindings in local mode returns synthetic OWNER binding Given a PermissionService in local mode When I get role bindings for "anyone" on "project" scope "proj-1" Then I should receive 1 binding And the first binding role should be "owner" Scenario: get_role_bindings in server mode returns matching bindings Given a PermissionService in server mode with no bindings And I add a binding for "eve" as "viewer" on "project" scope "proj-2" And I add a binding for "eve" as "editor" on "project" scope "proj-2" When I get role bindings for "eve" on "project" scope "proj-2" Then I should receive 2 bindings # ── Policy default_deny=False ─────────────────────────────────── Scenario: Server mode with default_deny disabled permits unbound action Given a PermissionService in server mode with default_deny disabled When I check permission for "frank" to "read" on "namespace" scope "ns-9" Then the permission check result should be true And the reason should contain "default-deny disabled" # ── Scope mismatch ────────────────────────────────────────────── Scenario: Binding on project scope does not grant access at namespace scope Given a PermissionService in server mode And a role binding for principal "alice" with role "editor" on "project" scope "proj-1" When I check permission for "alice" to "read" on "namespace" scope "proj-1" Then the permission check result should be false Scenario: Binding on skill scope does not grant access at plan scope Given a PermissionService in server mode And a role binding for principal "bob" with role "admin" on "skill" scope "sk-1" When I check permission for "bob" to "admin" on "plan" scope "sk-1" Then the permission check result should be false # ── Duplicate binding prevention ──────────────────────────────── Scenario: Adding a duplicate binding is rejected Given a PermissionService in server mode with no bindings And I add a binding for "dave" as "editor" on "project" scope "proj-1" When I try to add a duplicate binding for "dave" as "editor" on "project" scope "proj-1" Then the add result should be false # ── RoleBinding validation ────────────────────────────────────── Scenario: RoleBinding rejects empty principal When I try to create a role binding with empty principal Then a permission validation error should be raised Scenario: RoleBinding rejects empty scope_id When I try to create a role binding with empty scope_id Then a permission validation error should be raised # ── PermissionCheck with result=False ─────────────────────────── Scenario: PermissionCheck accepts result false Given a PermissionCheck for "denied-user" action "write" scope "project" scope_id "proj-1" result false reason "denied" Then the check result should be false And the check reason should be "denied" # ── enforce_permission with pre-loaded service ────────────────── Scenario: enforce_permission decorator uses pre-loaded service with bindings Given a pre-loaded permission service with "alice" as "editor" on "project" scope "proj-1" for action "write" When I call the pre-loaded decorated function in server mode Then it should succeed without raising Scenario: enforce_permission decorator uses module-level default service Given a module-level default service with "bob" as "admin" on "namespace" scope "ns-1" And a function decorated with enforce_permission for "admin" on "namespace" scope "ns-1" When I call the decorated function in server mode via module default Then it should succeed without raising # ── Input validation — check_permission ───────────────────────── Scenario: check_permission rejects empty principal Given a PermissionService in local mode When I check permission with empty principal Then a permission ValueError should mention "principal" Scenario: check_permission rejects whitespace-only principal Given a PermissionService in local mode When I check permission with whitespace-only principal Then a permission ValueError should mention "principal" Scenario: check_permission rejects empty scope_id Given a PermissionService in local mode When I check permission with empty scope_id Then a permission ValueError should mention "scope_id" Scenario: check_permission rejects whitespace-only scope_id Given a PermissionService in local mode When I check permission with whitespace-only scope_id Then a permission ValueError should mention "scope_id" Scenario: check_permission strips leading/trailing whitespace from principal Given a PermissionService in local mode When I check permission for " alice " to "read" on "project" scope "proj-1" Then the permission check result should be true Scenario: check_permission strips leading/trailing whitespace from scope_id Given a PermissionService in local mode When I check permission for "alice" to "read" on "project" scope " proj-1 " Then the permission check result should be true # ── Input validation — get_role_bindings ──────────────────────── Scenario: get_role_bindings rejects empty principal Given a PermissionService in local mode When I get role bindings with empty principal Then a permission ValueError should mention "principal" Scenario: get_role_bindings rejects whitespace-only scope_id Given a PermissionService in local mode When I get role bindings with whitespace-only scope_id Then a permission ValueError should mention "scope_id"