Busflow Docs

Internal documentation portal

Skip to content
Reviewed 02 May 2026

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). 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โœ…โœ๏ธ๐Ÿ”’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.
person_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 TourDeparturePublished. 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