Appearance
ADR: RBAC and tenancy v2 - mandatory org/outlet context
Status
Proposed
Runtime frontend, backend authorization, database, and permission-constant changes must be implemented by separate follow-up tasks after this ADR is accepted.
Date
2026-07-06
Plane / Epic
- Plane task: MHPAY-118 / MHPAY-ADR-008
- Epic: MHPAY-117 / MHPAY-RBAC-V2-000
Context
MyHoreca Pay already uses scoped RBAC with Platform, Organization, and Outlet ownership. Existing documentation also separates Syrve connection selection from RBAC scope: a Syrve connection is a navigation and registry concept under an organization, while payment workflows remain owned by organization and Outlet.
The next RBAC/tenancy iteration must close two risks before runtime work starts:
- platform roles such as
MegaAdminandMegaManagermust not see operational data across all Outlets simply because an operational endpoint was called without explicitorganizationId/locationId; - frontend active context must not be split across competing stores such as legacy workspace keys and a separate active context helper that can disagree about the selected organization, Outlet, or UI-only Syrve connection.
The product term is Outlet. Location remains a legacy/runtime name only, for example existing locationId, X-Location-Id, and location.* permission constants.
Decision
Adopt active context v2 as the single frontend/backend contract for operational customer flows.
RBAC assignment scopes remain only:
Platform;Organization;Outlet.
Connection, SyrveConnection, and syrveConnectionId are not RBAC assignment scopes. Do not add ConnectionId to UserRoleAssignment.
Operational screens and APIs that are Outlet-facing require an explicit active organization and Outlet context for every MVP role, including MegaAdmin and MegaManager. Platform roles may access platform administration screens without an Outlet, but they must select an explicit organization and Outlet before using the operational dashboard or payment workflow screens. Operational screens must be filtered to the selected context, not silently expanded to all organizations or all Outlets.
Terminology / scopes
| Term | Scope type | Rule |
|---|---|---|
| Platform | RBAC assignment scope | MyHoreca Pay operator scope for platform administration, support, workers, global settings, and platform reporting endpoints. |
| Organization | RBAC assignment scope | Customer/business ownership boundary. Required for business/customer flows after login or context selection. |
| Outlet | RBAC assignment scope | Product term for the operational endpoint. Required for Outlet-facing operational and payment workflows. Existing runtime may still call this Location. |
| Syrve Connection | UI/navigation context | Organization-owned or Outlet-mapped registry entry for Syrve screens. It is not an RBAC scope and must not be added to role assignments. |
Active context v2 contract
The canonical active context model is:
| Field | Requirement | Meaning |
|---|---|---|
organizationId | Required after login/context selection for customer and business flows | Selected customer/business ownership scope. |
locationId / outletId | Required for Outlet operational workflows | Selected Outlet. locationId is the current legacy/runtime field name; outletId is the product/docs term for the same operational selection. |
syrveConnectionId | Optional UI/navigation context only | Selected Syrve connection for connection-scoped Syrve read/registry screens. Not a backend authorization scope. |
syrveConnectionName | Optional UI-only display field if already present in frontend state | Display label for the selected connection. It must not drive authorization or tenant filtering. |
Active context is not authorization by itself. Backend scoped authorization remains the source of truth. The frontend may send X-Organization-Id and optional X-Location-Id, but backend endpoints must revalidate the caller's effective scope and target resource ownership.
Operational APIs must not fall back to platform-wide data just because the caller is MegaAdmin or MegaManager. If an endpoint is operational or Outlet-scoped, missing active organization/Outlet context is a context error unless the endpoint has an explicit equivalent route/query scope.
Required context by route/API category
This table is a documentation contract for follow-up runtime work, not an implementation change in this ADR.
| Category | Required context | Notes |
|---|---|---|
| Dashboard counters / operational dashboard | Organization + Outlet | Required for all roles, including platform roles. |
| Review queue | Organization + Outlet | Aggregates only selected workflow scope. |
| Bank statement import/list/detail where workflow is Outlet-facing | Organization + Outlet | Organization-level unresolved data may exist, but operational screens must not become all-Outlets views. |
| Transaction review | Organization + Outlet | Uses selected workflow scope. |
| Counterparty matching and rules where Outlet workflow applies | Organization + Outlet | Organization-level inherited rules remain allowed when explicitly modeled. |
| Payment allocation / preview / approval | Organization + Outlet | Mutations require scoped permission and audit. |
| Syrve documents in payment workflow | Organization + Outlet | Payment workflow routes stay outside /syrve/connections/:connectionId. |
| Syrve postings | Organization + Outlet | Queue, retry, approve, reconcile, and evidence actions stay workflow-scoped. |
| Reconciliation | Organization + Outlet | Uses selected workflow scope. |
| Payment anomalies | Organization + Outlet | Includes notifications/evidence when tied to workflow. |
| Audit views used in operational workflow | Organization + Outlet | Platform/global audit must be a separate platform endpoint or screen. |
| Manual corrections / notifications / evidence scoped to workflow | Organization + Outlet | Mutations require scoped permission and audit. |
| Organization settings and connection registry | Organization | /settings and /syrve/connections are organization-scoped. |
/syrve/connections/:connectionId, /sync-runs, /references | Organization + selected connection ownership | Connection UI/read screens must validate selected connection ownership. Connection is not an RBAC scope. |
| Platform users, workers, bank adapters, platform settings | Platform | These are platform administration screens and do not require Outlet context. |
Dedicated platform-wide support/reporting endpoints may exist only when explicitly designed as platform endpoints, separately documented, guarded, and audited. They must not be accidental operational endpoints with null organization/Outlet filters.
Role behavior
MegaAdmin: may access platform administration, but the operational dashboard and payment workflows require a selected organization and Outlet. The operational dashboard must not be used as all-platform aggregation unless a dedicated platform reporting endpoint exists.MegaManager: follows the same active context requirement for operational screens. Support diagnostics are allowed only within documented support behavior; production write approval remains denied.OrganizationAdmin: requires organization context. Outlet context is required for Outlet workflow. Parent organization admin inheritance remains governed by the ownership ADR.Accountant: works within assigned organization/Outlet workflow. Outlet context is required unless the endpoint is explicitly organization-level.Viewer: read-only within selected and assigned context.PaymentOperatorandLocationAdmin: future/legacy only. They have no MVP permissions and must not be required by MVP runtime work.
Frontend gate / redirect policy
For Outlet-required routes:
- if
organizationIdis missing, redirect to active context selection or organization selection; - if
organizationIdexists butlocationId/ Outlet is missing, redirect to Outlet selection or show a blocking context-required gate; - do not silently choose the first/default Outlet for multi-Outlet users or platform users;
- single safe context auto-selection may remain allowed only when backend/auth context declares it safe;
- platform roles must choose an explicit organization and Outlet before entering the operational dashboard or payment workflows;
- connection-scoped Syrve screens may use remembered/default/first connection resolution only inside an already valid organization context, and inside an Outlet context when the screen requires one by route policy;
- resolving a Syrve connection for
/syrve/connections/:connectionId,/sync-runs, or/referencesmust not imply payment workflow scoping.
Frontend visibility is not authorization. Route guards improve UX, while backend scoped authorization and ownership checks remain mandatory.
Backend API filtering policy
Operational and Outlet APIs must require active organizationId / locationId or an explicit equivalent route/query scope.
ApplyLocationScopeFilter(..., null, null) must not be used to return all Outlet operational data for platform roles in user-facing operational flows. Platform-wide support/reporting endpoints must be explicit platform endpoints with separate documentation, authorization, and audit expectations.
Backend paths must validate server-side:
- organization ownership;
- Outlet ownership;
- selected Syrve connection ownership when a connection id is present;
- scoped permission for mutations;
- audit trail for payment actions, manual corrections, postings, approvals, and other workflow mutations.
Cross-tenant reads return 403 or concealed 404 / null according to existing endpoint policy. Cross-tenant mutations are denied and must not mutate data.
Syrve connection / auto RMS/CHAIN policy
Syrve probe may classify a connection as RMS or CHAIN according to probe result and evidence.
CHAINmay imply an organization-level connection registry with multiple Outlets/units that require explicit mapping or selection.RMSmay allow a single operational endpoint only when evidence is unambiguous.- Auto mapping must not create a fake/default Outlet only to satisfy the Outlet requirement.
- If probe cannot safely map organization/Outlet, the result must be
NeedsReviewor an equivalent review-required state. - Auto-selected RMS/CHAIN result is connection/navigation metadata, not an RBAC scope.
- Payment posting/allocation remains blocked or review-required when Outlet mapping is unsafe.
This ADR does not change IikoConnector. IikoConnector remains low-level transport only.
Storage policy: one active context store, no competing localStorage
Frontend must have one canonical active context store/helper for active organization, Outlet, and optional Syrve connection context.
Rules:
- dual
localStoragestores or competing active context stores are forbidden; - legacy keys may exist only for backward compatibility and must be synchronized by the canonical helper;
- no new legacy
syrveConnectionIdlocalStoragekey may be introduced; - direct
localStoragewrites outside the canonical active context helper are forbidden for active context; - future runtime changes must consolidate all callers to the canonical active context helper before extending active context behavior.
Non-goals
- Runtime frontend changes.
- Backend authorization/runtime changes.
- Database schema changes or EF migrations.
- Adding
ConnectionIdtoUserRoleAssignment. - Adding Connection or Syrve connection as an RBAC scope.
- Adding new runtime permission constants.
- Sub-organization UI from MHPAY-50.
- Changes in
IikoConnectororiikoConnector. - Playwright/API tests, except docs validation if needed.
- Moving payment workflow routes under
/syrve/connections/:connectionId.
Consequences
- Follow-up frontend tasks must implement blocking gates/redirects for Outlet-required routes.
- Follow-up backend tasks must harden operational filters so platform roles do not accidentally receive all-Outlets operational data.
- Future active context work must consolidate storage through one canonical helper before adding behavior.
- Documentation and runtime implementation must keep Outlet as the product term while acknowledging legacy/runtime
Locationnames. - Platform support/reporting behavior must be modeled as explicit platform surfaces, not as missing filters on operational endpoints.
AI agent rules
- Treat this ADR as target architecture until accepted and implemented by follow-up tasks.
- Do not modify runtime frontend, backend, migrations, tests, or
IikoConnectorfor this ADR-only task. - Do not add
ConnectionIdtoUserRoleAssignment. - Do not add Connection/Syrve connection as an RBAC assignment scope.
- Do not add runtime permission constants for this docs-only decision.
- Use Outlet in product/UI wording; use
Locationonly when referring to existing legacy/runtime names. - If a follow-up task needs all-platform operational analytics, design a separate platform endpoint/screen with explicit authorization and audit.