Enterprise roles and permissions
Enterprise SaaS, Prototyping, UX research
Cohesion's access model packed scope into one number per feature. I led product and design on RBAC v2 so enterprise tenants could be modeled without custom roles or "God mode".
- Year
- Client
- Cohesion
- Role
- Product lead, design, web front end
- Links
- Prototype

Problem
Legacy RBAC stored scope inside each feature as a single number: off, my records, my company, or the whole building. That works for a single-tenant office. It breaks the moment one admin needs two companies, one department, or the whole building without belonging to a tenant.
Enterprise customers kept hitting that wall. A bank with subsidiaries wanted to manage users across every subsidiary so it could enforce one security policy. Instead it had custom roles, duplicate user accounts, and invitations showing the wrong host company. An enterprise spread across buildings needed an admin scoped to one company in one building, and there was no way to say that. A tenant in a shared building wanted department heads to manage reservations for their own people, and nothing below company level existed.
The constraint was the scariest part. Access here controls doors, visitors and keycards. A wrong mapping is not a bug report. It is a lockout or a security incident.
How I knew
The customer requests all pointed at the same root cause. Legacy kept only the widest tier and discarded company codes, so "ACME and BETA, but not GAMMA" could not be stored at all. Every workaround (custom roles, duplicate accounts, God mode) was a way around that one integer.
I wrote the PRD in October 2025 and a permission taxonomy that mapped every module and feature gate. The PRD set baselines for the problem: about 15 minutes of admin time per user, roughly 50 custom roles per building, 40% of support tickets tied to permissions, and 8 minutes to configure a role. Targets for 90 days were under 2 minutes, under 5 roles, under 10% and under 2 minutes.
The prototype disproved my own first idea. The original PRD put a scope on every permission inside a role. Clicked through, that meant a scope picker on every checkbox, and nobody in review could predict the result.
- 0Off
- 1My records
- 2My company
- 3All in building
Widest tier wins. Company codes are discarded, so “ACME and BETA, not GAMMA” cannot be stored.
role: "Visitor Admin" (Visitors module) scope: company scopeIds: ["ACME","BETA"] isAll: false
Scope types: portfolio, building, company, department, user. Roles are reusable; the same role is granted at a different scope per person.
One person, three scopes
Fig 1 · Dana, Facilities lead at ACME (example)Visitors → building isAll: true Work Orders → company [ACME, BETA] Mobile Keycard → user [dana]
Same person, three reaches. Each module role carries its own scope. Legacy could store “my company” or “all”, never “ACME and BETA but not GAMMA”.
What we did
The core decision was moving scope from the feature to the assignment. A permission belongs to a role, and the role is granted to a person with a scope: portfolio, building, company, department or user. Roles become reusable, and the same role can be granted at a different scope per person. We amended the PRD in March 2026 to one scope per assignment, which meant one picker per module row in the UI and simpler resolution on the backend.
I designed the role cards, module grid and wizards in Figma, then used Claude to build a clickable React prototype and deployed it on Vercel. It took days instead of weeks, and engineers and the customer could click through real flows before the API existed. The AI got the happy path. I corrected the scope logic, swapped generic components for our Blueprint patterns, added the empty, no-access and save-error states, and renamed system terms to what admins recognize. That prototype became the reference for the front end tickets.
We cut things on purpose. Mixed scopes inside one role went because admins could not reason about them; if you need two scopes, assign two roles. Company groups went because we had no usage data and they would have pushed the pilot out. A full notification matrix moved to its own project in favor of a small change that let v2 roles watch existing notifications.
Migration ran in shadow mode behind per-building flags. The backend ran legacy and v2 side by side, logged mismatches, and kept returning legacy until they agreed. That paid off. When the enterprise flag went on, my own admin account lost user management because the migration created duplicate entries for one person with the same rights from two legacy roles. We re-keyed the migration, rolled back and re-ran it without losing v2 data. I also missed the February date, flagged the project at risk, and reset the customer to late May rather than rushing a foundational access change.
I wrote and shipped the front end: the roles list, create and edit wizard, role detail, the per-module User Roles tab, bulk assignment, templates, and invite with an access profile.


Outcome
RBAC v2 is live with the pilot enterprise tenant. The admin UI, templates and scoped notifications have shipped, the front end milestone is at 97%, and 135 of 195 project tickets are closed. Shadow mode and QA's test plan confirmed that legacy and v2 return the same access before cutover, and rollback is tested.
The headline targets are still hypotheses, and I'm keeping them labeled that way. Custom roles dropping from about 50 to under 5 per building will be measured with a role audit 60 to 90 days after each building moves over. Permission tickets and "can see but can't act" denials are the balancing metric: fewer roles only counts if those don't rise and God mode doesn't creep back.
Running a real enterprise tenant on v2 surfaced the next two projects, which I'm leading. Parent-Child Hierarchy brings back company groups in better shape, with parent and child companies, departments and a building management company so operators stop needing God mode. Permission Cleanup splits overlapping permissions so the templates can actually get admins down to a handful of roles.