# How to set permissions xly's permission model has two complementary tables. Understanding the difference matters: they answer different questions and are loaded at different times. ## The two tables | Table | Granularity | Loaded when | |---|---|---| | [`gdsjurisdiction`](../../auto-catalog/tables/gdsjurisdiction.md) | per module + per role + per button/data type | every `getModelBysId` call (skipped for ADMIN — see [Slice 1](../../slices/01-hello-world.md)) | | [`sysjurisdiction`](../../auto-catalog/tables/sysjurisdiction.md) | per user + per individual permission grant | session/auth time | ## What `gdsjurisdiction` controls Per-module rules that decide: - **Which buttons** a role can see and click on the form (新增 / 修改 / 保存 / 删除 / 导出 / 打印 / …). - **Which data** a role can see — typically by limiting visible columns or by adding extra `WHERE` predicates to the form's `sSqlStr`. Indexed by: - The module the rule applies to. - The button or data-type identifier (lookup IDs in `plat_base_authority_button_type` and `plat_base_authority_data_type`). - The role / user-type the rule applies to. In dev there are 4,130 `gdsjurisdiction` rows — substantial. In production, expect hundreds per module. ## What `sysjurisdiction` controls Per-user permission grants. 26,926 rows in dev — 6× larger than `gdsjurisdiction`. Likely the table that records the actual *granted* permissions per user, where `gdsjurisdiction` is the *defined* rules the framework applies. ## How permissions are loaded When the SPA calls `/business/getModelBysId/{moduleId}`, the service-layer pseudocode is: ```java if (!UserType.ADMIN.equals(userType)) { jList = businessGdsconfigformsService.getJurisdictionData(qMap); } returnMap.put("gdsjurisdiction", jList); ``` — from `BusinessBaseServiceImpl.java:196-198`. **ADMIN bypasses permissions entirely** and gets an empty jurisdiction list (which the SPA interprets as "show everything"). Non-admin users get the rules that apply to their role + module. The frontend then enables/disables buttons and shows/hides columns based on those rules. ## How to set a permission For a new module, the typical path is: 1. Define the module + form ([recipe](define-form.md)). 2. For each role that should NOT have full access, insert `gdsjurisdiction` rows describing what they cannot do. 3. (Or insert per-user grants in `sysjurisdiction` if more granularity is needed.) 4. Save through BACK so cache invalidation propagates. The exact column conventions for the rule rows (which combinations of fields express "can save but not delete", "can see but not edit", etc.) deserve their own walkthrough that the wiki should add as a follow-up. ## ADMIN as a special case The hardcoded ADMIN bypass (line 196-198 above) is a real escape hatch. Every deployment has at least one ADMIN account by convention. From a security audit perspective, ADMIN access should be tightly controlled and audited externally — the framework offers no within-app constraint. ## Cross-tenant boundary is *not* permissions Worth saying explicitly: `gdsjurisdiction` enforces *within-tenant* permissions. The cross-tenant boundary (one company can't read another company's rows) is enforced by the [multi-tenant scoping](../../concepts/multi-tenancy.md) — automatic `sBrandsId`/`sSubsidiaryId` injection, not jurisdiction rules.