Practice management software has historically been built for single firms. The moment you scale out to dozens or hundreds of firms on the same platform, the rules change. Every read needs a tenant filter. Every write needs a tenant context. And the worst class of bug is the one nobody sees until a customer reports a leak.
We solved that by making tenant resolution a first-class request concern: every request resolves a tenant from the subdomain before any business logic runs. Application code never asks 'which tenant is this?' — it cannot, by construction. Data access is filtered at the infrastructure layer, and the resolved tenant is carried through the entire request pipeline.
The second decision was permissions over roles. Roles are convenient labels — Partner, Manager, Associate — but they accumulate semantic debt. Two firms will define the same role differently within a year. Permissions don't lie: a user either has the claim to advance a stage or they don't. We resolve permissions per request and check them at every endpoint.
The combination is boring in the best way. Engineers ship features without thinking about tenant boundaries, and security reviews happen at the framework layer rather than at every PR.