RBAC Decomposition
What This Is
Cross-repo coordination doc. Lives in the workspace repo because it spans platform and frontend. The authoritative design is the RBAC analysis document. Per-repo implementation plans live in their repos, not here.
Fine-grained, deny-by-default RBAC for setldhub, spanning two repos:
- platform (
packages/card-auth/authorize-api,packages/shared/auth, + newpackages/shared/rbac) — Fastify/Prisma backend; the real enforcement gate. - frontend (
apps/setldhub,libs/shared/auth, + newlibs/shared/rbac) — Angular 21; advisory UI gating only.
Hybrid model: Frontegg assigns coarse roles (roles[] JWT claim); a code-owned catalog (@setldpay/rbac) maps roles → fine-grained <entity>:<action>permissions. 14 roles, 36 permissions. Multi-role = union (additive, no deny). The design is locked (DEC-1...DEC-14 resolved); these units are the implementation, not a re-litigation.
Units & Sequencing
B - @setldpay/rbac catalog + interim FE sync ---- FOUNDATION; blocks C & D
|-> C - Backend enforcement (platform) -- carries the ISSUE-1/2/3/4 hotfixes
|-> D - Frontend enforcement (frontend) -- parallel to C once B lands
|
v
E - Cutover (Frontegg roles, soak, retire aliases)| Unit | Repo(s) | Scope | Depends on |
|---|---|---|---|
| B | platform + frontend | @setldpay/rbac catalog (data + resolvers) in platform; interim vendored copy + the frontend repo's mise run rbac:sync + self-validating tests in frontend. | --- |
| C | platform | resolvePermissions hook, requirePermission preHandler + route requires, DEC-6 eligibilityFiltersFor + widener migration, ApiKey.roles[] + adapter/issuer + backfill, scope-coverage tests. Carries the hotfixes. | B |
| D | frontend | PermissionsService rewrite, authGuard + permissionGuard (kill alert), *spCan directive, sidebar requires, dev-token presets. | B |
| E | Frontegg + both | Create real roles + scope assignment, shadow-mode soak/metrics, execute key backfill, retire RANUIAdministrator/onboarding-admin aliases. | C, D |
Security Hotfixes
Folded into Unit C, opening routes of its rollout.
| # | Sev | Issue | Handling in C |
|---|---|---|---|
| ISSUE-1 | CRITICAL | mcc-imports fully ungated -- any token can publish, rebuilding global mcc_code | Gated first in the route-by-route rollout. |
| ISSUE-2 | HIGH | x-internal is cosmetic -- agreement:write / program:write enforced by nothing | Replace with real requirePermission. |
| ISSUE-3 | INTENTIONAL | agreement-auth write checks clientId, not programId | Not a bug -- by design. Document (code comment + asserting scope test); no fix. |
| ISSUE-4 | MEDIUM | onboarding search may leak cross-client merchant/card existence | Verify listMerchants/listAllCards are client-scoped. |
| ISSUE-5 | HIGH | client API keys unrestricted (no roles + no enforcement) | Closed by the ApiKey.roles[] + backfill work. |
Locked Sequencing Forks
- Distribution = interim sync bridge first. No publish pipeline exists in either repo today. The spec assumed a pipeline; we start with a vendored copy + manual
mise run rbac:sync(frontend repo's task) + a source-commit stamp drift guard. The full AWS CodeArtifact pipeline (the spec's end-state) is a deferred follow-on. - Hotfixes folded into Unit C (not a standalone track).
Resolved execution-order decisions (Unit C planning)
- Single big-C PR, no carve-out. All enforcement infra + the ApiKey prereq + the DEC-6 widener + the catalog grant ship in one platform PR, internally sequenced as TDD tasks.
- Per-route shadow → enforce. Route-option
requires: Permission[]+ optionalenforceNow; globalRBAC_ENFORCE(defaultfalse). Internal-only routes carryenforceNow: true→ enforced on merge (closes ISSUE-1 + ISSUE-2). All external routes getrequiresin shadow. - Uniform token-type handling. All token types resolve
roles → permissionsidentically; no token-type-specific ceiling. Keys are confined by role assignment. Defense-in-depth ceiling deferred. - PM + client-onboarder granted signing. With ISSUE-3 reclassified intentional,
agreement_authorization:write+onboarding:sign_agreementare added to both roles in the catalog.
Sequencing Hazards
These are guaranteed by Unit B's catalog and must be handled before C flips enforcement:
sp-serviceships with[]permissions (deny-by-default). A workspace-wide search found zero backend service-to-service callers of authorize-api. So shippingsp-service: []is low-risk. Keep a cutover-time per-env check ofALLOW_INTERNAL_TOKENS.- API keys carry no roles today →
permissionsForRoles([])is empty → every key 403s the moment enforcement flips (ISSUE-5). C must landApiKey.roles[]+ adapter/issuer threading + a least-privilege backfill before any backend enforcement. This is a hard prerequisite. program-manager/client-onboardership without signing permissions. Resolved: ISSUE-3 is intentional, so C grants both roles these permissions in the catalog.
Provenance
Decomposed from the locked design on 2026-05-30. Unit B plan reviewed via three-leg multi-model-review (self-audit + codex + adversarial) before execution — matrix fidelity verified clean (0 cell divergences, 36 perms / 14 roles); findings folded into the per-repo plans.