Complete the public -> platform naming migration across the codebase. This aligns with the naming convention where "platform" refers to the marketing/public-facing pages of the platform itself. Changes: - Update all imports from public to platform modules - Update template references from public/ to platform/ - Update route registrations to use platform prefix - Update documentation to reflect new naming - Update test files for platform API endpoints Files affected: - app/api/main.py - router imports - app/modules/*/routes/*/platform.py - route definitions - app/modules/*/templates/*/platform/ - template files - app/modules/routes.py - route discovery - docs/* - documentation updates - tests/integration/api/v1/platform/ - test files Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
24 KiB
Plan: Flexible Role & Permission Management with Platform Controls
Status: READY FOR APPROVAL
Summary
Design a flexible role/permission management system that:
- Modules define permissions - Each module declares its available permissions
- Platforms control availability - Platforms can restrict which permissions vendors can use
- Vendors customize roles - Vendors create custom roles within platform constraints
- Multi-tier hierarchy - Platform → Vendor → User permission inheritance
Current State Analysis
What Exists Today
| Component | Location | Description |
|---|---|---|
| Role Model | app/modules/tenancy/models/vendor.py |
vendor_id, name, permissions (JSON array) |
| VendorUser Model | Same file | Links user → vendor with role_id |
| PermissionDiscoveryService | app/modules/tenancy/services/permission_discovery_service.py |
Discovers permissions from modules |
| VendorTeamService | app/modules/tenancy/services/vendor_team_service.py |
Manages team invitations, role assignment |
| Role Presets | In discovery service code | Hardcoded ROLE_PRESETS dict |
| Platform Model | models/database/platform.py |
Multi-platform support |
| PlatformModule | models/database/platform_module.py |
Controls which modules are enabled per platform |
| VendorPlatform | models/database/vendor_platform.py |
Vendor-platform relationship with tier_id |
Current Gaps
- No platform-level permission control - Platforms cannot restrict which permissions vendors can assign
- No custom role CRUD API - Roles are created implicitly when inviting team members
- Presets are code-only - Cannot customize role templates per platform
- No role templates table - Platform admins cannot define default roles for their vendors
Proposed Architecture
Tier 1: Module-Defined Permissions (Exists)
Each module declares permissions in definition.py:
permissions=[
PermissionDefinition(
id="products.view",
label_key="catalog.permissions.products_view",
category="products",
),
PermissionDefinition(
id="products.create",
label_key="catalog.permissions.products_create",
category="products",
),
]
Discovery Service aggregates all permissions at runtime.
Tier 2: Platform Permission Control (New)
New PlatformPermissionConfig model to control:
- Which permissions are available to vendors on this platform
- Default role templates for vendor onboarding
- Permission bundles based on subscription tier
┌─────────────────────────────────────────────────────────────────┐
│ PLATFORM │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PlatformPermissionConfig │ │
│ │ - platform_id │ │
│ │ - allowed_permissions: ["products.*", "orders.*"] │ │
│ │ - blocked_permissions: ["team.manage"] │ │
│ │ - tier_restrictions: {free: [...], pro: [...]} │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ PlatformRoleTemplate │ │
│ │ - platform_id │ │
│ │ - name: "Manager", "Staff", etc. │ │
│ │ - permissions: [...] │ │
│ │ - is_default: bool (create for new vendors) │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Tier 3: Vendor Role Customization (Enhanced)
Vendors can:
- View roles available (from platform templates or custom)
- Create custom roles (within platform constraints)
- Edit role permissions (within allowed set)
- Assign roles to team members
┌─────────────────────────────────────────────────────────────────┐
│ VENDOR │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Role (existing model, enhanced) │ │
│ │ - vendor_id │ │
│ │ - name │ │
│ │ - permissions: [...] (validated against platform) │ │
│ │ - is_from_template: bool │ │
│ │ - source_template_id: FK (nullable) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ VendorUser (existing, unchanged) │ │
│ │ - user_id │ │
│ │ - vendor_id │ │
│ │ - role_id │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
Data Model Changes
New Models
1. PlatformPermissionConfig
# app/modules/tenancy/models/platform_permission_config.py
class PlatformPermissionConfig(Base):
"""Platform-level permission configuration"""
__tablename__ = "platform_permission_configs"
id: Mapped[int] = mapped_column(primary_key=True)
platform_id: Mapped[int] = mapped_column(ForeignKey("platforms.id"), unique=True)
# Permissions this platform allows vendors to use
# Empty = all discovered permissions allowed
allowed_permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
# Explicit blocklist (takes precedence over allowed)
blocked_permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
# Tier-based restrictions: {"free": ["products.view"], "pro": ["products.*"]}
tier_permissions: Mapped[dict] = mapped_column(JSON, default=dict)
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime] = mapped_column(onupdate=func.now())
# Relationships
platform: Mapped["Platform"] = relationship(back_populates="permission_config")
2. PlatformRoleTemplate
# app/modules/tenancy/models/platform_role_template.py
class PlatformRoleTemplate(Base):
"""Role templates defined at platform level"""
__tablename__ = "platform_role_templates"
id: Mapped[int] = mapped_column(primary_key=True)
platform_id: Mapped[int] = mapped_column(ForeignKey("platforms.id"))
name: Mapped[str] = mapped_column(String(50)) # "Manager", "Staff", etc.
display_name: Mapped[str] = mapped_column(String(100)) # i18n key or display name
description: Mapped[str | None] = mapped_column(String(255))
# Permissions for this template
permissions: Mapped[list[str]] = mapped_column(JSON, default=list)
# Configuration
is_default: Mapped[bool] = mapped_column(default=False) # Auto-create for new vendors
is_system: Mapped[bool] = mapped_column(default=False) # Cannot be deleted
order: Mapped[int] = mapped_column(default=100) # Display order
created_at: Mapped[datetime] = mapped_column(default=func.now())
updated_at: Mapped[datetime] = mapped_column(onupdate=func.now())
# Relationships
platform: Mapped["Platform"] = relationship(back_populates="role_templates")
__table_args__ = (
UniqueConstraint("platform_id", "name", name="uq_platform_role_template"),
)
Enhanced Existing Models
Role Model Enhancement
# Add to existing Role model in app/modules/tenancy/models/vendor.py
class Role(Base):
# ... existing fields ...
# NEW: Track template origin
source_template_id: Mapped[int | None] = mapped_column(
ForeignKey("platform_role_templates.id"),
nullable=True
)
is_custom: Mapped[bool] = mapped_column(default=False) # Vendor-created custom role
# Relationship
source_template: Mapped["PlatformRoleTemplate"] = relationship()
Service Layer Changes
1. PlatformPermissionService (New)
# app/modules/tenancy/services/platform_permission_service.py
class PlatformPermissionService:
"""Manages platform-level permission configuration"""
def get_allowed_permissions(
self,
db: Session,
platform_id: int,
tier_id: int | None = None
) -> set[str]:
"""
Get permissions allowed for a platform/tier combination.
1. Start with all discovered permissions
2. Filter by platform's allowed_permissions (if set)
3. Remove blocked_permissions
4. Apply tier restrictions (if tier_id provided)
"""
pass
def validate_permissions(
self,
db: Session,
platform_id: int,
tier_id: int | None,
permissions: list[str]
) -> tuple[list[str], list[str]]:
"""
Validate permissions against platform constraints.
Returns (valid_permissions, invalid_permissions)
"""
pass
def update_platform_config(
self,
db: Session,
platform_id: int,
allowed_permissions: list[str] | None = None,
blocked_permissions: list[str] | None = None,
tier_permissions: dict | None = None
) -> PlatformPermissionConfig:
"""Update platform permission configuration"""
pass
2. PlatformRoleTemplateService (New)
# app/modules/tenancy/services/platform_role_template_service.py
class PlatformRoleTemplateService:
"""Manages platform role templates"""
def get_templates(self, db: Session, platform_id: int) -> list[PlatformRoleTemplate]:
"""Get all role templates for a platform"""
pass
def create_template(
self,
db: Session,
platform_id: int,
name: str,
permissions: list[str],
is_default: bool = False
) -> PlatformRoleTemplate:
"""Create a new role template (validates permissions)"""
pass
def create_default_roles_for_vendor(
self,
db: Session,
vendor: Vendor
) -> list[Role]:
"""
Create vendor roles from platform's default templates.
Called during vendor onboarding.
"""
pass
def seed_default_templates(self, db: Session, platform_id: int):
"""Seed platform with standard role templates (Manager, Staff, etc.)"""
pass
3. Enhanced VendorTeamService
# Updates to app/modules/tenancy/services/vendor_team_service.py
class VendorTeamService:
def get_available_permissions(
self,
db: Session,
vendor: Vendor
) -> list[PermissionDefinition]:
"""
Get permissions available to this vendor based on:
1. Platform constraints
2. Vendor's subscription tier
"""
platform_perm_service = PlatformPermissionService()
vendor_platform = db.query(VendorPlatform).filter(...).first()
allowed = platform_perm_service.get_allowed_permissions(
db,
vendor_platform.platform_id,
vendor_platform.tier_id
)
# Return PermissionDefinitions filtered to allowed set
all_perms = permission_discovery_service.get_all_permissions()
return [p for p in all_perms if p.id in allowed]
def create_custom_role(
self,
db: Session,
vendor: Vendor,
name: str,
permissions: list[str]
) -> Role:
"""
Create a custom role for the vendor.
Validates permissions against platform constraints.
"""
# Validate permissions
valid, invalid = self.platform_permission_service.validate_permissions(
db, vendor.platform_id, vendor.tier_id, permissions
)
if invalid:
raise InvalidPermissionsException(invalid)
role = Role(
vendor_id=vendor.id,
name=name,
permissions=valid,
is_custom=True
)
db.add(role)
return role
def update_role(
self,
db: Session,
vendor: Vendor,
role_id: int,
name: str | None = None,
permissions: list[str] | None = None
) -> Role:
"""Update an existing role (validates permissions)"""
pass
def delete_role(
self,
db: Session,
vendor: Vendor,
role_id: int
) -> bool:
"""Delete a custom role (cannot delete if in use)"""
pass
API Endpoints
Platform Admin Endpoints (Admin Panel)
# app/modules/tenancy/routes/admin/platform_permissions.py
@router.get("/platforms/{platform_id}/permissions")
def get_platform_permission_config(platform_id: int):
"""Get platform permission configuration"""
@router.put("/platforms/{platform_id}/permissions")
def update_platform_permission_config(platform_id: int, config: PermissionConfigUpdate):
"""Update platform permission configuration"""
@router.get("/platforms/{platform_id}/role-templates")
def list_role_templates(platform_id: int):
"""List role templates for a platform"""
@router.post("/platforms/{platform_id}/role-templates")
def create_role_template(platform_id: int, template: RoleTemplateCreate):
"""Create a new role template"""
@router.put("/platforms/{platform_id}/role-templates/{template_id}")
def update_role_template(platform_id: int, template_id: int, template: RoleTemplateUpdate):
"""Update a role template"""
@router.delete("/platforms/{platform_id}/role-templates/{template_id}")
def delete_role_template(platform_id: int, template_id: int):
"""Delete a role template"""
Vendor Dashboard Endpoints
# app/modules/tenancy/routes/api/vendor_roles.py
@router.get("/roles")
def list_vendor_roles():
"""List all roles for current vendor"""
@router.post("/roles")
def create_custom_role(role: RoleCreate):
"""Create a custom role (validates permissions against platform)"""
@router.put("/roles/{role_id}")
def update_role(role_id: int, role: RoleUpdate):
"""Update a role"""
@router.delete("/roles/{role_id}")
def delete_role(role_id: int):
"""Delete a custom role"""
@router.get("/available-permissions")
def get_available_permissions():
"""Get permissions available to this vendor (filtered by platform/tier)"""
Permission Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ PERMISSION FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
1. MODULE DEFINES PERMISSIONS
┌──────────────┐
│ catalog │ → products.view, products.create, products.edit, ...
│ orders │ → orders.view, orders.manage, orders.refund, ...
│ team │ → team.view, team.manage, team.invite, ...
└──────────────┘
↓
2. DISCOVERY SERVICE AGGREGATES
┌────────────────────────────────────────────────┐
│ PermissionDiscoveryService │
│ get_all_permissions() → 50+ permissions │
└────────────────────────────────────────────────┘
↓
3. PLATFORM FILTERS PERMISSIONS
┌────────────────────────────────────────────────┐
│ PlatformPermissionConfig │
│ allowed: ["products.*", "orders.view"] │
│ blocked: ["orders.refund"] │
│ tier_permissions: │
│ free: ["products.view", "orders.view"] │
│ pro: ["products.*", "orders.*"] │
└────────────────────────────────────────────────┘
↓
4. VENDOR CREATES/USES ROLES
┌────────────────────────────────────────────────┐
│ Role (vendor-specific) │
│ Manager: [products.*, orders.view] │
│ Staff: [products.view, orders.view] │
└────────────────────────────────────────────────┘
↓
5. USER GETS PERMISSIONS VIA ROLE
┌────────────────────────────────────────────────┐
│ VendorUser │
│ user_id: 123 │
│ role_id: 5 (Staff) │
│ → permissions: [products.view, orders.view] │
└────────────────────────────────────────────────┘
Database Migrations
Migration 1: Add Platform Permission Config
CREATE TABLE platform_permission_configs (
id SERIAL PRIMARY KEY,
platform_id INTEGER NOT NULL UNIQUE REFERENCES platforms(id),
allowed_permissions JSONB DEFAULT '[]',
blocked_permissions JSONB DEFAULT '[]',
tier_permissions JSONB DEFAULT '{}',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP
);
Migration 2: Add Platform Role Templates
CREATE TABLE platform_role_templates (
id SERIAL PRIMARY KEY,
platform_id INTEGER NOT NULL REFERENCES platforms(id),
name VARCHAR(50) NOT NULL,
display_name VARCHAR(100) NOT NULL,
description VARCHAR(255),
permissions JSONB DEFAULT '[]',
is_default BOOLEAN DEFAULT FALSE,
is_system BOOLEAN DEFAULT FALSE,
"order" INTEGER DEFAULT 100,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP,
UNIQUE (platform_id, name)
);
Migration 3: Enhance Roles Table
ALTER TABLE roles
ADD COLUMN source_template_id INTEGER REFERENCES platform_role_templates(id),
ADD COLUMN is_custom BOOLEAN DEFAULT FALSE;
Implementation Phases
Phase 1: Data Models (Foundation)
Files to create:
app/modules/tenancy/models/platform_permission_config.pyapp/modules/tenancy/models/platform_role_template.pymigrations/versions/xxx_add_platform_permission_tables.pymigrations/versions/xxx_enhance_roles_table.py
Files to modify:
app/modules/tenancy/models/__init__.py- Export new modelsapp/modules/tenancy/models/vendor.py- Add Role enhancement
Phase 2: Service Layer
Files to create:
app/modules/tenancy/services/platform_permission_service.pyapp/modules/tenancy/services/platform_role_template_service.py
Files to modify:
app/modules/tenancy/services/vendor_team_service.py- Add role CRUD, permission validation
Phase 3: API Endpoints
Files to create:
app/modules/tenancy/routes/admin/platform_permissions.pyapp/modules/tenancy/routes/api/vendor_roles.pyapp/modules/tenancy/schemas/platform_permissions.pyapp/modules/tenancy/schemas/roles.py
Files to modify:
app/modules/tenancy/routes/__init__.py- Register new routers
Phase 4: Vendor Onboarding Integration
Files to modify:
app/modules/tenancy/services/vendor_service.py- Create default roles from templates during vendor creation
Phase 5: Admin UI (Optional, Future)
Files to create/modify:
- Admin panel for platform permission configuration
- Admin panel for role template management
- Vendor dashboard for custom role management
Verification
-
App loads:
python -c "from main import app; print('OK')" -
Migrations run:
make migrate-up -
Architecture validation:
python scripts/validate_architecture.py -v -
Unit tests: Test permission filtering logic
- Platform with no config → all permissions allowed
- Platform with allowed list → only those permissions
- Platform with blocked list → all except blocked
- Tier restrictions → correct subset per tier
-
Integration tests:
- Create vendor → gets default roles from platform templates
- Create custom role → validates against platform constraints
- Assign role → user gets correct permissions
- Change tier → available permissions update
-
API tests:
- Platform admin can configure permissions
- Vendor owner can create/edit custom roles
- Invalid permissions are rejected
Files Summary
New Files (9)
| File | Purpose |
|---|---|
app/modules/tenancy/models/platform_permission_config.py |
Platform permission config model |
app/modules/tenancy/models/platform_role_template.py |
Platform role template model |
app/modules/tenancy/services/platform_permission_service.py |
Platform permission logic |
app/modules/tenancy/services/platform_role_template_service.py |
Role template logic |
app/modules/tenancy/routes/admin/platform_permissions.py |
Admin API endpoints |
app/modules/tenancy/routes/api/vendor_roles.py |
Vendor API endpoints |
app/modules/tenancy/schemas/platform_permissions.py |
Pydantic schemas |
app/modules/tenancy/schemas/roles.py |
Role schemas |
migrations/versions/xxx_platform_permission_tables.py |
Database migration |
Modified Files (5)
| File | Changes |
|---|---|
app/modules/tenancy/models/__init__.py |
Export new models |
app/modules/tenancy/models/vendor.py |
Enhance Role model |
app/modules/tenancy/services/vendor_team_service.py |
Add role CRUD, validation |
app/modules/tenancy/services/vendor_service.py |
Create default roles on vendor creation |
app/modules/tenancy/routes/__init__.py |
Register new routers |
Key Design Decisions
-
Wildcard support in permissions -
products.*matchesproducts.view,products.create, etc. -
Tier inheritance - Higher tiers include all permissions of lower tiers
-
Template-based vendor roles - Default roles created from platform templates, but vendor can customize
-
Soft validation - Invalid permissions in existing roles are not automatically removed (audit trail)
-
Backward compatible - Existing roles without
source_template_idcontinue to work cl