Skip to content

PRD: Per-merchant Casbin scoped authorization

ModulePermissions (CORE-02)PRD IDPRD-EFF-001
StatusShippedOwnerPermissions squad
Date2026-06-04Versionv1.0
Packages@nx/core · @nx/identity · @nx/commerce · @nx/finance · @nx/inventoryURDEFF · GRANT · ROLE

TL;DR

Centralizes authorization on a per-merchant Casbin engine so every verifier service enforces grants within the active merchant domain chosen per request, and resolves a user's reachable merchants/organizers from the Casbin policy repository instead of scattered request-context filtering. The result: "what can this user see and do here" becomes one consistent, policy-backed answer across commerce, finance, and inventory - with per-merchant (per-schema) isolation and soft-delete honoured.

1. Context & Problem

Authorization in KICKO is originally driven by request-context filtering: each service derives a user's reachable organizations and merchants from the request and hand-filters its queries. This makes "what can this user see" implicit, duplicated across services, and impossible to reason about in one place - and it has no single home to resolve a grant within the active merchant domain a request operates in.

A central Casbin scoped-RBAC engine in the VerifierApplication closes this gap: every verifier service enforces grants the same way, and merchant/organizer reach comes from the Casbin policy repository rather than per-request guesswork. Because the framework's adapter does not honour soft-delete and KICKO needs per-merchant (per-schema) isolation, an application-level Casbin adapter belongs in @nx/core so policy/grouping rows load under the project's own contract without editing IGNIS. Inventory is the pilot surface before broader enablement.

2. Goals & Non-Goals

Goals

  • Enforce authorization through Casbin in the VerifierApplication, resolving grants within the active merchant domain selected per request (x-merchant-id).
  • Resolve a user's reachable merchants/organizers from the Casbin policy repository instead of request-context filtering (commerce, finance, core).
  • Provide an application-level scoped Casbin adapter in @nx/core that honours soft-delete and per-merchant (per-schema) isolation, requiring no IGNIS changes.
  • Pilot end-to-end enforcement on inventory: seed inventory role permissions, backfill owner roles, resolve allowed merchant IDs via policy.
  • Keep the engine aligned with the framework's scoped Casbin model (wildcard/scoped model correctness, the scoped adapter, Casbin cache invalidation on token refresh).

Non-Goals

  • Wildcard / regex permissions (sales.*) - out of scope (URD §2, §7).
  • Permission categories / UI grouping, and role templates / bundles.
  • Time- or shift-based permissions and permission audit logging.
  • The resource / action / domain hierarchy and declaration toolkit - owned by PRD-HIER-001 (HIER, DECL).

3. Success Metrics

MetricTarget / signal
Central enforcement100% of verifier-service authorization decisions flow through the Casbin enforcer, not ad-hoc request filtering
Scope correctnessMerchant/organizer reach resolved from policy matches the user's actual grants; no foreign-merchant data leaks
Tenant isolationPolicy/grouping rows never cross a merchant (per-schema) boundary
Soft-delete fidelityRevoked (soft-deleted) grants are never enforced as active
Pilot stabilityInventory operates fully under per-merchant authorization with no regression in allowed-merchant resolution

4. Personas & Use Cases

PersonaGoal in this feature
OwnerReach only their own organizer and its merchants - resolved consistently from policy
Employee / CashierAct only within assigned merchants, enforced per active-merchant domain
Platform adminTrust one central enforcement path across all verifier services
Service (verifier)Resolve allowed merchant IDs from policy instead of bespoke per-request filtering

Core scenarios: a request arrives with an active-merchant header → the verifier enforces the grant within that merchant domain via Casbin → reachable merchants/organizers are resolved from the policy repository → soft-deleted grants are excluded and tenant rows stay per-schema.

5. User Stories

  • As a platform admin, I want authorization enforced centrally in the verifier, so every service answers "can this user do this here" the same way.
  • As an owner, I want my reachable merchants resolved from policy, so I see exactly my organizer's merchants and nothing foreign.
  • As an employee, I want grants enforced within the active merchant domain, so my access is scoped to the merchant I'm acting on.
  • As a service developer, I want to resolve allowed merchant IDs from the Casbin policy, so I stop hand-filtering queries from request context.
  • As a platform admin, I want revoked grants to stop applying immediately (no soft-deleted rows enforced), so access revocation is trustworthy.
  • As a platform admin, I want a refreshed token to clear the Casbin cache, so role/permission changes take effect cleanly.

6. Functional Requirements

#RequirementURD ref
FR-1Casbin RBAC enforcement is wired into the VerifierApplication; every verifier service enforces grants centrallyURD-EFF-004
FR-2A grant resolves only within the active merchant domain selected per request (x-merchant-id)URD-EFF-004 · URD-ROLE-005..006
FR-3Effective permissions are the deduplicated union of direct + role-inherited grants, resolved from the policy repositoryURD-EFF-001..002
FR-4A user's reachable organizations and merchants are resolved from the Casbin policy repository, not request-context filteringURD-EFF-003
FR-5Grant / revoke is enforced through the policy repository (idempotent, privilege-escalation guarded)URD-GRANT-001..007
FR-6An application-level scoped Casbin adapter loads policy/grouping rows honouring soft-delete and per-merchant (per-schema) isolationURD-EFF-004
FR-7Tenant isolation is enforced by constraining the membership variant in the grouping queryURD-ROLE-007..008
FR-8An Owner at a head-quarter merchant reaches every sibling merchant of that organizerURD-ROLE-009
FR-9Inventory resolves allowed merchant IDs via Casbin policy; inventory role permissions are seeded and owner roles backfilledURD-EFF-003..004
FR-10A token refresh invalidates the Casbin cache so policy changes take effectURD-EFF-004

