Automatic isolation, not manual discipline
A global scope filters every query by organization the moment a model boots, with nothing for a developer to remember and no single query that can forget it, including the ones behind route links.
Security
No developer has to remember to filter a query by organization. A global scope does it automatically, on every model that needs it, so a stale or guessed id for someone else's data resolves to nothing, not a leak.
Each one closes a different way a multi-tenant platform usually leaks, quietly, until someone goes looking.
A global scope filters every query by organization the moment a model boots, with nothing for a developer to remember and no single query that can forget it, including the ones behind route links.
Admin access gets checked on every request, not just at login, so suspending an organization locks its team out on their very next click. Its products stop being publicly reachable at the same moment.
Voiding a ticket or cancelling an order requires its specific permission rechecked at the exact moment of the action, independent of whatever permission got someone onto the screen in the first place.
Cost and margin are never written into a storefront or gate-scan response. It isn't hidden behind a flag that could be misconfigured later; the field simply never exists in what gets returned.
How it holds
Five mechanisms, each closing a different way isolation usually breaks under real use.
A global scope adds the organization filter to every query issued on a model that carries it, applied the moment the model boots, with no filter to remember and no way for a single query to skip it.
Route links rely on that same scope, so a link to another organization's record resolves to a plain 404 before any handler code even runs, never a permission error that would confirm the record exists.
The scope alone protects reads and deletes, but not a blind write to a foreign id, so an id going into a pivot table or a foreign key column gets re-resolved through its own target's scoped query first.
Every sensitive action re-runs a live permission check against the database when it actually happens, not once at page load, so a permission revoked mid-session stops working on the very next click, not the next login.
Deleting a product, option or ticket category with real sales gets refused twice: the application layer explains why before even attempting it, and the database's own foreign key is declared to block that same delete, so a path that skipped the check would still fail.
The detail, as it is
The response a scanner sees is a hand-written list of exactly what gate staff needs. Cost, net price and margin were never added to it, so there is nothing to accidentally expose later.
{
"returned_to_gate_staff": {
"ticket_code": "AB12CD34EF",
"status": "redeemed",
"product_name": "City Food Tour",
"buyer_name": "Jane Buyer",
"position": 2,
"quantity": 3
},
"never_included": [
"cost_amount",
"net_amount",
"margin"
]
}The detail almost nobody explains
An audit found eight models reachable by guessing an id from another organization: six governing pricing and availability rules, two governing tax assignments. None carried their own organization filter; each inherited one from a parent relation that a raw lookup by a client-supplied id skipped entirely. Every one of them now carries its own organization id, closing that exact class of gap for good, and a dedicated test suite keeps it closed by attempting precisely that guess, on every one of the eight, every time the suite runs.
No. Every model that needs it carries a global scope filtering by organization automatically, which also protects route links: a stale or guessed id for another organization's record resolves to a 404, never a permission error that would confirm the record exists.
Its admin team loses panel access on their very next request, not just their next login, and its products stop being reachable through the public API at the same moment. Suspension is enforced, not just recorded.
Yes. Every sensitive action re-checks the permission live against the database at the moment it runs, not a role cached when the page first loaded. The very next click after a revocation gets blocked.
No, twice over. The application layer refuses the delete with a clear explanation before even attempting it, and the database's own foreign key is declared to restrict that same delete, so even a path that bypassed the application check would still fail.
No. Those responses are hand-written to include only what the recipient actually needs. The field is never written into the response in the first place, not hidden behind a flag that could be misconfigured.
Tiqory keeps availability, pricing, payments and access working together as your operation grows. Tell us where you are headed; we’ll show you how to get there without starting over.