Skip to content

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 MegaAdmin and MegaManager must not see operational data across all Outlets simply because an operational endpoint was called without explicit organizationId / 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 ​

TermScope typeRule
PlatformRBAC assignment scopeMyHoreca Pay operator scope for platform administration, support, workers, global settings, and platform reporting endpoints.
OrganizationRBAC assignment scopeCustomer/business ownership boundary. Required for business/customer flows after login or context selection.
OutletRBAC assignment scopeProduct term for the operational endpoint. Required for Outlet-facing operational and payment workflows. Existing runtime may still call this Location.
Syrve ConnectionUI/navigation contextOrganization-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:

FieldRequirementMeaning
organizationIdRequired after login/context selection for customer and business flowsSelected customer/business ownership scope.
locationId / outletIdRequired for Outlet operational workflowsSelected Outlet. locationId is the current legacy/runtime field name; outletId is the product/docs term for the same operational selection.
syrveConnectionIdOptional UI/navigation context onlySelected Syrve connection for connection-scoped Syrve read/registry screens. Not a backend authorization scope.
syrveConnectionNameOptional UI-only display field if already present in frontend stateDisplay 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.

CategoryRequired contextNotes
Dashboard counters / operational dashboardOrganization + OutletRequired for all roles, including platform roles.
Review queueOrganization + OutletAggregates only selected workflow scope.
Bank statement import/list/detail where workflow is Outlet-facingOrganization + OutletOrganization-level unresolved data may exist, but operational screens must not become all-Outlets views.
Transaction reviewOrganization + OutletUses selected workflow scope.
Counterparty matching and rules where Outlet workflow appliesOrganization + OutletOrganization-level inherited rules remain allowed when explicitly modeled.
Payment allocation / preview / approvalOrganization + OutletMutations require scoped permission and audit.
Syrve documents in payment workflowOrganization + OutletPayment workflow routes stay outside /syrve/connections/:connectionId.
Syrve postingsOrganization + OutletQueue, retry, approve, reconcile, and evidence actions stay workflow-scoped.
ReconciliationOrganization + OutletUses selected workflow scope.
Payment anomaliesOrganization + OutletIncludes notifications/evidence when tied to workflow.
Audit views used in operational workflowOrganization + OutletPlatform/global audit must be a separate platform endpoint or screen.
Manual corrections / notifications / evidence scoped to workflowOrganization + OutletMutations require scoped permission and audit.
Organization settings and connection registryOrganization/settings and /syrve/connections are organization-scoped.
/syrve/connections/:connectionId, /sync-runs, /referencesOrganization + selected connection ownershipConnection UI/read screens must validate selected connection ownership. Connection is not an RBAC scope.
Platform users, workers, bank adapters, platform settingsPlatformThese 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.
  • PaymentOperator and LocationAdmin: 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 organizationId is missing, redirect to active context selection or organization selection;
  • if organizationId exists but locationId / 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 /references must 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.

  • CHAIN may imply an organization-level connection registry with multiple Outlets/units that require explicit mapping or selection.
  • RMS may 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 NeedsReview or 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 localStorage stores 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 syrveConnectionId localStorage key may be introduced;
  • direct localStorage writes 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 ConnectionId to UserRoleAssignment.
  • Adding Connection or Syrve connection as an RBAC scope.
  • Adding new runtime permission constants.
  • Sub-organization UI from MHPAY-50.
  • Changes in IikoConnector or iikoConnector.
  • 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 Location names.
  • 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 IikoConnector for this ADR-only task.
  • Do not add ConnectionId to UserRoleAssignment.
  • 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 Location only 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.