Skip to content

Permissions — Canonical Matrix

This file is the single source of truth for roles, permissions, and who-can-do-what. A drift test (src/test/permissions.matrix.test.ts) parses this file and fails the build if the code uses a permission that isn't declared here, or if this file declares a permission no code references. If you add a page/section/action, update this file — the test will fail otherwise.

Last audited: 2026-04-16 (enterprise rebuild)


1. How to use this document

  • Adding a new page, section, or action? Add a row to the relevant section in §6. If the action introduces a new permission name, add it to §4 (catalog) and §5 (role grants).
  • Changing who can do what? Update §5. Update the seed migration (supabase/migrations/*seed*permissions*.sql). Run npm test to confirm the drift test still passes.
  • Removing a permission? Remove every reference from code first (grep permission="x.y", requirePermission('x.y')), then remove from §4/§5, then add a DROP migration.

2. Architecture

Permission enforcement happens at three layers:

  1. Route-level (client) — <ProtectedRoute requiredPermissions={[...]}> in src/App.tsx. Gates whole pages.
  2. UI-level (client) — <PermissionGate permission="..."> wrapping individual buttons/actions in component trees.
  3. API-level (server) — requirePermission('...') in src/lib/api.ts handler functions. The authoritative check. Also backed by Postgres RLS policies on sensitive tables (Booking, Customer, Agent, Supplier, Payment, JournalEntry, JournalLine, FinanceConfig) via auth_user_has_permission(perm_name).

The SQL function order of precedence (post 20260416020000): 1. explicit user-level deny (UserPermission.allowed=false) → denied 2. explicit user-level allow (UserPermission.allowed=true) → granted 3. role grant (RolePermission) → granted 4. otherwise → denied

No role-name bypasses. CEO/GM/IT_ADMIN are super-admins by virtue of being seeded every permission row explicitly — not by a hardcoded short-circuit. Revoking a specific row in the PermissionsMatrix UI actually restricts them.


3. Role catalog

19 roles total — 4 admin-tier, 13 functional staff, 2 portal-scoped (partner/customer).

Role Tier Purpose Auto-grant?
CEO super-admin Chief executive; ultimate authority Seeded every permission (20260416020000)
GM super-admin General Manager; operational head Seeded every permission (20260416020000)
IT_ADMIN super-admin System administrator; tech owner Seeded every permission (20260416020000)
ADMIN_HR admin HR/admin operations Seeded most permissions (20260115000000 + 20260402210000 + 20260415210000 + 20260416010000)
SALES_MANAGER functional Sales team lead Sales bundle
SALES_EXEC functional Sales representative Sales-minimal bundle
B2B_MANAGER functional B2B/partner account manager Partner + sales bundle
B2B_EXEC functional B2B sales rep B2B-minimal bundle
OPS_MANAGER functional Operations lead (groups, inventory) Operations bundle
OPS_EXEC functional Operations staff Operations-minimal bundle
FINANCE_MANAGER functional Finance lead Finance + approvals bundle
ACCOUNTANT functional Bookkeeper Finance bundle
CASHIER functional Payments + receipts only Cashier bundle
TICKET_MANAGER functional Ticketing lead Tickets + reports
VISA_OFFICER functional Visa processing Visa bundle
AUDITOR functional Read-only oversight bookings.view, reports.export
AGENT portal B2B partner user (external) Portal-only; no staff permissions
CUSTOMER portal End customer (external) Portal-only; no staff permissions

Custom roles can be created via the Admin → Permissions UI. They start with zero permissions and must be granted explicitly.


4. Permission catalog

Permission name format: <module>.<action> or <module>.<sub-resource>.<action>. Every permission here MUST be inserted into the Permission table via a seed migration, and MUST be referenced in code (otherwise the drift test fails).

4.1 Core staff modules

Module Permission Meaning Enforced at
agents (B2B partners) agents.view List/read B2B partners Route /agents, Sidebar
agents.create Create new partner <PermissionGate> on "Add Business Partner"
approvals approvals.view See the approvals queue Route /approvals, Sidebar
approvals.approve Approve/reject/send-back bookings <PermissionGate> in Approvals.tsx + server-side
bookings bookings.view List/read bookings Route /sales/bookings, /sales/bookings/:id
bookings.create Create new booking (wizard, import) Route /sales/bookings/new, API POST
bookings.edit Modify booking fields, add passengers <PermissionGate>, API PATCH
bookings.delete Soft/hard-delete booking <PermissionGate>, API DELETE
customers customers.view List/read customers Route /sales/customers, /people
customers.create Create new customer <PermissionGate>, wizard customer creation
customers.edit Edit customer profile, documents <PermissionGate>, API PATCH
customers.delete Delete customer record <PermissionGate>, API DELETE
finance finance.view See Finance module / reports / vouchers Route /finance
finance.create Record payments, post journals <PermissionGate> on "Add Payment", journal composer
finance.edit Edit finance config, reconcile RLS on FinanceConfig, Payment/Journal updates
finance.payments.record Post a payment receipt to ledger API POST /finance/vouchers/agent-receipt-allocate
groups groups.view List/read travel groups Route /groups
groups.create Create new group <PermissionGate> on "Create Group"
groups.edit Edit group, reassign passengers, link/unlink flights, misc expenses <PermissionGate> + API PATCH/POST
groups.delete Soft-delete group API DELETE /groups/:id
group_pricing group_pricing.edit Edit the rate sheet on the group's Pricing tab (4 line items + tax lines) API PATCH /groups/:id/pricing, <PermissionGate> on Pricing tab save
group_invoices group_invoices.view See group invoices tab + individual invoices Route (Invoices tab on group), API GET
group_invoices.create Create a draft invoice from a group payer <PermissionGate> on "Issue Invoice", API POST
group_invoices.edit Edit a DRAFT invoice (line items, taxes, due date) <PermissionGate> + API PATCH (server rejects on ISSUED)
group_invoices.issue Transition DRAFT → ISSUED and post the revenue + tax journal <PermissionGate> + API POST /group-invoices/:id/issue
group_invoices.cancel Cancel an ISSUED invoice (posts reversing credit-note journal) <PermissionGate> + API POST /group-invoices/:id/cancel
hotels hotels.view List/read hotels Route /hotels, Sidebar
hotels.create Create hotel/facility <PermissionGate>
hotels.edit Update hotel fields, contracts <PermissionGate>
hotels.delete Remove hotel <PermissionGate>
hotels.export Export/print hotel reports <PermissionGate> on export buttons
inventory inventory.view Access inventory module (airline blocks, FIT, ground, B2B) Route /inventory/*, /suppliers
inventory.create Create airline block / FIT / ground transfer / B2B offer <PermissionGate>
inventory.edit Edit inventory record <PermissionGate>, inline forms
leads leads.view List/read leads Route /sales/leads
leads.edit Update lead status, edit fields <PermissionGate> in detail dialog
partners partners.create Create partner (alt to agents) Legacy; kept for RLS policies
partners.edit Edit partner Legacy alias
partners.delete Delete partner Legacy alias
quotations quotations.view List/read quotations Route /sales/quotations
quotations.create Create new quotation; also used for edit/send/delete until split <PermissionGate>
reports reports.view Access analytics / reports pages Route /reports
reports.export Download/print any report <PermissionGate> on export buttons
requests requests.view See service requests queue Route /requests
requests.create Create a service request (ops-facing; customer/agent portals are self-service) requirePermission in API
requests.edit Update or add notes to a service request requirePermission in API
suppliers suppliers.create Add supplier <PermissionGate>
suppliers.edit Edit supplier, record transactions <PermissionGate>
suppliers.delete Remove supplier <PermissionGate>
tickets tickets.view List/read tickets Route /tickets
tickets.edit Update passenger names, upload docs, issue <PermissionGate>
tickets.approve Approve exception request, issue ticket <PermissionGate>
visa visa.view List/read visa cases Route /visa
visa.create Create new visa case <PermissionGate>
visa.edit Update case status, upload/delete docs <PermissionGate>

4.2 Admin module

Permission Meaning Enforced at
admin.view Access /admin and /people/employees Route guards

Legacy admin.edit kept for backwards-compat with existing RLS. New code should use the specific admin.* permissions defined in §4.4 below.

4.3 Cross-cutting (dashboard, chat)

Permission Meaning Enforced at
dashboard.view Show Dashboard nav item and KPIs Sidebar nav filter
chat.view Access internal chat Route /internal/chat

4.4 Granular sectional permissions (phase 1)

The coarse finance.view / admin.view gate the ROUTE. These sectional permissions gate individual sections inside those pages so an admin can e.g. hide Profit & Loss from an Accountant while still letting them see Trial Balance. A user needs BOTH the parent route permission AND the sectional permission.

Finance reports — each has independent view + export:

Permission Meaning
finance.reports.trial_balance.view Trial Balance report
finance.reports.trial_balance.export Export Trial Balance
finance.reports.day_book.view Day Book report
finance.reports.day_book.export Export Day Book
finance.reports.balance_sheet.view Balance Sheet
finance.reports.balance_sheet.export Export Balance Sheet
finance.reports.profit_loss.view Profit & Loss
finance.reports.profit_loss.export Export P&L
finance.reports.group_profit_loss.view Group Profitability
finance.reports.group_profit_loss.export Export Group P&L
finance.reports.aging.view Aging Report
finance.reports.aging.export Export Aging
finance.reports.receivables_payables.view Receivables & Payables summary
finance.reports.receivables_payables.export Export R&P
finance.reports.gst_summary.view GST Summary
finance.reports.gst_summary.export Export GST
finance.reports.stock_status.view Stock Status (inventory valuation)
finance.reports.stock_status.export Export Stock Status
finance.reports.group_status.view Group Status
finance.reports.group_status.export Export Group Status
finance.reports.settlements.view Settlement Report
finance.reports.settlements.export Export Settlements

Finance — TDS (Tax Deducted at Source):

Permission Meaning
finance.tds.view View TDS rate master and deduction ledger
finance.tds.deduct Apply TDS on a supplier payment
finance.tds.export Export the quarterly 26Q CSV

Finance workflow — split from the coarse finance.create / approvals.approve:

Permission Meaning
finance.payments.verify Verify a pending payment
finance.payments.reject Reject a pending payment
finance.payments.refund Issue a refund
finance.journals.approve Approve a manual journal entry
finance.journals.approve_own Override: bypass maker-checker to approve/reject a journal YOU created. Super-admin only
finance.journals.reject Reject a manual journal entry
finance.journals.bulk_approve Bulk approve journals
finance.journals.reverse Post a reversal voucher
finance.journals.reverse_own Override: bypass maker-checker to reverse a journal YOU created. Super-admin only
finance.bookings.approve_finance Finance-tier booking approval
finance.bookings.reject_finance Finance-tier booking rejection
finance.cancellations.approve_airline Approve airline cancellation refund
finance.cancellations.approve_b2b Approve B2B buyer cancellation
finance.allocations.agent_receipt Allocate on-account agent receipt
finance.allocations.supplier_advance Allocate supplier advance
finance.stock_adjustment.post Post opening/closing stock adjustment
finance.ledger.rebuild Rebuild ledger from source
finance.config.edit Edit finance config (prefixes, GL defaults)

Finance period/year management:

Permission Meaning
finance.periods.create Create accounting period
finance.periods.lock Lock accounting period
finance.periods.unlock Unlock period
finance.periods.close Permanently close period (exec-level)
finance.years.create Create financial year
finance.years.close Close financial year (exec-level)

Admin — split from the monolithic admin.view:

Permission Meaning
admin.dashboard.view Admin landing dashboard
admin.audit.view Audit log access
admin.audit.export Export audit log
admin.currency.view View currencies
admin.currency.create Create currency
admin.currency.edit Edit currency
admin.currency.delete Delete currency
admin.cities.view View service cities
admin.cities.create Create city
admin.cities.edit Edit city
admin.cities.delete Delete city
admin.locations.view India locations hierarchy
admin.integrations.view View email/WhatsApp/SMS config
admin.integrations.edit Edit integrations
admin.integrations.test Send test messages
admin.config.edit System config (branding, numbering)
admin.permissions.view View permissions matrix
admin.permissions.edit Edit permissions matrix (privilege escalation — super-admin only)
admin.users.view View staff users
admin.users.create Create staff user
admin.users.edit Edit staff user role
admin.users.delete Delete staff user (super-admin only)
admin.users.reset_password Reset staff password
admin.roles.view View custom roles
admin.roles.create Create custom role
admin.roles.edit Edit custom role
admin.roles.delete Delete custom role (super-admin only)

Agents — split from coarse agents.*:

Permission Meaning
agents.deactivate Soft-disable partner
agents.credit_limit.set Modify credit limit
agents.commission.set Modify commission rate
agents.access_key.manage Create/reset partner portal login
agents.ledger.view Partner ledger access
agents.ledger.export Export partner ledger
agents.allocate_receipt Allocate on-account receipt to bookings

4.5 Portal-scoped permissions (NON-STAFF)

Portal users (role AGENT or CUSTOMER) authenticate into their own scoped APIs. Their data access is enforced by ownership checks in the handler (e.g. customerId === auth.uid()), not by permissions in the matrix. The staff permission matrix does NOT apply to portal users — they get zero staff permissions.

Portal users are gated only by allowedRoles on their routes: - /customer/**allowedRoles={['customer']} - /partner/**allowedRoles={['agent']}

Actions available inside portals are tracked in §6.10 (Customer Portal) and §6.11 (Partner Portal) but are NOT enforced against the matrix — only against ownership + role.


5. Role → permission grants

Canonical grants (as seeded by migrations through 20260416020000). Legend: = granted, - = not granted.

5.1 Super-admin & admin tiers

Permission CEO GM IT_ADMIN ADMIN_HR
agents.*
approvals.*
bookings.*
customers.*
finance.*
groups.*
hotels.*
inventory.*
leads.*
partners.*
quotations.*
reports.*
requests.*
suppliers.*
tickets.*
visa.*
admin.*
chat.view
finance.journals.approve_own -
finance.journals.reverse_own -

Maker-checker overrides. finance.journals.approve_own and finance.journals.reverse_own are break-glass permissions that let the holder approve/reject or reverse a journal entry they themselves created, bypassing the default segregation-of-duties rule. They are granted ONLY to the three super-admin roles (CEO/GM/IT_ADMIN). ADMIN_HR has every other finance permission but NOT these — HR is not an approver of record. No other functional role holds them; FINANCE_MANAGER or ACCOUNTANT must route their own journals to a different approver.

5.2 Functional staff (abbreviated — canonical bundles)

Role Grants
SALES_MANAGER bookings.*, customers.create/edit/delete, quotations.create, leads.edit, groups.create, reports.export, group_pricing.edit, group_invoices.create, group_invoices.edit + bookings.view, customers.view, quotations.view, leads.view, group_invoices.view
SALES_EXEC bookings.*, customers.create/edit, quotations.create, leads.edit + bookings.view, customers.view, quotations.view, leads.view, group_invoices.view
B2B_MANAGER bookings., partners., agents.create, customers.create, quotations.create, reports.export, group_pricing.edit, group_invoices.create, group_invoices.edit + agents.view, bookings.view, customers.view, quotations.view, group_invoices.view
B2B_EXEC bookings.*, partners.edit, customers.create, quotations.create + agents.view, bookings.view, customers.view, group_invoices.view
OPS_MANAGER bookings.view, suppliers., hotels., inventory.*, groups.create, reports.export, approvals.approve + bookings.view, inventory.view, approvals.view, group_invoices.view
OPS_EXEC bookings.view, suppliers.edit, hotels.edit, hotels.export, inventory.edit + bookings.view, inventory.view, group_invoices.view
FINANCE_MANAGER finance.*, finance.payments.record, approvals.approve, reports.export, group_pricing.edit, group_invoices.create, group_invoices.edit, group_invoices.issue, group_invoices.cancel + finance.view, bookings.view, approvals.view, group_invoices.view
ACCOUNTANT finance.*, finance.payments.record, reports.export, group_invoices.create, group_invoices.edit + finance.view, bookings.view, group_invoices.view
CASHIER finance.create, finance.payments.record + finance.view, bookings.view
TICKET_MANAGER tickets.edit, tickets.approve, reports.export + tickets.view, bookings.view
VISA_OFFICER visa.create, visa.edit + visa.view, bookings.view
AUDITOR reports.export + all *.view

5.3 Portal roles

Role Staff-matrix grants
AGENT none (portal-scoped via route allowedRoles + ownership)
CUSTOMER none (portal-scoped via route allowedRoles + ownership)

6. Page-by-page action matrix

This is the inventory of every user-triggerable action, the permission it requires, and where the enforcement lives. The drift test confirms that every permission string here actually appears in code, and vice versa.

6.1 Sales → Customers (/sales/customers)

Section Action Permission Enforcement
Route View page customers.view <ProtectedRoute>
Header Sync customers customers.edit (should gate — currently none)
Header Export CSV customers.view client-only
Header Import CSV customers.create <PermissionGate> recommended
Header Create customer customers.create <PermissionGate>
Row Edit customer customers.edit <PermissionGate>
Row Delete customer customers.delete <PermissionGate>
Profile Upload doc to Drive customers.edit server-side
Profile Delete Drive doc customers.edit server-side
Profile View ledger customers.view server-side scoping

6.2 Sales → Leads (/sales/leads)

Section Action Permission
Route View page leads.view
Header Create lead leads.edit
Detail Update status leads.edit
Detail Convert to quotation quotations.create
Detail Convert to booking bookings.create

6.3 Sales → Quotations (/sales/quotations)

Section Action Permission
Route View page quotations.view
Header Create quotation quotations.create
Row Edit quotations.create (treat as edit, currently same perm)
Row Send to customer quotations.create
Row Delete quotations.create (destructive — consider quotations.delete future)

6.4 Sales → Bookings (/sales/bookings) + Booking Detail (/sales/bookings/:id) + Wizard (/sales/bookings/new)

Section Action Permission
Route (list) View page bookings.view
Route (detail) View page bookings.view
Route (wizard) Create new bookings.create
List header Create booking bookings.create
List header Import bookings (CSV/XLSX) bookings.create
List row Print invoice bookings.view
Detail / List Edit booking bookings.edit
Detail / List Add passengers bookings.edit
Detail / List Send message / WhatsApp bookings.view
Detail / List Add payment finance.create
Detail / List Request visa case visa.create
Detail / List Send back (to ops) approvals.approve
Detail / List Resubmit to finance bookings.edit
Detail / List Delete booking bookings.delete
Detail Customer ledger customers.view

6.5 Groups (/groups)

Section Action Permission
Route View page groups.view
Header Create group groups.create
Card Delete group groups.delete
Passenger Mark group leader groups.edit
Passenger Move to another group groups.edit
Passenger Drop from group groups.edit
Flights tab Link flight groups.edit
Flights tab Unlink flight groups.edit
Hotels tab Assign hotel groups.edit
Ground tab Assign transfer groups.edit
Pricing tab View rate sheet groups.view
Pricing tab Edit line items + tax lines (save) group_pricing.edit
Pricing tab Refresh rates from inventory group_pricing.edit
Invoices tab View payers + their invoices group_invoices.view
Invoices tab Create draft invoice for a payer group_invoices.create
Invoices tab Edit draft invoice group_invoices.edit
Invoices tab Issue draft → posts ledger group_invoices.issue
Invoices tab Create supplementary for issued invoice group_invoices.create
Invoices tab Cancel issued invoice (credit note) group_invoices.cancel

6.6 Approvals (/approvals) + Corrections (/corrections)

Section Action Permission
Route /approvals View queue approvals.view
Route /corrections View queue approvals.view (currently bookings.view — drift; see §8)
Row Approve booking approvals.approve
Row Reject booking approvals.approve
Row Send back for correction approvals.approve
Row Log communication approvals.view
Row Assign correction owner approvals.approve
Row Resubmit corrected bookings.edit

6.7 Visa (/visa, /visa/:id) + Tickets (/tickets, /tickets/:id)

Section Action Permission
Route /visa View pipeline visa.view
Route /visa/:id View case visa.view
Header New visa case visa.create
Header Upload document visa.edit
Header Sync visa cases visa.edit
Row Bulk status change visa.edit
Row Per-row status visa.edit
Docs dialog Delete visa doc visa.edit
Route /tickets View queue tickets.view
Route /tickets/:id View detail tickets.view
Header Sync tickets tickets.edit
Header Print report tickets.view
Tab Bulk update names tickets.edit
Row Update passenger name tickets.edit
Row Request exception tickets.edit
Row Approve exception tickets.approve
Row Issue ticket tickets.approve
Detail Upload ticket doc tickets.edit

6.8 Finance (/finance)

Section Action Permission
Route View module finance.view
Accounts Create GL account finance.edit
Accounts Edit GL account finance.edit
Accounts Delete GL account finance.edit
Accounts View ledger drill-down finance.view
Payments Verify payment finance.create
Payments Reject payment finance.create
Payments Record payment finance.create
Payments Refund payment finance.edit
Vouchers Post receipt / payment / contra finance.create
Journal Create manual journal finance.create
Journal Reverse journal finance.edit
Journal Approve manual journal approvals.approve
Approvals Approve booking (finance) approvals.approve
Approvals Reject booking (finance) approvals.approve
Cancellations Approve B2B cancellation approvals.approve
Cancellations Approve airline cancellation approvals.approve
Allocate Allocate agent receipt finance.payments.record
Reports View trial balance, P&L, balance sheet, day book, aging finance.view
Reports Export any reports.export
Settings Create/lock/close period finance.edit
Settings Rebuild ledger / quota ledger finance.edit
Settings Save finance config finance.edit
Settings Stock adjustment finance.edit
TDS View TDS rate master / deduction ledger finance.tds.view
TDS Export 26Q CSV finance.tds.export

6.9 Partners (admin) (/agents)

Section Action Permission
Route View partners agents.view
Header Add partner agents.create
Row Edit partner agents.edit
Row View ledger agents.view
Ledger Allocate receipt finance.payments.record
Ledger Print statement reports.export
Row Set/reset access key agents.edit
Row Deactivate / reactivate agents.edit
Row Delete partner agents.delete

6.10 Customer portal (/customer/**) — role-scoped

Not enforced against staff matrix. Access gated by allowedRoles={['customer']} + API ownership checks.

Actions available: view own bookings, request group interest, download own invoice, upload payment with receipt, view visa status, download visa docs, accept/reject quotations sent to them, create/respond to service requests, edit own profile, view own statement, manage own sessions.

6.11 Partner portal (/partner/**) — role-scoped

Not enforced against staff matrix. Access gated by allowedRoles={['agent']} + API ownership checks.

Actions available: dashboard KPIs, view own bookings, view own customers, view own quotations, create booking (partner wizard → always on_account), add passengers, view/sell seat inventory, view/create/edit/print invoices, submit group-booking requests, view own reports (bookings, revenue, customers, invoices), edit contact person on profile, manage own sessions.

6.12 Inventory (/inventory/**)

Route Action Permission
/inventory/quota View blocks inventory.view
Create/edit/delete block inventory.create / inventory.edit / inventory.delete
Manage airlines inventory.edit
Link/unlink block to group inventory.allocate
Sell seats to B2B inventory.edit
Record supplier payment finance.create
/inventory/fit View inventory.view
CRUD inventory.create / .edit / .delete
Record FIT supplier payment finance.create
/inventory/b2b-flights View inventory.view
Create offer inventory.create
Close offer inventory.edit
/inventory/ground-transfers View inventory.view
CRUD inventory.create / .edit / .delete
Assign / remove inventory.allocate

6.13 Suppliers (/suppliers)

Section Action Permission
Route View page inventory.view (semantically should be suppliers.view — tracked in §8)
Header Add supplier suppliers.create
Row / Dialog Edit supplier suppliers.edit
Dialog Delete supplier suppliers.delete
Ledger Add transaction suppliers.edit
Ledger Edit transaction suppliers.edit
Ledger Delete transaction suppliers.delete
Payment Apply TDS on supplier payment finance.tds.deduct

6.14 Hotels (/hotels)

Section Action Permission
Route View page hotels.view
Header Create hotel hotels.create
Row Edit hotel hotels.edit
Row Delete hotel hotels.delete
Any Export hotels.export

6.15 People (/people, /people/employees)

Section Action Permission
Route /people View directory customers.view
Route /people/employees View staff admin.view
User mgmt Create user admin.view (should tighten — admin.edit)
User mgmt Reset password admin.view (should tighten)
User mgmt Change role admin.view (should tighten)
User mgmt Delete user admin.view (should tighten — also needs confirm dialog)

6.16 Admin (/admin) — role+permission double-gated

Section Action Permission
Route View admin admin.view + allowedRoles=['admin']
Permissions matrix Edit role permissions admin.edit
Permissions matrix Edit user overrides admin.edit
Currencies CRUD admin.edit
Cities CRUD admin.edit
Integrations Edit email/WhatsApp/SMS config admin.edit
Audit logs View admin.view

6.17 Reports (/reports) + Requests (/requests) + Communications (/communications, /whatsapp) + Chat (/internal/chat)

Route Permission
/reports reports.view
/requests requests.view
/communications customers.view (drift — should be dedicated communications.view future)
/whatsapp customers.view (drift — same)
/internal/chat chat.view

7. Sidebar visibility

Nav items are filtered by hasPermission(). Parent visible if user has any child's permission.

Nav label Path Permission
Dashboard /app dashboard.view (route uses allowedRoles instead — drift)
Inventory → Airline Blocks / FIT / B2B / Ground /inventory/* inventory.view
Inventory → Hotels & Food /hotels hotels.view
Inventory → Visa /visa visa.view
Sales → Leads /sales/leads leads.view
Sales → Requests /requests requests.view
Sales → Quotations /sales/quotations quotations.view
Sales → Bookings /sales/bookings bookings.view
Groups /groups groups.view
Finance /finance finance.view
Customers /sales/customers customers.view
Business Partners /agents agents.view
Suppliers /suppliers inventory.view (drift — should be suppliers.view)
People /people customers.view
People → Employees /people/employees admin.view
Workflow → Ops Approval /approvals approvals.view
Workflow → Corrections /corrections bookings.view (drift — should be approvals.view)
Workflow → Finance Approval /finance?tab=approvals finance.view
Workflow → Tickets /tickets tickets.view
Workflow → WhatsApp /whatsapp customers.view (drift)
Workflow → Communications /communications customers.view (drift)
Workflow → Internal Chat /internal/chat chat.view
Reports /reports reports.view
Admin /admin admin.view

8. Known drift (as of this audit)

Items where code/matrix/seed don't fully agree. Each is tracked here; fixing them is incremental work.

  1. chat.view referenced but unseeded. App.tsx:187 requires it, but no migration inserts it. Fixed in this PR by supabase/migrations/20260416030000_seed_chat_and_dashboard_view.sql.
  2. dashboard.view referenced by Sidebar but no route guard uses it. /app route relies on allowedRoles={['admin','staff']} instead. Seeded this PR; tightening the route guard is a follow-up.
  3. /corrections uses bookings.view. Semantically this is an approvals/ops action; should migrate to a dedicated corrections.view. Tracked for a follow-up PR — when wired, add corrections.view to §4.
  4. /communications and /whatsapp use customers.view. Should migrate to a dedicated communications.view. Tracked; when wired, add communications.view to §4.
  5. /suppliers route uses inventory.view. Should migrate to suppliers.view. Tracked; when wired, add to §4.
  6. agents.edit / agents.delete / groups.delete / inventory.delete / inventory.allocate / quotations.delete not yet wired. The API enforces these actions via RLS and role-level grants, but there are no <PermissionGate> wrappers in the UI so row-level buttons remain visible regardless of permission. Tightening UI gates is a follow-up per PR; when added to code, re-add to §4.
  7. admin.edit / admin.users.* / admin.config.edit / admin.audit.view not yet split. User management actions in <UserManagement> aren't separately gated; all live behind admin.view. Split when refactoring admin surface.
  8. Route-level allowedRoles=['admin','staff'] on /app bypasses the permission matrix for dashboard access. Migrate to requiredPermissions={['dashboard.view']} in a follow-up.
  9. Partner & customer portals are role-gated only (allowedRoles={['agent']} / ['customer']). Actions inside portals are ownership-gated by the API, not matrix-gated. This is intentional (see §4.5) but means there is no single matrix row describing partner self-service capabilities.
  10. Granular permissions (§4.4) are seeded but mostly not yet wired in code. Migration 20260416050000 inserts all ~70 sectional permissions and grants the right role bundles (e.g. finance.reports.profit_loss.view to FINANCE_MANAGER but not ACCOUNTANT). The PermissionsMatrix admin UI surfaces them so admins can already toggle per-role. Currently wired: finance report button filtering in Finance.tsx (the user's explicit Accountant-vs-P&L example works). Not yet wired: export-button gates, workflow-split checks (e.g. finance.payments.verify still goes through coarse finance.create), admin tab-level splits (user management still behind admin.view), agent CRUD splits. The ALLOWED_DOC_ONLY list in src/test/permissions.matrix.test.ts enumerates exactly which permissions are seeded-but-unwired; each follow-up PR that wires a batch removes its entries from that list.

9. Governance (how this stays true)

When you add a page/section/action: 1. Add a row to §6 under the appropriate page. 2. If the permission is new, add it to §4 catalog and §5 grants matrix. 3. Add the corresponding row(s) to a new seed migration under supabase/migrations/. 4. Reference the permission in code: - Frontend: <PermissionGate permission="x.y"> around the action - Backend: await requirePermission('x.y') at the top of the handler - Route: <ProtectedRoute requiredPermissions={['x.y']}> if gating a whole page 5. Run npm test — the drift test will catch missing seed rows, orphaned permissions, or code that references permissions not in this file.

When you rename a permission: 1. Update §4 and §5 in this file. 2. Update every code reference in the same PR (grep the old name). 3. Add a migration that renames in the DB (UPDATE "Permission" SET name = ...) AND updates RLS policies referencing the old name. 4. Run npm test.

When you delete a permission: 1. Remove every code reference first. 2. Remove from §4 and §5. 3. Add a migration that drops the RolePermission rows and then the Permission row. 4. Run npm test.

The drift test is in src/test/permissions.matrix.test.ts. It will fail the build if: - Code uses a permission string not declared in this file - This file declares a permission that no code references (after a grace list for migrations)


10. Canonical changelog of this matrix

Date Migration Change
2026-01-15 20260115000000 Initial 22-permission seed + admin-tier grants
2026-04-02 20260402210000 Added groups.view/edit/delete
2026-04-15 20260415200000 Permission-aware RLS on Booking/Customer/Agent/Supplier/Payment/Journal*/FinanceConfig
2026-04-15 20260415210000 Added customers.edit/delete, partners.*, suppliers.*, finance.edit, admin.edit + functional subsets
2026-04-15 20260415220000 Seeded all 13 functional role bundles
2026-04-16 20260416010000 Added *.view family + finance.payments.record; broadcast to staff roles
2026-04-16 20260416020000 Removed CEO/GM/IT_ADMIN role-name bypass from auth_user_has_permission(); grant every permission to those roles explicitly
2026-04-16 20260416030000 Added chat.view and dashboard.view to close drift with code