RBAC Matrix โ
Status: โ Active
Roles source: roles.md ยท ADR-005Last updated: 2026-04-17
This document defines who can do what across all Busflow entities. It is the source of truth for Hasura permission rules and NestJS @RequiresCapability() guards.
Design Principles โ
- Tenant isolation first. Every query and mutation filters by
x-hasura-tenant-id. No role bypasses this (exceptbusflow_staff). - Three core roles.
MANAGER,DISPATCHER,DRIVER. Additional roles (OWNER,VIEWER) are deferred to V1.1. - Additive capabilities.
user_access_grantsextends a role โ never restricts. MANAGER implicitly holds all capabilities. - Keep it simple. Start with coarse permissions. Refine column-level or row-level restrictions when a real use case demands it.
Notation โ
| Symbol | Meaning |
|---|---|
| โ | Full access (CRUD) |
| ๐ | Read-only |
| โ๏ธ | Create + Read + Update (no delete) |
| ๐ | No access |
| โก | Via Hasura Action only (business logic enforced server-side, no direct table mutation) |
| ๐งฉ | Requires additional capability grant |
Backoffice Schema โ
Capability Gate = the capability required for a non-MANAGER role to access. If the role already has the capability as a base grant (per roles.md), no additional
user_access_grantsentry is needed. DISPATCHER base capabilities:DISPATCH,BOOKING_MGMT,FLEET_MGMT.
| Entity | MANAGER | DISPATCHER | DRIVER | Capability Gate | Notes |
|---|---|---|---|---|---|
operators | โ๏ธ | ๐ | ๐ | โ | MANAGER edits company settings. No delete (lifecycle via status). Must omit platform_fee_config and subscription_tier from MANAGER update (column-level). |
operator_settings | โ๏ธ | ๐ | ๐ | โ | 1:1 with operator. No delete. |
operator_integrations | โ๏ธ | ๐ | ๐ | โ | Mollie, DATEV, DPA connections. No delete (lifecycle via status). |
tenant_subscriptions | โ๏ธ | ๐ | ๐ | โ | Billing plan state. busflow_staff provisions. Must omit plan_id and status from MANAGER update (column-level). The BillingModule handles state mutations. |
user_tenant_assignments | โ | ๐ | ๐ | โ | Only MANAGER manages user access. |
user_access_grants | โ | ๐ | ๐ | โ | Only MANAGER grants capabilities. |
tour_templates | โ | โ๏ธ | ๐ | DISPATCH | Base DISPATCHER capability. |
tour_departures | โ | โ๏ธ | ๐ | DISPATCH | Drivers see their assigned departures (row-scoped). |
boarding_points | โ | โ๏ธ | ๐ | DISPATCH | Part of departure setup. Drivers see stop info. |
costing_sheets | โ | ๐ | ๐ | โ | Dispatchers need read access for margin context when quoting. Full financial detail requires ๐งฉ FINANCIAL_REPORTS. |
price_matrices | โ | ๐ | ๐ | โ | Dispatchers need published prices for booking management. |
crew_members | โ | ๐ | ๐ | CREW_MGMT | MANAGER CRUDs. Dispatchers read for assignments (๐งฉ CREW_MGMT for write). |
crew_qualifications | โ | ๐ | ๐ | CREW_MGMT | Dispatchers read for dispatch validation. |
crew_absences | โ | โ๏ธ | ๐ | CREW_MGMT | Dispatchers create/approve (๐งฉ CREW_MGMT required for write). |
vehicles | โ | โ๏ธ | ๐ | FLEET_MGMT | Base DISPATCHER capability. Drivers see assigned vehicle. |
vehicle_inspections | โ | โ๏ธ | ๐ | FLEET_MGMT | Base DISPATCHER capability. |
suppliers | โ | ๐ | ๐ | โ | Hotel/ferry/guide partners. |
allotments | โ | ๐ | ๐ | โ | Inventory blocks. |
resellers | โ | ๐ | ๐ | โ | Agency/corporate partners. |
charter_quotes | โ | โ๏ธ | ๐ | DISPATCH | B2B quotes. |
person_profiles | โ | โ๏ธ | ๐ | BOOKING_MGMT | Base DISPATCHER capability. Drivers see manifest names (row-scoped). |
notification_templates | โ | ๐ | ๐ | โ | Template config is MANAGER-only. |
change_events | ๐ | ๐ | ๐ | โ | Audit trail. System-written. Drivers don't access Backoffice audits. |
Commerce Schema โ
| Entity | MANAGER | DISPATCHER | DRIVER | Capability Gate | Notes |
|---|---|---|---|---|---|
tour_offerings | ๐ | ๐ | ๐ | โ | Created by system (on TourDeparturePublished). Read-only. |
bookings | โ | โ๏ธ | ๐ | BOOKING_MGMT | Dispatchers manage bookings. Drivers see passenger count. |
passengers | โ | โ๏ธ | ๐ | BOOKING_MGMT | Drivers see manifest names. |
tickets | ๐ | ๐ | ๐ | โ | System-issued. All roles read. |
seat_reservations | โ | โ๏ธ | ๐ | BOOKING_MGMT | Dispatchers reassign seats. |
ancillaries | โ | โ๏ธ | ๐ | BOOKING_MGMT | Add-ons per booking. |
checkout_sessions | ๐ | ๐ | ๐ | โ | System-managed payment flow. Read-only. |
payments | ๐ | ๐ | ๐ | FINANCIAL_REPORTS | Financial data. |
invoices | ๐ | ๐ | ๐ | FINANCIAL_REPORTS | System-generated. Read-only. |
financial_ledgers | ๐ | ๐ | ๐ | FINANCIAL_REPORTS | Sensitive financial aggregation. ๐งฉ grant needed. |
tax_ledger_entries | ๐ | ๐ | ๐ | FINANCIAL_REPORTS | Tax data. ๐งฉ grant needed. |
Operations Schema โ
| Entity | MANAGER | DISPATCHER | DRIVER | Capability Gate | Notes |
|---|---|---|---|---|---|
service_legs | โ | โ๏ธ | โก | DISPATCH | Drivers transition via Actions (startServiceLeg, completeServiceLeg). Dispatchers cancel via cancelServiceLeg. |
leg_assignments | โ | โ๏ธ | ๐ | DISPATCH | Dispatchers assign crew. Drivers see own assignments. |
boarding_events | ๐ | ๐ | โก | โ | Drivers create via offline sync. Read-only for workspace users. |
incidents | โ๏ธ | โ๏ธ | โก | โ | Drivers create (offline). Dispatchers manage (acknowledge, assign, resolve via takeOverIncident, resolveIncident). |
issue_reports | โ๏ธ | โ๏ธ | โก | โ | Drivers create via createIssueReport. Dispatchers link/resolve via linkIssueReportToIncident, resolveIssueReport. |
incident_issue_reports | ๐ | โ๏ธ | ๐ | โ | Join table. Created by Actions. |
onboard_sales | ๐ | โ๏ธ | โก | โ | Drivers create (offline sync). Dispatchers can void/refund. |
expense_receipts | ๐ | โ๏ธ | โก | โ | Drivers submit. Dispatchers verify/reject (verifyExpenseReceipt, rejectExpenseReceipt). |
telemetry_points | ๐ | ๐ | โก | โ | Drivers push GPS data (POST /api/telemetry). System-managed. |
route_waypoints | ๐ | ๐ | ๐ | โ | System-created via TourDeparturePublished. ETA updated by system. |
crew_duty_logs | ๐ | ๐ | ๐ | โ | System-populated. Phase 2 for write access. |
change_events | ๐ | ๐ | ๐ | โ | Operations audit trail. System-written. |
Communications Schema โ
| Entity | MANAGER | DISPATCHER | DRIVER | Capability Gate | Notes |
|---|---|---|---|---|---|
conversations | ๐ | ๐ | ๐ | โ | System-created for broadcast threads. |
messages | ๐ | โ๏ธ | ๐ | โ | Dispatchers approve broadcasts, view message history. |
contacts | ๐ | ๐ | ๐ | โ | System-managed (passenger-facing). |
channel_accounts | โ | ๐ | ๐ | โ | WhatsApp/Email provider config. MANAGER-only. |
Public & Passenger Access โ
These roles operate outside the tenant RBAC system. They don't use user_tenant_assignments or x-hasura-tenant-id.
anonymous (Hasura public role) โ
The Hasura anonymous role serves unauthenticated frontends: the booking widget, landing page, and marketing site. The system scopes permissions by the operator's public slug or domain โ no JWT required.
| Entity | Access | Scope | Frontend |
|---|---|---|---|
tour_offerings | ๐ | WHERE status = 'PUBLISHED' AND operator matched by public slug | Booking widget โ tour catalog |
price_matrices | ๐ | WHERE status = 'PUBLISHED' AND linked to public tour_offering | Booking widget โ price display |
boarding_points | ๐ | WHERE linked to published tour_departure | Booking widget โ pickup selector |
seat_reservations | ๐ | Availability count only (no passenger data) | Booking widget โ seat picker |
bookings | โ๏ธ | CREATE only (via Hasura Action createBooking) | Booking widget โ checkout flow |
NOTE
Booking widget tenant resolution: The widget identifies the operator via a public slug in the URL (e.g., book.busflow.de/reisebuero-mueller). The server resolves slug โ tenant_id and applies it as the implicit filter. No x-hasura-tenant-id in the JWT โ anonymous requests don't have JWTs.
passenger [future] โ
Passengers interact with booking-widget (pre-trip) and passenger portal (post-booking). Authentication is lightweight โ email + booking code or magic link, not traditional login. The identity model (Nhost auth.users vs. standalone token service) is an open architectural decision โ see roles.md ยงPassengers. A dedicated B2C auth specification will define token TTLs, flow diagrams, and Hasura role configuration.
Expected data scope (pending auth spec):
| Entity | Access | Scope |
|---|---|---|
bookings | ๐ | Own booking(s) only |
passengers | ๐ | Own passenger record only |
tickets | ๐ | Own tickets (QR code, Wallet pass) |
boarding_points | ๐ | Linked to own booking's departure |
Consumer ETA Tracking (token-secured, no auth) โ
GET /api/track/:tracking_token โ no Hasura role. This is a standalone NestJS endpoint secured by a short-lived JWT tracking token (not a Hasura session). Returns vehicle position + ETA. See schema-operations.md ยงConsumer ETA Tracking.
NOTE
Why not a Hasura role? The tracking endpoint serves data from multiple schemas (Operations telemetry_points + route_waypoints) to unauthenticated passengers via a signed token. It's simpler and more secure as a dedicated NestJS route than as a Hasura permission on the anonymous role โ the token carries the specific service_leg_id + passenger_id scope, and the response is a computed aggregate, not a raw table read.
Hasura Actions โ Role Matrix โ
Actions enforce business logic server-side. The handler validates the requesting user's role and capabilities.
| Action | MANAGER | DISPATCHER | DRIVER | Notes |
|---|---|---|---|---|
provisionTenant | ๐ | ๐ | ๐ | busflow_staff only |
startServiceLeg | ๐ | ๐ | โ | Guard: active LegAssignment for requesting crew member |
completeServiceLeg | ๐ | ๐ | โ | Guard: active LegAssignment |
cancelServiceLeg | โ | โ | ๐ | Guard: MANAGER required if boarding events exist |
takeOverIncident | โ | โ | ๐ | Dispatcher acknowledges + takes ownership |
resolveIncident | โ | โ | ๐ | Any DISPATCHER/MANAGER (no assigned_to check โ shift handoff) |
createIssueReport | โ | ๐ | โ | Per schema: x-hasura-role โ { DRIVER, MANAGER }. Offline-capable via sync. |
linkIssueReportToIncident | โ | โ | ๐ | Dispatcher links report โ incident |
resolveIssueReport | โ | โ | ๐ | Dispatcher resolves (OPEN only; IN_PROGRESS auto-resolves via inspection) |
verifyExpenseReceipt | โ | โ | ๐ | Dispatcher confirms OCR output |
rejectExpenseReceipt | โ | โ | ๐ | Dispatcher rejects with mandatory reason |
swapVehicle | โ | โ | ๐ | Guard: requires FLEET_MGMT capability (DISPATCHER base) |
upsertPricingConfig | โ | โก | ๐ | Guard: requires premium X-Hasura-Plan (tier values TBD by product). Used to update pricing_rules on tour_templates. |
Driver Data Scoping โ
Drivers access a narrow slice of data, scoped to their assignments:
Driver sees:
tour_departures WHERE id IN (leg_assignments.tour_departure_id WHERE crew_member_id = me)
service_legs WHERE id IN (my leg_assignments)
boarding_events WHERE service_leg_id IN (my legs)
passengers WHERE booking.tour_offering_id IN (my tour_offerings)
boarding_points WHERE tour_departure_id IN (my departures)
vehicles WHERE id IN (my leg_assignments.vehicle_id)NOTE
Hasura row-level permissions implement this scoping. The x-hasura-user-id session variable resolves the driver's crew_member_id via crew_members.user_id, then filters through leg_assignments. This avoids exposing any data from other drivers' assignments or other tenants.
Platform Role: busflow_staff โ
| Access | Scope |
|---|---|
| All tenant data | Cross-tenant SELECT (no tenant_id filter) |
provisionTenant Action | Creates operators + seeds config |
| User management | Via Nhost Admin SDK |
| Audit trail | All mutations logged with user_id + correlation_id |
WARNING
No direct table mutations. Busflow staff mutate via Actions only (enforced by Hasura permission rules). All staff actions generate change_events with scope = CONFIG.
Implementation Notes โ
Hasura role strategy: Start with 3 Hasura roles (
manager,dispatcher,driver) mapping directly touser_tenant_assignments.default_role. Evaluate Hasura inherited roles during implementation if capability-based granularity is needed at the Hasura layer (see ADR-005 ยงRBAC Warning).Capability resolution: Happens at the NestJS application layer via
@RequiresCapability()decorator. Hasura permissions handle role + tenant scoping; NestJS handles fine-grained capability checks on Actions.Column-level restrictions (deferred): Phase 1 uses table-level permissions. We can add column-level restrictions (e.g., hiding
bank_detailsJSONB from DISPATCHER) when a concrete privacy requirement surfaces.Row-level for Drivers: Driver permissions use relationship-based filtering (through
leg_assignmentsโservice_legsโ etc.). This is the most complex Hasura permission configuration โ test thoroughly with multi-driver, multi-departure scenarios.Subscription Tier Gating: The Nhost custom claims webhook (the same mechanism that injects
x-hasura-tenant-idandx-hasura-roleper ADR-005) also queriestenant_subscriptions.plan_idand injectsX-Hasura-Planinto session variables. Premium features require explicit gating rules (tier-to-feature mapping TBD by product). The gating mechanism and strategies (including theupsertPricingConfigAction workaround for column-level limitations) are defined by ADR-030 and L3-6.2.2 (Schema Matrix Enforcement).