# UI Compatibility — Data Governance Policy Builder The Data Governance Policy Builder edits a **strict subset** of the MDAPI. Anything outside that subset must be maintained via MDAPI/Tooling for the policy's lifetime — admins cannot open it in the builder without silent data loss or a hard crash. > **Default goal**: produce UI-compatible policies. Only fall back to API-only after asking the operator and getting explicit confirmation. --- ## What the UI Can Render **Categories** : `GOVERNANCE_POLICY_RULE_DEFINITION`, `RECORD_POLICY_RULE_DEFINITION`, `TRANSFORM_POLICY_RULE_DEFINITION` **Categories NOT handled** : `ACCESS_POLICY_RULE_DEFINITION` (UI forces to GOVERNANCE on save), `IDENTIFIED_RECORD_POLICY_RULE_DEFINITION` **Effects** : `Permit`, `Forbid` (UI label "Deny"), `Transform` **Actions** : `Read`, `TupleRead` — two real choices for OLS/FLS rules. - `Read`: evaluates conditions against the resource itself - `TupleRead`: cascade rewriter — expands object-level condition to also cover `resource.object.*` for fields. Use when the intent is "objects matching X AND all their fields" - Multiple `` entries: only the first is kept on save **``** : only `DATACLOUD` (hardcoded on save) **``** : only `ANY` (hardcoded on save) **``** : `ANY`, `RECORD`, `FIELD`, `DATASPACE`, `SPAN` **``** : `INTERNAL`, `AUTHENTICATED`, `UNIDENTIFIED`. `IDENTIFIED` not exposed. **``** : read but NOT editable. Once `true`, UI forces read-only mode. **``** : not exposed; non-zero values dropped. --- ## Conditions — Supported Shapes **Top-level conjunction**: only ``. The UI forces `` to `null` on save. **Conjunction expression**: the UI builds `(AND …)` of up to four sections dispatched by path type. A pure flat `(OR 1 2)` at the top level triggers `// ERROR: Unsupported rule!` — rule silently dropped. `OR` only round-trips inside a single section's group. **Path types per UI section:** | UI Section | Recognized path | |------------|-----------------| | Users (Permission) | `principalPath = ASSIGNED_PERMISSIONS_PATH` | | Resource entity-type | `resourcePath = ENTITYTYPE` with `IS` operator | | Data space | `resourcePath = DATASPACE` or `contextPath = SESSION_DATASPACE` | | Tag / Classification | `resourcePath = TAG` or `CLASSIFICATION` | | RLS rule criteria | `resourcePath = RECORDFIELD` or `EXPRESSION` | Any other path type → condition silently ignored when UI rebuilds the rule. **Operators per path**: - Tag/Classification: `CONTAINS_ANY`, `CONTAINS_NONE`. (`CONTAINS_ALL` exists in schema but not offered in dropdowns) - Permission: `CONTAINS_ANY` (Permit) / `CONTAINS_NONE` (Forbid/Transform) - RLS `RECORDFIELD`: `EQUALS`, `NOT_EQUALS`, `LIKE`, `LESS_THAN(_OR_EQUALS)`, `GREATER_THAN(_OR_EQUAL)`, `HIERARCHICALLY_ABOVE` (DLO/DMO only). **Not editable**: `HIERARCHICALLY_BELOW`, `IN`, `CONTAINS_ALL` - RLS join (`EXPRESSION`): `EXISTS` only **`` reference types** : `CUSTOM_TAG`, `STANDARD_TAG`, `CUSTOM_CLASSIFICATION`, `STANDARD_CLASSIFICATION`, `CUSTOM_PERMISSION`. **Not editable**: `HIERARCHY`, `DATASPACE`. --- ## Implicit Defaults Injected by the UI on Save When the UI saves, it auto-injects these — if your hand-authored XML omits them or uses different values, the first UI save rewrites them: | Field | UI default | |-------|-----------| | `principalAuthenticationLevel` | `INTERNAL` | | `principalScopeType` | `ANY` | | `ruleConsumer` | `DATACLOUD` | | `unlessPolicyRuleDefinitionClauseConjunction` | `null` (always) | | `actions` | only first action kept | | `policyRuleResourceDomains` | only first `` kept | --- ## The Non-Round-Trip Checklist The policy will **not** round-trip through the UI without data loss or silent failure if **any** of the following is true: - [ ] `category` is `ACCESS_POLICY_RULE_DEFINITION` or `IDENTIFIED_RECORD_POLICY_RULE_DEFINITION` - [ ] `ruleConsumer` ≠ `DATACLOUD` - [ ] More than one ``, or action is not `Read` / `TupleRead` - [ ] **No top-level `resourcePath=ENTITYTYPE` discriminator group** for ACCESS/GOVERNANCE rules — this causes a **hard crash** (`Cannot use 'in' operator to search for 'Permit' in undefined`), not a graceful degradation. A `TupleRead` rule without the OR-of-ENTITYTYPE clause still deploys and works at runtime — the crash is UI-only. - [ ] **Bare top-level condition index** in `` (e.g. `3` instead of `(AND 3)`) — causes crash in `buildCriteria` (`param.params.map(...)` = `undefined.map()`) - [ ] More than one `` - [ ] An `` block present - [ ] Any `` uses `UNLESS` - [ ] `` uses top-level `OR` (e.g. `(OR 1 2)`) - [ ] `` other than `ASSIGNED_PERMISSIONS_PATH` (user ID etc. may appear only inside SOQL `${User.X}` substitutions) - [ ] `` other than `TAG`, `CLASSIFICATION`, `DATASPACE`, `RECORDFIELD`, `EXPRESSION`, `ENTITYTYPE` - [ ] `` is not one of `USER_ID`, `USER_ROLE_ID`, `ORGANIZATION_ID`, `SCALAR_ATTRIBUTE`, `PLURAL_ATTRIBUTE` (with `${User.X}` expression) - [ ] `` other than `SESSION_DATASPACE` - [ ] Operator is `HIERARCHICALLY_BELOW`, `IN`, or `CONTAINS_ALL` - [ ] `` is `HIERARCHY` or `DATASPACE` - [ ] `` is typed JSON other than `{"t":"Text","v":"FIELD"|"RECORD"|"OBJECT"}` or `{"t":"Boolean","v":…}` with a UI-recognized boolean field - [ ] `` not in the masking-category mapped set - [ ] `` ARGLIST has multiple arguments - [ ] `true` in an org with replication frozen If **none** of the above is true, the policy is **UI-compatible**. > **Important**: The UI shows NO banner when it drops unsupported pieces on save (`// ERROR: Unsupported rule!` comment, no surfaced message). Operator-confirmation is the only line of defense. --- ## Operator-Confirmation Protocol Whenever a request would produce a UI-incompatible policy: 1. **Try to make it UI-compatible first.** Mechanical rewrites: - `unless` → `when` with negated operator: | Intent | UI-compatible rewrite | |--------|----------------------| | `unless principal has permission X` | `WHEN ASSIGNED_PERMISSIONS_PATH CONTAINS_NONE X` | | `unless resource has tag X` | `WHEN TAG CONTAINS_NONE X` | | `unless record field = value` | `WHEN RECORDFIELD NOT_EQUALS value` | - Top-level `OR` between two tags → nest inside a tag/classification section group - Multiple `` → split into multiple rules 2. **If a UI-compatible rewrite is not possible**, tell the operator: - Which traits make it incompatible (cite items from the checklist above) - That the Data Governance Policy Builder will silently drop unsupported pieces on save with no warning - That maintenance must happen through MDAPI/Tooling for the policy's lifetime 3. **Wait for explicit confirmation.** Do not write the file until confirmed. 4. **On confirmation**, add an in-source marker to `` (or a comment above the rule): `# API-only: not editable in Data Governance Policy Builder. Reasons: `. Also set `false` on the parent set. --- ## UI-Compatibility Matrix for Templates | Template | UI-compatible? | Notes | |----------|----------------|-------| | Bare permit, no conditions | Partial | Single `Read` only; `ruleConsumer=DATACLOUD`. UI maps ACCESS → Governance on load. | | Tag / Classification / Permission gate | Yes | `CONTAINS_ANY` / `CONTAINS_NONE` only. Tag refs must resolve in org. | | Basic RLS (RECORDFIELD = user attribute) | Yes | `USER_ID` / `ORGANIZATION_ID` / `USER_ROLE_ID` round-trips. | | TupleRead with OR-of-ENTITYTYPE | Yes | The canonical UI-compatible OLS/FLS shape. | | IDENTIFIED_RECORD | Not MDAPI-authorable | Must be a runtime RuleProvider. | | RLS via PROJECTION join | Yes | Single PROJECTION editable. | | Scalar/plural SOQL principal | Out of MDAPI contract | `SCALAR_ATTRIBUTE`/`PLURAL_ATTRIBUTE` not in `RulePrincipalPathType`. Fixture-only. | | TRANSFORM with permission + classification | Yes | Use masking-category-mapped transforms only. | | `(AND 1 2)` multi-condition | Yes (if each section is one of the four UI buckets) | | | Top-level `(OR 1 2)` | No | Triggers unsupported-rule path; silently dropped. | | Hierarchy traversal | No | `HIERARCHICALLY_BELOW` and `HIERARCHY` not in UI dropdowns. Always API-only. |