Multi-Tenancy
Every agency in the system is a row in the agencies table. The agency_type column determines its role:
agency_type | Role |
|---|---|
psap | A 911/dispatch center — creates incidents, owns CFS configurations, escalation rules, and notification rules |
company | A third-party response company — receives notifications, manages users and transport preferences |
There is no shared data between agencies. Every agency-scoped entity carries an agency_id foreign key, and all repository queries enforce that scope automatically.
AgencyAwareRepository
AgencyAwareRepository is the base class for all repositories. It holds the currently authenticated agency (resolved from the JWT or API key at request time) and automatically appends AND agency_id = :currentAgency to every query.
Rules:
- All repositories must extend
AgencyAwareRepository - Never call
findAll()orfindBy([])without an agency scope unless in a superadmin context - The superadmin context bypasses agency scoping explicitly — this is not the default
Roles
| Slug | Who | Key permissions |
|---|---|---|
superadmin | Platform admin | Full access to all agencies and settings |
psap_admin | PSAP administrator | Manage PSAP settings, TCOs, transport channels, escalation/notification rules |
psap_agent | TCO (Telecommunicator) | Create incidents, monitor PSAP dashboard, ACK company replies via web UI |
company_admin | Company administrator | Manage company info, users, escalation hierarchy |
company_user | Company responder | Manage own transport preferences, receive/ACK notifications, send replies |
viewer | Read-only observer | View incidents and notifications |
All authorization decisions go through Symfony Voters — never raw role string checks in controllers.