Authoritative reference for the permission strings recognised by Powernode access control.
- Overview
- Forbidden Patterns
- Correct Patterns
- Permission Categories
- AI Autonomy Permissions
- Common Permission Patterns
- Backend Roles
- API Response Format
- Navigation Filtering
Powernode uses permission-based access control. The frontend MUST check permissions only, never roles. The backend uses current_user.has_permission?('name') for authorization. Roles exist only as a backend convenience for grouping permissions; they are never inspected by frontend code. The canonical registry is Permission records seeded by server/app/services/permission_seeder.rb.
For the design rationale (why permissions instead of roles, ABAC vs RBAC trade-offs, etc.), see ../concepts/permissions.md.
// FORBIDDEN — role-based access control
const canManage = currentUser?.roles?.includes('account.manager');
const isSystemAdmin = currentUser?.role === 'system.admin';
if (user.roles.includes('billing.manager')) { return <AdminPanel />; }
// FORBIDDEN — mixed role / permission checks
const hasAccess = user.roles.includes('admin') || user.permissions.includes('read');
// FORBIDDEN — hardcoded role checks
if (currentUser?.roles?.some(r => r.includes('admin'))) { /* ... */ }# FORBIDDEN — .include? on permissions collection (returns objects, not strings)
if current_user.permissions.include?('users.manage')
# FORBIDDEN — role-based authorization
if current_user.roles.any? { |r| r.name == 'admin' }const canManageUsers = currentUser?.permissions?.includes('users.manage');
const canViewBilling = currentUser?.permissions?.includes('billing.read');
const canAccessAdmin = currentUser?.permissions?.includes('admin.access');
if (!canAccessAdmin) return <AccessDenied />;
<Button disabled={!currentUser?.permissions?.includes('users.create')}>
Create User
</Button>
{currentUser?.permissions?.includes('analytics.read') && <AnalyticsDashboard />}# Correct — has_permission? helper
if current_user.has_permission?('users.manage')
# allow
end
# Controller before_action
before_action -> { require_permission('users.read') }, only: %i[index show]
before_action -> { require_permission('users.manage') }, only: %i[create update destroy]
# Inline
def sensitive_action
return render_forbidden("Access denied") unless current_user.has_permission?('admin.access')
# proceed
endPermissions are organised by prefix. The seeder is the source of truth; counts shown below are illustrative — query the runtime registry for current numbers.
| Category | Description |
|---|---|
admin.* |
Admin panel access — accounts, AI, audit, billing, DevOps, Docker, files, Git, marketplace |
ai.* |
AI features — agents, workflows, memory, knowledge, conversations, providers, autonomy |
system.* |
System-level — admin, monitoring, health, configuration |
supply_chain.* |
Supply chain management |
devops.* |
DevOps — pipelines, providers, repositories, templates |
swarm.* |
Docker Swarm operations |
git.* |
Git — approvals, credentials, pipelines, providers, repositories |
docker.* |
Docker container management |
marketing.* |
Marketing campaigns |
integrations.* |
Third-party integrations |
app.* |
App marketplace |
files.* |
File management |
kb.* |
Knowledge base articles |
mcp.* |
MCP protocol operations |
subscription.* |
Subscription lifecycle (extension-gated) |
page.* |
CMS pages |
review.* |
Code reviews |
storage.* |
Storage backends |
listing.* |
Marketplace listings |
team.* |
Team management |
webhook.* |
Webhook management |
api.* |
API key management |
audit.* |
Audit logs |
billing.* |
Billing operations (extension-gated) |
plans.* |
Plan management (extension-gated) |
report.* |
Reports |
user.* |
User management |
invoice.* |
Invoice management (extension-gated) |
marketplace.* |
Marketplace access |
users.* |
User listing |
| Permission | Description |
|---|---|
ai.kill_switch.manage |
Activate and deactivate the AI emergency kill switch |
ai.goals.manage |
Create, update, and delete AI agent goals |
ai.intervention_policies.manage |
Configure AI intervention policies and notification preferences |
ai.proposals.view |
View AI agent proposals |
ai.proposals.review |
Approve or reject AI agent proposals |
ai.escalations.view |
View AI agent escalations |
ai.escalations.resolve |
Acknowledge and resolve AI agent escalations |
ai.feedback.submit |
Submit feedback on AI agent performance |
ai.feedback.view |
View AI agent feedback history |
ai.autonomy.manage |
Manage AI agent autonomous behaviour and duty cycles |
Role assignments: ai.kill_switch.manage is automatically assigned to owner and admin roles.
# CRUD pattern
resource.create
resource.read
resource.update
resource.delete
# Management shortcut
resource.manage # implies full CRUD
# Admin-scoped
admin.resource.read
admin.resource.update
admin.resource.delete
Roles exist only for permission grouping in the backend. Frontend code NEVER checks roles.
| Role | Description | Typical Permissions |
|---|---|---|
system.admin |
Full system access | All permissions |
account.manager |
Account management | Account-scoped permissions |
account.member |
Basic access | Read-only permissions |
billing.manager |
Billing operations | Billing-related permissions (extension-gated) |
User objects returned from API MUST include a permissions array:
class UserSerializer
def permission_names
object.permissions.pluck(:name)
end
end{
"data": {
"id": "uuid",
"email": "user@example.com",
"permissions": ["users.read", "billing.read", "analytics.read"]
}
}const filteredNavItems = navigationItems.filter(item => {
if (!item.permission) return true;
return currentUser?.permissions?.includes(item.permission);
});- ../concepts/permissions.md — Design rationale
- ../guides/backend.md —
has_permission?patterns - ../guides/frontend.md — Permission-gated UI
- api/overview.md — Endpoint-level permission requirements
- Source of truth:
server/app/services/permission_seeder.rb
docs/platform/PERMISSION_SYSTEM_REFERENCE.md(registry portions)
Last verified: 2026-05-17