8.5 KiB
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 itselfTupleRead: cascade rewriter — expands object-level condition to also coverresource.object.*for fields. Use when the intent is "objects matching X AND all their fields"- Multiple
<action>entries: only the first is kept on save
<ruleConsumer> : only DATACLOUD (hardcoded on save)
<principalScopeType> : only ANY (hardcoded on save)
<resourceScopeType> : ANY, RECORD, FIELD, DATASPACE, SPAN
<principalAuthenticationLevel> : INTERNAL, AUTHENTICATED, UNIDENTIFIED. IDENTIFIED not exposed.
<replicated> : read but NOT editable. Once true, UI forces read-only mode.
<transformPrecedence> : not exposed; non-zero values dropped.
Conditions — Supported Shapes
Top-level conjunction: only <whenPolicyRuleDefinitionClauseConjunction>. The UI forces <unlessPolicyRuleDefinitionClauseConjunction> 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_ALLexists 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):EXISTSonly
<policyRuleValueSet> 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 <resourceDomain> 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:
categoryisACCESS_POLICY_RULE_DEFINITIONorIDENTIFIED_RECORD_POLICY_RULE_DEFINITIONruleConsumer≠DATACLOUD- More than one
<action>, or action is notRead/TupleRead - No top-level
resourcePath=ENTITYTYPEdiscriminator group for ACCESS/GOVERNANCE rules — this causes a hard crash (Cannot use 'in' operator to search for 'Permit' in undefined), not a graceful degradation. ATupleReadrule without the OR-of-ENTITYTYPE clause still deploys and works at runtime — the crash is UI-only. - Bare top-level condition index in
<conjunctionExpression>(e.g.3instead of(AND 3)) — causes crash inbuildCriteria(param.params.map(...)=undefined.map()) - More than one
<resourceDomain> - An
<unlessPolicyRuleDefinitionClauseConjunction>block present - Any
<conditions>uses<clause>UNLESS</clause> <conjunctionExpression>uses top-levelOR(e.g.(OR 1 2))<principalPath>other thanASSIGNED_PERMISSIONS_PATH(user ID etc. may appear only inside SOQL${User.X}substitutions)<resourcePath>other thanTAG,CLASSIFICATION,DATASPACE,RECORDFIELD,EXPRESSION,ENTITYTYPE<valuePrincipalPath>is not one ofUSER_ID,USER_ROLE_ID,ORGANIZATION_ID,SCALAR_ATTRIBUTE,PLURAL_ATTRIBUTE(with${User.X}expression)<contextPath>other thanSESSION_DATASPACE- Operator is
HIERARCHICALLY_BELOW,IN, orCONTAINS_ALL <valueReferenceType>isHIERARCHYorDATASPACE<valueString>is typed JSON other than{"t":"Text","v":"FIELD"|"RECORD"|"OBJECT"}or{"t":"Boolean","v":…}with a UI-recognized boolean field<resourceTransform>not in the masking-category mapped set<resourceExpression>ARGLIST has multiple arguments<replicated>true</replicated>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:
- Try to make it UI-compatible first. Mechanical rewrites:
unless→whenwith negated operator:Intent UI-compatible rewrite unless principal has permission XWHEN ASSIGNED_PERMISSIONS_PATH CONTAINS_NONE Xunless resource has tag XWHEN TAG CONTAINS_NONE Xunless record field = valueWHEN RECORDFIELD NOT_EQUALS value- Top-level
ORbetween two tags → nest inside a tag/classification section group - Multiple
<action>→ split into multiple rules
- 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
-
Wait for explicit confirmation. Do not write the file until confirmed.
-
On confirmation, add an in-source marker to
<description>(or a comment above the rule):# API-only: not editable in Data Governance Policy Builder. Reasons: <bullets>. Also set<builderCompatible>false</builderCompatible>on the parent set.
UI-Compatibility Matrix for Templates
| Template | UI-compatible? | Notes |
|---|---|---|
| Bare permit, no conditions | Partial | Single <action>Read</action> 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. |