Architecture: How should platform-wide role-based access control be implem...
Documentation for Sessions
🏗️ Architecture: How should platform-wide role-based access control be implem...
Date: 2026-03-31
Time: 18:41:43 UTC
Session: gspace1-293.2
Agent: angular
Confidence: 95%
Status: ✅ Documented
📋 Decision Context
Question:
How should platform-wide role-based access control be implemented across 3 backends, all frontend apps, and 238+ NX libraries?
Task: S292-293: Vitto Capability System & Feature Enhancements
🎯 Decision Made
Chosen:
Capability Matrix System: A typed VittoCapability enum (~85 values) with a CAPABILITY_MATRIX constant containing metadata (category, scope, risk, allowedRoles, overridable) as the single source of truth in @libs-foundational/core-models. Consumed by: CapabilityGuard (NestJS APP_GUARD) on all 3 backends, RoleAccessService (Angular signals) on all frontends, and a startup validator that prevents unprotected endpoints from shipping.
Options Considered:
- Approach A: Capability Matrix with typed enum and auto-generated docs
- Approach B: Extend existing DerivedFeatures (15 flags) to 50+ flags
- Approach C: Hybrid — Matrix generates DerivedFeatures automatically
💭 Reasoning
Root cause: The existing RBAC was binary (isAdmin vs non-admin) on the frontend and inconsistent on backends. DerivedFeatures existed (15 flags) but was used in ZERO frontend components. ~60+ backend-api controllers, ALL backend-ai-api intelligence endpoints, and critical backend-vitto-api operations (vault, skills CRUD, module generation, audit logs) had NO role checks. Approach A was chosen because: (1) TypeScript enum prevents typos at compile time, (2) startup validator prevents gaps at runtime, (3) 3-tier override resolution (user > space > matrix) maps directly to the core/personal layer architecture, (4) auto-generated table in gograb-dev serves as living documentation for all agents and developers, (5) the @RequireCapability decorator pattern mirrors existing @Roles/@Public patterns so migration is incremental. Approach B was rejected because modifying derivePermissions() with 50+ if/switch blocks is error-prone and doesn't provide a visual matrix. Approach C was rejected as unnecessary complexity — we can deprecate DerivedFeatures over time.
📁 Files Involved
libs/libs-foundational/core-models/src/lib/capabilities/capability.enum.tslibs/libs-foundational/core-models/src/lib/capabilities/capability-matrix.tslibs/libs-foundational/core-models/src/lib/capabilities/capability-resolver.tslibs/libs-api/auth-core/src/lib/guards/capability.guard.tslibs/libs-foundational/auth-core/src/lib/role-access/role-access.service.ts
🔗 Related
- Decision ID:
dev_1774982503715_i43oidg - Session: 293
- Workspace: gspace1
Auto-generated by MCP Smart Consolidation