Busflow Docs

Internal documentation portal

Skip to content

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 ​

  1. Tenant isolation first. Every query and mutation filters by x-hasura-tenant-id. No role bypasses this (except busflow_staff).
  2. Three core roles. MANAGER, DISPATCHER, DRIVER. Additional roles (OWNER, VIEWER) are deferred to V1.1.
  3. Additive capabilities. user_access_grants extends a role β€” never restricts. MANAGER implicitly holds all capabilities.
  4. Keep it simple. Start with coarse permissions. Refine column-level or row-level restrictions when a real use case demands it.

Notation ​

SymbolMeaning
βœ…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_grants entry is needed. DISPATCHER base capabilities: DISPATCH, BOOKING_MGMT, FLEET_MGMT.

EntityMANAGERDISPATCHERDRIVERCapability GateNotes
operatorsβœοΈπŸ“–πŸ”’β€”MANAGER edits company settings. No delete (lifecycle via status).
operator_settingsβœοΈπŸ“–πŸ”’β€”1:1 with operator. No delete.
operator_integrationsβœοΈπŸ“–πŸ”’β€”Mollie, DATEV, DPA connections. No delete (lifecycle via status).
tenant_subscriptionsβœοΈπŸ”’πŸ”’β€”Billing plan state. MANAGER read/update. busflow_staff provisions during onboarding. No delete (lifecycle via status).
user_tenant_assignmentsβœ…πŸ”’πŸ”’β€”Only MANAGER manages user access.
user_access_grantsβœ…πŸ”’πŸ”’β€”Only MANAGER grants capabilities.
tour_templatesβœ…βœοΈπŸ”’DISPATCHBase DISPATCHER capability.
tour_departuresβœ…βœοΈπŸ“–DISPATCHDrivers see their assigned departures (row-scoped).
boarding_pointsβœ…βœοΈπŸ“–DISPATCHPart 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_MGMTMANAGER CRUDs. Dispatchers read for assignments (🧩 CREW_MGMT for write).
crew_qualificationsβœ…πŸ“–πŸ”’CREW_MGMTDispatchers read for dispatch validation.
crew_absencesβœ…βœοΈπŸ”’CREW_MGMTDispatchers create/approve (🧩 CREW_MGMT required for write).
vehiclesβœ…βœοΈπŸ“–FLEET_MGMTBase DISPATCHER capability. Drivers see assigned vehicle.
vehicle_inspectionsβœ…βœοΈπŸ”’FLEET_MGMTBase DISPATCHER capability.
suppliersβœ…πŸ“–πŸ”’β€”Hotel/ferry/guide partners.
allotmentsβœ…πŸ“–πŸ”’β€”Inventory blocks.
resellersβœ…πŸ“–πŸ”’β€”Agency/corporate partners.
charter_quotesβœ…βœοΈπŸ”’DISPATCHB2B quotes.
passenger_profilesβœ…βœοΈπŸ“–BOOKING_MGMTBase 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 ​

EntityMANAGERDISPATCHERDRIVERCapability GateNotes
tour_offeringsπŸ“–πŸ“–πŸ”’β€”Created by system (on TourDeparturePublished). Read-only.
bookingsβœ…βœοΈπŸ“–BOOKING_MGMTDispatchers manage bookings. Drivers see passenger count.
passengersβœ…βœοΈπŸ“–BOOKING_MGMTDrivers see manifest names.
ticketsπŸ“–πŸ“–πŸ“–β€”System-issued. All roles read.
seat_reservationsβœ…βœοΈπŸ“–BOOKING_MGMTDispatchers reassign seats.
ancillariesβœ…βœοΈπŸ”’BOOKING_MGMTAdd-ons per booking.
checkout_sessionsπŸ“–πŸ“–πŸ”’β€”System-managed payment flow. Read-only.
paymentsπŸ“–πŸ“–πŸ”’FINANCIAL_REPORTSFinancial data.
invoicesπŸ“–πŸ“–πŸ”’FINANCIAL_REPORTSSystem-generated. Read-only.
financial_ledgersπŸ“–πŸ”’πŸ”’FINANCIAL_REPORTSSensitive financial aggregation. 🧩 grant needed.
tax_ledger_entriesπŸ“–πŸ”’πŸ”’FINANCIAL_REPORTSTax data. 🧩 grant needed.

Operations Schema ​

EntityMANAGERDISPATCHERDRIVERCapability GateNotes
service_legsβœ…βœοΈβš‘DISPATCHDrivers transition via Actions (startServiceLeg, completeServiceLeg). Dispatchers cancel via cancelServiceLeg.
leg_assignmentsβœ…βœοΈπŸ“–DISPATCHDispatchers 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 TripPublished. ETA updated by system.
crew_duty_logsπŸ“–πŸ“–πŸ“–β€”System-populated. Phase 2 for write access.
change_eventsπŸ“–πŸ“–πŸ”’β€”Operations audit trail. System-written.

Communications Schema ​

EntityMANAGERDISPATCHERDRIVERCapability GateNotes
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.

EntityAccessScopeFrontend
tour_offeringsπŸ“–WHERE status = 'PUBLISHED' AND operator matched by public slugBooking widget β€” tour catalog
price_matricesπŸ“–WHERE status = 'PUBLISHED' AND linked to public tour_offeringBooking widget β€” price display
boarding_pointsπŸ“–WHERE linked to published tour_departureBooking 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):

EntityAccessScope
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.

ActionMANAGERDISPATCHERDRIVERNotes
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 ​

AccessScope
All tenant dataCross-tenant SELECT (no tenant_id filter)
provisionTenant ActionCreates operators + seeds config
User managementVia Nhost Admin SDK
Audit trailAll 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 ​

  1. Hasura role strategy: Start with 3 Hasura roles (manager, dispatcher, driver) mapping directly to user_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).

  2. 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.

  3. Column-level restrictions (deferred): Phase 1 uses table-level permissions. We can add column-level restrictions (e.g., hiding bank_details JSONB from DISPATCHER) when a concrete privacy requirement surfaces.

  4. 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.

  5. Subscription Tier Gating: The Nhost custom claims webhook (the same mechanism that injects x-hasura-tenant-id and x-hasura-role per ADR-005) also queries tenant_subscriptions.plan_id and injects X-Hasura-Plan into session variables. Premium features require explicit gating rules (tier-to-feature mapping TBD by product). The gating mechanism and strategies (including the upsertPricingConfig Action workaround for column-level limitations) are defined by ADR-030 and L3-6.2.2 (Schema Matrix Enforcement).

Internal documentation β€” Busflow