Full requirement text and acceptance criteria live in the Permissions URD. This PRD references them rather than restating them.

7. Non-Functional Requirements

AreaRequirement
Central enforcementAll authorization decisions in verifier services go through one Casbin enforcer; no per-service ad-hoc filtering remains as the source of truth
Tenancy & authzEvery grant resolves within one active merchant domain per request; policy/grouping rows are isolated per merchant (per-schema)
Soft-delete fidelityThe scoped adapter excludes soft-deleted policy/grouping rows; revocation is honoured without physical deletes
ConsistencyCache invalidation on token refresh keeps enforcement aligned with current policy
Framework alignmentThe application-level adapter requires no IGNIS edits and tracks the framework's scoped Casbin model
i18nUser-facing role/permission labels remain bilingual ({ en, vi })

8. UX & Flows

Key surfaces are backend: enforcement lives in @nx/core (application/verifier.ts, security/casbin-model.ts, security/application-casbin-adapter.ts, utilities/request.utility.ts); commerce/finance/core resolve scope from policy; inventory is the pilot service. There is no dedicated end-user screen - this increment is the authorization substrate other modules rely on.

9. Data & Domain

EntityRole
Casbin policy (p)A grant: subject, resource, action, scoped to a merchant domain
Casbin grouping (g)Membership - user→role, user→org/merchant, role→org/merchant; tenant-isolated by variant
Scoped Casbin adapterLoads policy/grouping rows soft-delete-aware and per-merchant (per-schema)
Active merchant domainThe merchant a request operates in, selected via x-merchant-id
Casbin modelScoped RBAC model defining how grants resolve within a domain

Conceptual only - full policy schema and adapter contract in the developer Casbin Authorization docs and RBAC docs.

10. Dependencies & Assumptions

Depends on

  • Fixed & custom roles + grants (URD-ROLE · URD-GRANT) - the policy rows the engine enforces.
  • Commerce (@nx/commerce) - organizations and merchants are the scopes grants attach to.
  • Identity (@nx/identity) - sign-in token carries role context; token refresh triggers cache invalidation.
  • IGNIS framework - the scoped Casbin model the application-level adapter aligns with.

Assumptions

  • Requests carry an active-merchant header (x-merchant-id) selecting the domain.
  • A merchant's policy/grouping rows exist in its own schema (per-merchant isolation).
  • Owner roles are backfilled and inventory role permissions seeded for the pilot.

11. Risks & Open Questions

Risk / questionMitigation / status
Framework adapter ignores soft-deleteApplication-level adapter in @nx/core honours soft-delete; no IGNIS edit
Cross-merchant policy leakageMembership query constrains the variant; rows isolated per schema
Stale enforcement after role/permission changeCasbin cache cleared on token refresh
Framework Casbin model drifts from app expectationsTrack scoped model; migrate to the new scoped adapter as upstream changes
Broad enablement beyond inventory pilotRoll out per service after the inventory pilot validates end-to-end

12. Release Plan & Launch Criteria

AspectPlan
PhaseP2 (EFF + GRANT, Built) - see URD feature catalog
RolloutInventory pilot first, then broader verifier-service enablement
MigrationSeed inventory role permissions; backfill owner roles
Launch criteriaCentral enforcement verified in the verifier; scope resolved from policy matches grants; soft-delete and per-schema isolation verified; inventory allowed-merchant resolution correct end-to-end
MonitoringAuthorization decision path coverage, foreign-merchant access attempts, cache-invalidation on token refresh, pilot error rate

13. FAQ

Where is authorization enforced now? Centrally, in the VerifierApplication via the Casbin enforcer - every verifier service uses the same path instead of hand-filtering from request context.

How is "which merchant" decided? Per request, by the active-merchant header (x-merchant-id); a grant resolves only within that merchant domain.

Why an application-level adapter instead of the framework's? The framework adapter does not honour soft-delete and KICKO needs per-merchant (per-schema) isolation; the @nx/core adapter provides both with zero IGNIS edits.

Does revoking a grant take effect immediately? Revocation soft-deletes the policy row, which the scoped adapter excludes; a token refresh clears the Casbin cache so changes apply cleanly.

Why pilot on inventory first? To validate end-to-end per-merchant enforcement (seed + owner backfill + policy-resolved merchant IDs) on one service before broader rollout.

Is wildcard / hierarchy permission part of this? No - coarse grants via the resource/action/domain hierarchy are owned by PRD-HIER-001.

References

Proprietary and Confidential. Unauthorized copying, distribution, or use of this software is strictly prohibited.