Shipmind Labs

A permission check that returns False tells you nothing. It tells the user nothing, it tells your support engineer nothing, and it tells the next developer nothing.

That is the part teams underestimate.

The access rule itself is usually simple: this actor, this permission, does this object belong to them. The expensive part shows up months later, when a customer says "I should be able to edit this" and nobody can answer without opening the code and reconstructing the decision by hand.

So we changed what a check returns.

Not a boolean. A denial that explains itself: which actor was evaluated, which permission was required, which ownership rule failed. Support reads the answer instead of escalating it. The engineer debugging a report gets the reason, not a stack trace hunt.

Declaring actors and permissions explicitly buys a second thing we did not expect at first. Once the rules are data rather than scattered conditionals, the same rules can drive the admin interface. In our own Django work that means an admin adapter separated by role — a compliance reviewer and an operations user see different objects and different actions, from the identical rule set that guards the API.

We extracted this into an open-source package we call role-scopes, because we kept rebuilding it. Compliance tooling made the need obvious: when a regulator or an auditor asks why someone was allowed to approve something, "the code said no" is not an answer.

One rule set, two consumers, and every denial able to state its own reason.

How does your system answer the question "why was this user denied?" — does it come from the code, or from someone reading it?

Was this useful?

Building something similar?

or email hello@shipmindlabs.com