6. Permission Safety — No Platform/Root-Admin Leakage
6.1 Two entirely separate permission catalogs
Shumoul.Domain.Constants.PermissionConstants(BackEnd) — ~250 nested classes, 2,278 constants — the tenant-operational catalog (InventoryItems, SalesInvoice, Accounts, CostCenters, Users, Branches, and every other day-to-day business permission).Shumoul.Framework.MultiTenancy.Api.Constants.TenancyPermissions(MultiTenancyApi) — 36 nested classes — the root/SaaS-admin-only catalog:HostingServers,SharedDbConnectionStrings,Tenants,SubscriptionPlans/Packages/Features/PackageFeatures/PlanPackages/ExtraFeatures/ FeatureClaims/FeatureReportActions/FeatureSettingsGroups/PackageFeatureRules,BusinessActivities,AccountingChartTemplates,InventoryAccountCategoryTemplates,CostCenterTemplates,TenantStarterKits,ProductCatalogTemplates,ProductCatalogPrerequisiteTemplates,DemoSubscriptions,MenuClaims(admin CRUD),TaxTypes/TaxPeriods/TaxGroups(Saas),Lookups/LookupGroups(Saas),PaymentMethods(Saas),Notifications/NotificationTemplates/NotificationTopics(Subscribers)(admin),TenantSubscriptions/TenantSubscriptionLogs(cross-tenant admin view),ProformaInvoices(Admin.*). Every controller using[MustHavePermission(TenancyPermissions...)]is routed underapi/Saas/*— confirmed by grepping all 40 such controllers.
6.2 MenuClaims.json merges both catalogs — but seeding never crosses the line
MenuClaims.json (17,418 lines, 2,916 distinct claim keys) is a single UI-menu tree used by both
the root tenant's admin panel and every customer tenant's admin panel — it deliberately contains both
catalogs' permission strings (confirmed by two sync tests asserting it's a superset of both
PermissionConstants and TenancyPermissions). This is why MenuClaims.json alone cannot be trusted
to curate a customer package's permissions — as the task instructions warned.
The actual seeding-time safety backstop is structural, in ApplicationDbSeeder.SeedRolesAsync:
if (tenant == root tenant)
seed ALL of PermissionConstants + ALL of TenancyPermissions to Admin role — unconditional
else // regular/customer tenant
permissions = PermissionConstants values, filtered to only those in tenantClaims (via FeatureMenuClaim)
seed those filtered PermissionConstants + unconditionally seed all of TenantPermissions (small set)
// TenancyPermissions is NEVER seeded for a non-root tenant, by construction
A customer tenant can never receive a TenancyPermissions.* role claim through any package/feature
configuration — the non-root branch only ever intersects against PermissionConstants values. This
means the real risk for this task was never "TenancyPermissions leaking in" (structurally impossible);
it was accidentally wiring a quasi-platform-flavored PermissionConstants entry (e.g.
DemoSubscriptionHistory, OAuthClient, delivery-integration diagnostics) into a customer package.
6.3 What this task actually touched — and what it did not
This task never touched MenuClaims.json, FeatureMenuClaim, or any PermissionConstants/
TenancyPermissions value at all. The four Shumoul packages are linked only to existing
SubscriptionFeature keys (ProductsManagement, InventoryManagement, PointofSalesApp,
SalesManagement, AccountManagement, PayablesManagement, ReceivablesManagement,
ProjectManagement) — every one of which is a normal, already-existing, already-reviewed tenant
feature with no platform/admin flavor whatsoever. Shumoul.Framework.MultiTenancy.Test.Packages. ShumoulPackageFeatureSeederShould.EmbeddedSeedFile_OnlyReferencesKnownTenantOperationalFeatures (see
Chapter 11) guards this permanently — it fails immediately if a
future edit introduces any feature key outside this known-safe set.
Since SubscriptionPackageFeatureRule/SubscriptionFeatureReportAction/SubscriptionFeatureSettingsGroup
are all pure catalog/entitlement data (never raw permission strings), and since no
SubscriptionPackage-to-FeatureMenuClaim wiring was added or modified by this task, there is no path
by which this work could have granted a customer tenant any elevated or platform permission.
