Permission models that prove they work, not just claim they work
Role-based access control is a marketing line on every SaaS site. Whether the role keeps you out of screens you should not see is a different question.
Role-based access control is a marketing line on every SaaS site. The product page lists roles. The pricing page mentions enterprise SSO. The trust page has a diagram with arrows and locks.
Whether the role you have actually keeps you out of the screens you are not supposed to see is a separate question, and the honest answer most vendors can give is “we think so.”
We chose to give a different answer. The permission matrix is a file. The test that pins the file fails the build if a non-admin role ever reaches an admin-only surface. The test that pins the data scoping fails the build if a no-scope role sees a non-zero number on a dashboard that should render zero. The shape of the claim is not “trust us.” It is “this test is green or we did not ship.”
The difference between RBAC marketing and an audit-defensible boundary
A permission claim has three layers, and most products only ship one.
The first layer is “roles exist.” The product has an admin, a manager, a member. That is the layer the marketing page shows. It is the layer that is true for almost every SaaS product, including the ones whose permission model leaks.
The second layer is “screens are gated.” A user with the manager role cannot navigate to the admin settings page. This is also usually true, because the navigation is gated client-side and most users do not paste URLs to try to bypass it.
The third layer is the one that almost nobody verifies. A user with the manager role cannot reach the admin settings page even if they paste the URL directly. They cannot call the admin API endpoint with their session cookie. They cannot read admin data through a debugging tool. They cannot see admin numbers leaking through a dashboard widget that was supposed to be hidden.
The third layer is the one auditors ask about. The honest way to answer is not a screenshot. It is a test that runs on every release and fails if the boundary leaks.
The role-by-surface matrix
The first artifact is docs/qa/permission-matrix.md. It is a canonical table. Six roles down the left side: Sales, Solutions Engineering, Individual Contributor, Delivery, Finance, Admin. Surfaces across the top: every page, every admin section, every settings panel, every integration screen. Every cell is either “allowed” or “denied.”
The matrix is the source of truth. It is not a description of how the product is supposed to work. It is the definition of how the product does work, and the test below pins it.
The finance-operational exception is documented in the matrix, not in folklore. Finance reaches the QBO integration page and the chart-of-accounts admin surfaces because finance needs them to do their job. Sales, SE, IC, and Delivery do not. Admin reaches everything because admin is admin. The exception is one cell on the matrix, not a side conversation between the product manager and the engineer.
The standing test is 337-permission-matrix. It reads the matrix. For every cell marked “denied,” it logs in as that role and asserts the surface returns a 403 or redirects. For every cell marked “allowed,” it logs in as that role and asserts the surface renders. The test runs on every release. A new admin-only page added without updating the matrix fails the test, because the new page is not in the matrix and the test does not know what to do with it. The matrix and the product cannot drift.
Dashboard data scoping is a separate problem
The matrix gates surfaces. The dashboard problem is finer-grained than that.
A dashboard might be allowed to render for every role, because the dashboard itself is not admin. The numbers on the dashboard are the question. The org total for closed-won revenue is something admin should see. A sales rep with no row scope should see zero, not the org total. A manager with a team’s scope should see their team’s number. The dashboard widget should render in all three cases, with three different numbers.
This is where naive permission models leak. The widget is gated by role. The numbers inside the widget are not. A rep loads the dashboard and sees the org total because the query that powers the widget did not respect the rep’s row scope.
The standing test is 338-dashboard-data-scoping. It is a behavioral test, not a visibility test. It logs in as a no-scope role and asserts the widget values are zero. It logs in as a scoped manager and asserts the values match the scope. It logs in as admin and asserts the values match the org total. The difference is what the screen renders, not whether the screen renders.
If a new dashboard widget gets added that does not respect row scope, the test fails. Not because the widget is invisible, but because the value the widget renders is wrong for the role looking at it.
Why this shape matters for a small firm
The buyer evaluating a small operations product does not have a red team available to verify the vendor’s claims. They have a CFO, a head of operations, and an outside consultant who reads the trust page once.
The trust page that says “RBAC supported, SOC 2 in progress” is the same shape as every competitor’s trust page. The buyer cannot tell from that page which products enforce the boundary and which products have a marketing claim with a hole behind it.
A standing test that fails the build if the matrix breaks is a different shape of claim. The buyer can read the matrix file. The buyer can ask the vendor to show the CI run. The vendor either has the test or they do not.
What PartnerView ships
A canonical six-role-by-surface permission matrix at docs/qa/permission-matrix.md. A standing test (337-permission-matrix) that fails the build if a denied cell becomes reachable. A standing test (338-dashboard-data-scoping) that fails the build if a dashboard widget renders the wrong value for the viewer’s row scope. The finance-operational exception is one row on the matrix, not folklore. Both tests run on every release. The trust framing in v5.5 is that the boundary is the test, not the claim.
A permission model that proves it works on every release is a different product than a permission model that claims to work. We chose to ship the first kind.