rbac-permissions-architect
Use this skill when architecting new features within the RBAC modular permissions system. Guides architects through workspace design, feature definition, permission mapping, and role hierarchy. Critical for understanding Owner/Super Admin special roles, feature independence, and multi-tenant context isolation. Invoke when creating PRDs that involve permissions, workspaces, features, or role-based access control.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: rbac-permissions-architect
description: Use this skill when architecting new features within the RBAC modular permissions system. Guides architects through workspace design, feature definition, permission mapping, and role hierarchy. Critical for understanding Owner/Super Admin special roles, feature independence, and multi-tenant context isolation. Invoke when creating PRDs that involve permissions, workspaces, features, or role-based access control.
---
# RBAC Permissions Architect
## Purpose
This skill enables the Architect Agent to design features that correctly integrate with the project's **RBAC modular permissions system** defined in `concepto-base.md`. It provides the necessary understanding of workspaces, features, permissions, and special roles to create compliant PRDs and entity designs.
## When to Use This Skill
Use this skill when:
- Creating a new feature that involves permissions or access control
- Designing features that operate within workspaces (organizations or projects)
- Defining resources and permissions for a new feature module
- Creating entities that need to respect workspace boundaries
- Architecting features that interact with Owner/Super Admin roles
- Any PRD that mentions: permissions, roles, access, workspaces, features, RLS
## Core Understanding Required
Before architecting any feature, understand these **immutable principles**:
### 1. The Three Pillars (Independent but Interconnected)
```
WORKSPACES (Containers)
↓
FEATURES (Modules)
↓
RBAC (Access Control)
```
- **Workspaces**: Organizations (root) and Projects (children)
- **Features**: Activatable modules with their own resources/permissions
- **RBAC**: Granular permissions grouped into roles
### 2. Critical Rules
- **NO inheritance**: Features and permissions DO NOT inherit between workspaces
- **Contextual permissions**: Permissions only apply in the workspace where assigned
- **Special roles**: Owner and Super Admin transcend normal permission rules
- **Mandatory feature**: `permissions-management` is always active
- **Independence**: Each workspace activates features explicitly
## Architect Workflow for RBAC-Aware Features
### Phase 1: Feature Scope Definition
**Questions to answer:**
1. **Workspace scope**: Does this feature apply to organizations, projects, or both?
2. **Resource identification**: What entities will users interact with?
3. **Permission granularity**: What actions can users perform on each resource?
4. **Role requirements**: Are there default roles needed for this feature?
5. **Special role interaction**: How do Owner/Super Admin interact with this feature?
**Reference files to consult:**
- **Basic concepts**: [CONCEPTS.md](references/CONCEPTS.md) - Workspace, Feature, Resource, Permission, Role definitions
- **Workspace architecture**: [WORKSPACES.md](references/WORKSPACES.md) - Organization vs Project behavior
### Phase 2: Permission Matrix Design
**Steps:**
1. **List all resources** in your feature (e.g., `boards`, `cards`, `comments`)
2. **Define CRUD+ actions** for each resource (create, read, update, delete, custom actions)
3. **Map to permission format**: `{resource}.{action}` (e.g., `boards.create`, `cards.move`)
4. **Consider visibility**: Which permission grants UI visibility? (usually `.read`)
5. **Document restrictions**: What can Owner/Super Admin do that normal users cannot?
**Example permission matrix:**
```
Feature: kanban
Resources:
├─ boards
├─ cards
└─ card_comments
Permissions:
├─ boards.create
├─ boards.read ← Grants visibility of Kanban in UI
├─ boards.update
├─ boards.delete
├─ cards.create
├─ cards.read
├─ cards.update
├─ cards.delete
├─ cards.move ← Custom action
├─ card_comments.create
└─ card_comments.delete
```
**Reference file to consult:**
- **Permission system**: [PERMISSIONS.md](references/PERMISSIONS.md) - Granular permissions, roles, verification flows
### Phase 2.5: CASL Ability Definition
**⚡ NEW: Authorization Layer Integration**
After defining the permission matrix, specify how CASL (the project's authorization library) will implement these permissions.
**Questions to answer:**
1. **Subject mapping**: What are the CASL subjects for each resource?
2. **Action alignment**: Do CASL actions match permission actions perfectly?
3. **Conditional rules**: Are there context-dependent rules (e.g., "own resources only")?
4. **Type safety**: How do we enforce TypeScript types for subjects and actions?
**CASL Subjects (PascalCase, Singular):**
Map each database resource to a CASL subject:
```typescript
// Database resources → CASL subjects
'boards' → 'Board'
'cards' → 'Card'
'card_comments' → 'Comment'
```
**Rules:**
- Use **PascalCase** for subjects (CASL convention)
- Use **singular** form (Board, not Boards)
- One resource = one subject (1:1 mapping)
**CASL Actions:**
Standard actions align with permissions:
```typescript
'create' → boards.create
'read' → boards.read
'update' → boards.update
'delete' → boards.delete
```
Custom actions:
```typescript
'move' → cards.move
'assign' → cards.assign
```
**Ability Builder Template:**
Include this in your PRD's `entities.ts`:
```typescript
// ===== CASL Integration =====
import { AbilityBuilder, createMongoAbility, MongoAbility } from '@casl/ability';
// Define CASL types
type Subjects = 'Board' | 'Card' | 'Comment' | 'all';
type Actions = 'create' | 'read' | 'update' | 'delete' | 'move' | 'manage';
export type AppAbility = MongoAbility<[Actions, Subjects]>;
/**
* Build CASL ability from user permissions
*
* @param user - Current user
* @param workspace - Current workspace context
* @param permissions - User's permissions in this workspace
*/
export function defineAbilitiesFor(
user: User,
workspace: Workspace,
permissions: Permission[]
): AppAbility {
const { can, cannot, build } = new AbilityBuilder<AppAbility>(createMongoAbility);
// ===== Owner: Full access bypass =====
if (user.id === workspace.owner_id) {
can('manage', 'all');
return build();
}
// ===== Super Admin: Full access with restrictions =====
const orgId = workspace.parent_id || workspace.id;
const isSuperAdmin = user.superAdminOrgs?.includes(orgId);
if (isSuperAdmin) {
can('manage', 'all');
// Explicit restrictions (Super Admin CANNOT do these)
cannot('delete', 'Organization');
cannot('modify', 'User', { isOwner: true });
cannot('assign', 'SuperAdminRole');
return build();
}
// ===== Normal users: Map granular permissions =====
permissions.forEach(perm => {
const [resource, action] = perm.full_name.split('.');
const subject = mapResourceToSubject(resource);
can(action as Actions, subject);
});
return build();
}
/**
* Map database resources to CASL subjects
*/
function mapResourceToSubject(resource: string): Subjects {
const mapping: Record<string, Subjects> = {
'boards': 'Board',
'cards': 'Card',
'card_comments': 'Comment',
};
return mapping[resource] || 'all';
}
```
**React Integration Pattern:**
Specify how UI components will use CASL:
```typescript
// ===== React Context (features/rbac/context/AbilityContext.tsx) =====
import { createContext } from 'react';
import { AppAbility } from '../entities';
export const AbilityContext = createContext<AppAbility | null>(null);
// ===== Custom Hook (features/rbac/hooks/useAppAbility.ts) =====
import { useContext } from 'react';
import { AbilityContext } from '../context/AbilityContext';
export function useAppAbility() {
const ability = useContext(AbilityContext);
if (!ability) throw new Error('AbilityContext not provided');
return ability;
}
// ===== Usage in Components =====
import { Can } from '@casl/react';
import { useAppAbility } from '@/features/rbac/hooks/useAppAbility';
function KanbanBoard() {
const ability = useAppAbility();
return (
<div>
{/* Declarative visibility with Can component */}
<Can I="create" a="Board" ability={ability}>
<CreateBoardButton />
</Can>
{/* Programmatic check */}
{ability.can('delete', 'Board') && (
<DeleteBoardButton />
)}
</div>
);
}
```
**⚠️ CRITICAL: CASL vs RLS**
CASL and RLS serve **different purposes**:
```
┌─────────────────────────────────────────┐
│ CLIENT-SIDE (CASL) │
│ - UX authorization │
│ - Show/hide UI elements │
│ - Enable/disable actions │
│ - NOT security (can be bypassed) │
└─────────────────────────────────────────┘
↓ HTTP Request
┌─────────────────────────────────────────┐
│ SERVER-SIDE (RLS) │
│ - REAL security │
│ - Database-level enforcement │
│ - Cannot be bypassed │
│ - Final source of truth │
└─────────────────────────────────────────┘
```
**Both are mandatory.** CASL improves UX, RLS enforces security.
**Deliverables for Phase 2.5:**
In your PRD, include:
- [ ] CASL subject mapping table
- [ ] AppAbility type definition
- [ ] `defineAbilitiesFor()` function in entities.ts
- [ ] Owner bypass logic
- [ ] Super Admin bypass + restrictions
- [ ] Normal user permission mapping
- [ ] `mapResourceToSubject()` helper
- [ ] React integration examples (`<Can>`, hooks)
- [ ] Explanation of CASL vs RLS roles
**Reference:**
- **CASL Official Docs**: https://casl.js.org/v6/en/guide/intro
- **CASL React Integration**: https://casl.js.org/v6/en/package/casl-react
### Phase 3: Special Roles Integration
**Questions to answer:**
1. **Owner access**: Does Owner have automatic full access? (Usually yes)
2. **Super Admin access**: Does Super Admin have full access? (Usually yes, with restrictions)
3. **Protected actions**: Are there actions only Owner can perform?
4. **Project creation**: If this is an org-level feature, can it create projects?
**Critical rules:**
- Owner and Super Admin **bypass normal permission checks**
- Owner can **always** manage Super Admins
- Super Admin **cannot** modify Owner or other Super Admins
- Super Admin **cannot** assign Super Admin role
- Only Owner can delete organizations
**Reference file to consult:**
- **Special roles**: [SPECIAL_ROLES.md](references/SPECIAL_ROLES.md) - Owner and Super Admin privileges, restrictions, hierarchy
### Phase 4: Feature Activation Strategy
**Questions to answer:**
1. **Default activation**: Should this feature auto-activate in new workspaces?
2. **Mandatory feature**: Is this feature required system-wide? (Rare, only `permissions-management`)
3. **Configuration options**: Does the feature need workspace-specific config?
4. **Dependencies**: Does this feature require other features to be active?
**Implementation in entities.ts:**
```typescript
// Feature definition (goes in global features catalog)
export const KanbanFeatureSchema = z.object({
id: z.string().uuid(),
slug: z.literal('kanban'),
name: z.string(),
description: z.string(),
category: z.string(),
is_mandatory: z.boolean().default(false),
});
// Workspace feature activation
export const WorkspaceFeatureSchema = z.object({
workspace_id: z.string().uuid(),
feature_id: z.string().uuid(),
enabled: z.boolean().default(true),
config: z.record(z.unknown()).optional(), // JSONB config
});
```
**Reference file to consult:**
- **Feature system**: [FEATURES.md](references/FEATURES.md) - Feature catalog, activation, independence
### Phase 5: Visibility Rules with CASL
**Design how users see this feature in the UI:**
1. **Minimum permission**: What's the least permission needed to see the feature?
2. **Owner/Super Admin**: Always visible if feature is active
3. **Normal users**: Visible only if they have at least one permission
4. **Component-level**: Which UI elements require specific permissions?
**CASL Integration for Visibility:**
Use CASL's `<Can>` component and `ability.can()` checks for UI visibility:
```typescript
// ===== Feature-Level Visibility =====
import { useAppAbility } from '@/features/rbac/hooks/useAppAbility';
function useFeatureVisibility(featureSlug: string) {
const { user, workspace } = useWorkspace();
const ability = useAppAbility();
// Owner/Super Admin bypass
if (user.isOwner || user.isSuperAdmin) {
return isFeatureActive(featureSlug, workspace);
}
// Check if user has ANY permission from feature
const feature = getFeature(featureSlug);
const hasPermission = feature.resources.some(resource => {
const subject = mapResourceToSubject(resource);
return ability.can('read', subject);
});
return hasPermission;
}
// ===== Usage in Navigation =====
function Navigation() {
const showKanban = useFeatureVisibility('kanban');
return (
<nav>
{showKanban && (
<NavItem href="/kanban">Kanban</NavItem>
)}
</nav>
);
}
```
**Component-Level Visibility with `<Can>`:**
```typescript
import { Can } from '@casl/react';
import { useAppAbility } from '@/features/rbac/hooks/useAppAbility';
function KanbanBoard({ board }: { board: Board }) {
const ability = useAppAbility();
return (
<div>
<h1>{board.name}</h1>
{/* Declarative: Create Button */}
<Can I="create" a="Card" ability={ability}>
<CreateCardButton boardId={board.id} />
</Can>
{/* Declarative: Delete Button */}
<Can I="delete" a="Board" ability={ability}>
<DeleteBoardButton boardId={board.id} />
</Can>
{/* Programmatic: Conditional UI */}
<div className="actions">
{ability.can('update', 'Board') && (
<EditBoardButton board={board} />
)}
{ability.can('move', 'Card') && (
<DragAndDropHandle />
)}
</div>
{/* Inverted: Show message when user CANNOT do something */}
<Can not I="create" a="Card" ability={ability}>
<p className="text-muted">
You don't have permission to create cards
</p>
</Can>
</div>
);
}
```
**Field-Level Visibility (Advanced):**
CASL supports field-level permissions for fine-grained visibility:
```typescript
// Define field-level permissions in ability
can('read', 'Board', ['name', 'description']); // Can see name and description
can('read', 'Board', ['budget'], { isOwner: true }); // Only owners see budget
// Check field access
const canSeeBudget = ability.can('read', 'Board', 'budget');
// UI implementation
function BoardDetails({ board }: { board: Board }) {
const ability = useAppAbility();
return (
<div>
<h2>{board.name}</h2>
<p>{board.description}</p>
{/* Budget only visible to owners */}
{ability.can('read', subject('Board', board), 'budget') && (
<div>Budget: ${board.budget}</div>
)}
</div>
);
}
```
**Loading State Pattern:**
While abilities are loading, show skeleton UI:
```typescript
function ProtectedPage() {
const { ability, isLoading } = useAbilityWithLoading();
if (isLoading) {
return <SkeletonLoader />;
}
return (
<Can I="read" a="Board" ability={ability}>
<BoardList />
</Can>
);
}
```
**Example visibility logic (CASL version):**
```typescript
// User sees "Kanban" in menu if:
// - Owner/Super Admin (ability.can('manage', 'all') === true), OR
// - Has ANY permission: ability.can('read', 'Board') || ability.can('create', 'Board') || ...
// User sees "Create Board" button if:
// - ability.can('create', 'Board') === true
```
**Deliverables for Phase 5:**
In your PRD, include:
- [ ] Feature-level visibility hook using CASL
- [ ] `<Can>` component usage examples for all major actions
- [ ] Programmatic `ability.can()` checks for conditional UI
- [ ] Field-level visibility examples (if applicable)
- [ ] Loading states while abilities are fetched
- [ ] Inverted checks (`<Can not>`) for permission denial messages
**Reference file to consult:**
- **Visibility system**: [VISIBILITY.md](references/VISIBILITY.md) - UI visibility, feature gates, permission-based rendering
- **CASL React Docs**: https://casl.js.org/v6/en/package/casl-react
### Phase 6: Database Schema and Security Layers
**Design RLS-ready schema:**
1. **Workspace foreign key**: All feature tables MUST reference `workspaces.id`
2. **RLS policies**: Plan policies for Owner, Super Admin, and normal users
3. **Multi-tenancy**: Ensure workspace_id filters prevent cross-workspace access
4. **Cascading deletes**: Plan what happens when workspace is deleted
**Example schema pattern:**
```sql
CREATE TABLE kanban_boards (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
name TEXT NOT NULL,
created_by UUID REFERENCES auth.users(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- RLS Policy
CREATE POLICY "Users can access boards in their workspaces"
ON kanban_boards FOR SELECT
TO authenticated
USING (
-- Owner bypass
workspace_id IN (
SELECT id FROM workspaces
WHERE owner_id = auth.uid()
)
OR
-- Super Admin bypass
workspace_id IN (
SELECT organization_id FROM organization_super_admins
WHERE user_id = auth.uid()
)
OR
-- Normal user with permission
user_can(auth.uid(), 'read', 'boards', workspace_id)
);
```
**⚡ Security Layers: CASL + RLS Coordination**
CASL and RLS work together as **defense in depth**:
```
┌──────────────────────────────────────────────────────────┐
│ USER INTERACTION │
└──────────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────────┐
│ LAYER 1: CASL (Client-Side Authorization) │
│ Purpose: UX optimization │
│ - Shows/hides UI elements │
│ - Disables buttons for unauthorized actions │
│ - Prevents unnecessary API calls │
│ - Improves user experience │
│ │
│ Security: ❌ NOT REAL SECURITY (can be bypassed) │
└──────────────────────────────────────────────────────────┘
↓
HTTP Request Sent
↓
┌──────────────────────────────────────────────────────────┐
│ LAYER 2: RLS (Server-Side Security) │
│ Purpose: REAL security enforcement │
│ - Database-level access control │
│ - Cannot be bypassed by client │
│ - Enforced even if CASL is disabled/hacked │
│ - Multi-tenant data isolation │
│ │
│ Security: ✅ FINAL SOURCE OF TRUTH │
└──────────────────────────────────────────────────────────┘
```
**Example Flow:**
```typescript
// ===== CLIENT SIDE =====
// Step 1: User clicks "Delete Board"
function BoardActions({ board }) {
const ability = useAppAbility();
// CASL check: Should we even show the button?
if (!ability.can('delete', 'Board')) {
return null; // Button not rendered
}
async function handleDelete() {
// CASL check passed, send request
const response = await fetch(`/api/boards/${board.id}`, {
method: 'DELETE',
});
// If RLS denies, we get 403 error here
if (!response.ok) {
alert('Permission denied');
}
}
return <button onClick={handleDelete}>Delete</button>;
}
// ===== SERVER SIDE (RLS) =====
-- Step 2: Supabase receives DELETE query
DELETE FROM kanban_boards WHERE id = '...';
-- Step 3: RLS policy checks
-- Policy evaluates:
-- 1. Is user the owner? workspace_id IN (SELECT ...)
-- 2. Is user super admin? workspace_id IN (SELECT ...)
-- 3. Does user have permission? user_can(auth.uid(), 'delete', 'boards', ...)
-- If ANY condition is true → DELETE succeeds
-- If ALL c