UNPKG

@servicenow/sdk

Version:
402 lines (313 loc) 21.6 kB
--- tags: [data policy, field enforcement, mandatory, read-only, server-side validation, import sets, soap, sys_data_policy2, sys_data_policy_rule, data integrity] --- # Data Policy Guide for creating ServiceNow Data Policies (`sys_data_policy2`) using the Fluent API. Data policies enforce field mandatory and read-only rules server-side across all interfaces — forms, imports, web services, and APIs — and cannot be bypassed, unlike client-side UI Policies. ## When to Use **Use Data Policy when:** - User explicitly requests "data policy" or "data policies" - Need ONLY mandatory or read-only enforcement across ALL interfaces (forms, imports, APIs, web services) - Need server-side enforcement that cannot be bypassed - Protecting critical fields from modification after certain conditions (e.g., closed records) - Ensuring data integrity from external sources (imports, API calls, integrations) - A field must be populated / required / non-empty when a condition is true — even if the word "validate" appears in the requirement ### Natural language signals — Data Policy vs UI Policy vs Business Rule The following phrases indicate mandatory/read-only enforcement. **Default to Data Policy for server-side security** unless the prompt explicitly mentions client-side context: | Prompt says… | Means… | Tool | |---|---|---| | "must hold a value", "must carry a value", "must have a value", "is required when", "required if" | Field mandatory under a condition | **DataPolicy** `mandatory: true` | | "locked", "read-only when", "cannot be edited when", "fields must be locked", "prevent modification" | Field read-only under a condition | **DataPolicy** `readOnly: true` | | "cannot be modified once", "immutable after", "freeze fields after", "all fields locked" | Read-only after a state/condition | **DataPolicy** `readOnly: true` (+ Business Rule for transition guard if needed) | | "locked against edits", "prevent changes if [field] was [value]" | Needs comparison to `previous` value | **BusinessRule** `before` — Data Policy cannot access `previous` | **Prefer `table` skill for unconditional enforcement:** For fields that are always mandatory or always read-only, set `mandatory: true` or `read_only: true` directly on the column definition (see the `table-guide` topic). Use Data Policy when enforcement is conditional, OR when you need guaranteed cross-API/import enforcement as an additional security layer. ## Instructions 1. **STOP if prompt mentions visibility or messages:** Check if the prompt mentions ONLY visibility, messages, or value-setting — use UI Policy instead. If both mandatory/read-only AND visibility/value needs exist, create a Data Policy for the mandatory/read-only part and a UI Policy for the rest. 2. **Verify Data Policy is the right tool:** Confirm the requirement is ONLY for mandatory or read-only enforcement. If the user needs visibility control, field values, scripts, or complex logic, use a different skill (see "When to Use" above). 3. **Use the `DataPolicy` API** from `@servicenow/sdk/core`. Every Data Policy must have `$id`, `table`, and `shortDescription`. Every individual rule inside `rules` must also have a `$id`. 4. **`$id` naming convention:** Use descriptive snake_case names in `Now.ID[]` — e.g., `Now.ID['scope_policy_purpose']`. Good: `Now.ID['hr_employee_mandatory_fields']`. Bad: `Now.ID['policy1']`. 5. **Default properties:** See "Default Property Values" section below. Only specify properties when overriding defaults. 6. **Never set both `mandatory: true` and `readOnly: true` on the same field:** These are mutually exclusive — a read-only field cannot be mandatory. This causes a build error. 7. **Conditions use encoded query syntax** — e.g., `"priority=1^state!=6"`. Empty conditions apply to all records. Use `^` for AND, `^OR` for OR. 8. **Always use stored values in conditions:** Choice fields use stored values, not display labels. Right-click field → Show Choice List → use the "Value" column. 9. **Omit `'ignore'` rule properties for cleaner code:** Since `'ignore'` is the default, only specify `mandatory` or `readOnly` when you need to change the field's state. 10. **Use dot-walk notation for cross-table rules:** To enforce rules on fields of a referenced table, use `'reference_field.target_field'` as the rule key (e.g., `'caller_id.email'`). Multiple levels are supported (e.g., `'employee.manager.department'`). 11. **Do not make fields mandatory for out-of-scope tables:** The mandatory flag is silently disabled for tables outside the current scope. Read-only rules are not affected. 12. **Competing policies — most restrictive wins:** When multiple policies target the same field on the same record, ServiceNow applies the most restrictive result. There is no evaluation order. To release a constraint, tighten the condition on the restrictive policy itself rather than relying on a second policy with `mandatory: false`. ## Key Concepts ### Data Policy vs UI Policy | Aspect | Data Policy | UI Policy | |--------|-------------|-----------| | Execution | Server-side | Client-side (browser) | | Can be bypassed via API? | No | Yes | | Applies to imports | Yes (default) | No | | Applies to REST/SOAP | Yes (default) | No | | Visibility control | No | Yes | | Field value actions | No | Yes | | Scripting support | No | Yes | | Field messages | No | Yes | Use Data Policies for **data integrity and security**. Use UI Policies for **UX enhancements**. **Use UI Policy instead when prompt includes:** - "on the form only", "in the UI only", "to the user", "for the user" - Visibility requirements alongside mandatory/read-only ("show section and make field required") - Messages or warnings ("make mandatory and display a message") - Client-side only context ("prevent users from editing on the form") **Use both Data Policy + UI Policy when:** - Prompt requires server-side enforcement (API/imports) AND client-side UX (messages/visibility) - Example: "Make vendor mandatory for high-value contracts and show a warning message" → Data Policy (mandatory) + UI Policy (message) **For detailed UI Policy usage,** see the `ui-policy-guide` topic. **Cross-policy conflict:** If a UI Policy makes a field optional but a Data Policy makes it mandatory, the field is mandatory on save. Server-side always takes precedence. ### Data Policy vs Business Rule | Scenario | Data Policy | Business Rule | |---|---|---| | Field must be mandatory/read-only under a condition | ✅ | — | | Field must be mandatory across all interfaces (imports, APIs) | ✅ | — | | Need a custom error message | — | ✅ | | Condition requires scripting or cross-field logic | — | ✅ | | Auto-populate or calculate a field value | — | ✅ | | Cascade updates to related records | — | ✅ | | Enforce on direct GlideRecord with `setWorkflow(false)` | — | ✅ | | Enforcement on out-of-scope table fields (mandatory) | — | ✅ | **Use Business Rule instead when:** - Need to compare field's `previous` value (e.g., "prevent changes if field was X") - Need custom error messages or validation logic - Need to auto-populate or calculate field values (e.g., "auto-populate field from another field") - Condition requires scripting or complex cross-field logic - Need transition guards (e.g., additional validation when state changes) **Decision rule:** If the requirement can be expressed as "when [condition] is true, field X must have a value / must be read-only" **without mentioning forms, UI, or messages** — that is always a Data Policy, not a Business Rule or UI Policy. Only reach for a Business Rule when the requirement needs `previous`, scripting, custom messaging, or value manipulation that Data Policy cannot express. **For detailed Business Rule usage,** see the `business-rule-guide` topic. ### Default Property Values These properties default to `true` and **should not be written in code unless overriding**: | Property | Default | Override to `false` when... | |----------|---------|------------------------------| | `active` | `true` | Deliberately disabling the policy | | `applyToImportSets` | `true` | Policy should not apply to imports | | `applyToSOAP` | `true` | Policy should not apply to REST/SOAP | | `useAsUiPolicyOnClient` | `true` | Only server-side enforcement is wanted | | `reverseIfFalse` | `true` | Rules should remain even when conditions are false | These default to `false` and **should not be written in code unless overriding**: | Property | Default | Override to `true` when... | |----------|---------|----------------------------| | `inherit` | `false` | Policy should apply to child tables in hierarchy | ### When Policies Apply - **No conditions (omitted)**: Policy applies to ALL records on the table - **With conditions**: Policy applies only when conditions evaluate to true - **`reverseIfFalse: true` (default)**: Rules invert when conditions are false (mandatory → optional, read-only → editable) - **`reverseIfFalse: false`**: No action when conditions are false — rules only apply when conditions match - **Competing policies**: When multiple policies match the same record and target the same field, `mandatory: true` from any matching policy overrides `mandatory: false` from another. To release a mandatory constraint for a specific case, tighten the condition on the mandatory policy itself. ### Table Inheritance - **`inherit: false`** (default): Policy applies only to the specified table - **`inherit: true`**: Policy applies to all tables that extend the specified table (e.g., `task``incident`, `problem`, `change`) Use inheritance when child tables should follow the same rules as their parent without defining the policy separately on each. ### Condition Syntax **AND operator (`^`)** -- all conditions must be true: ``` conditions: "priority=1^state=2" ``` **OR operator (`^OR`)** -- at least one must be true: ``` conditions: "priority=1^ORpriority=2" ``` **Common operators:** `=`, `!=`, `>`, `>=`, `<`, `<=`, `IN`, `LIKE`, `NOT LIKE`, `STARTSWITH`, `ANYTHING`, `EMPTYSTRING`, `SAMEAS`, `NSAMEAS`, `BETWEEN` **Choice field patterns:** | Pattern | Condition Example | |---------|-------------------| | Specific value | `category=hardware` | | Multiple values (OR) | `categoryINhardware,software` | | Not a value | `category!=hardware` | | Any value selected | `category!=NULL` | | No value selected | `category=NULL` | ### Rules Configuration Basic rule syntax for field enforcement: ```typescript rules: { field_name: { $id: Now.ID['policy_name_field_name_rule'], mandatory?: boolean | 'ignore', // Default: 'ignore' readOnly?: boolean | 'ignore' // Default: 'ignore' } } ``` - **$id** (required): Unique identifier for the rule record - **mandatory** (optional): `true` = required, `false` = optional, `'ignore'` = no change - **readOnly** (optional): `true` = locked, `false` = editable, `'ignore'` = no change **Important:** - Each field can have only one rule per policy - Cannot set both `mandatory: true` and `readOnly: true` on the same field - Omit properties set to `'ignore'` for cleaner code ### Cross-Table Enforcement Use dot-walk notation in rule keys to enforce rules on fields in referenced tables: ```typescript rules: { 'caller_id.email': { $id: Now.ID['policy_caller_email_rule'], mandatory: true }, 'assigned_to.department': { $id: Now.ID['policy_assignee_dept_rule'], readOnly: true } } ``` **Technical note:** While a `table` property exists in `DataPolicyRuleConfig` for specifying which table a rule applies to, **dot-walk notation is the recommended approach** for cross-table enforcement. The `table` property is primarily used by the plugin when transforming from ServiceNow XML and for internal scope validation. For new code, always use dot-walk notation in rule keys (e.g., `'employee.department'`) — it is clearer and more maintainable. Never use the `table` property inside rules for cross-table enforcement. ## API Reference See the `datapolicy-api` topic for the full property reference. ## Examples ### Conditional Mandatory -- Require Assignment for High Priority Records Make fields mandatory for high-priority records. `reverseIfFalse` defaults to `true` — fields become optional automatically when conditions are not met. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const highPriorityPolicy = DataPolicy({ $id: Now.ID['high_priority_incident_policy'], table: 'incident', shortDescription: 'Require assignment for high priority incidents', conditions: 'priority=1^ORpriority=2', rules: { assigned_to: { $id: Now.ID['high_priority_incident_policy_assigned_to_rule'], mandatory: true, }, assignment_group: { $id: Now.ID['high_priority_incident_policy_assignment_group_rule'], mandatory: true, }, }, }) ``` ### Conditional Read-Only -- Lock Fields After Record Closure Lock resolution fields once an incident is resolved or closed. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const closedIncidentProtection = DataPolicy({ $id: Now.ID['closed_incident_protection'], table: 'incident', shortDescription: 'Protect closed incident fields from modification', conditions: 'state=6^ORstate=7', rules: { close_code: { $id: Now.ID['closed_incident_protection_close_code_rule'], readOnly: true }, close_notes: { $id: Now.ID['closed_incident_protection_close_notes_rule'], readOnly: true }, resolved_at: { $id: Now.ID['closed_incident_protection_resolved_at_rule'], readOnly: true }, resolved_by: { $id: Now.ID['closed_incident_protection_resolved_by_rule'], readOnly: true }, }, }) ``` ### Unconditional Mandatory -- Validate All Records Including Imports and APIs Ensure records arriving from external systems have required fields. Omitting `conditions` applies the policy to all records. `applyToImportSets` and `applyToSOAP` default to `true`no extra config needed. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const userImportValidation = DataPolicy({ $id: Now.ID['user_import_validation'], table: 'sys_user', shortDescription: 'Validate user imports and API calls', rules: { user_name: { $id: Now.ID['user_import_validation_user_name_rule'], mandatory: true }, email: { $id: Now.ID['user_import_validation_email_rule'], mandatory: true }, first_name: { $id: Now.ID['user_import_validation_first_name_rule'], mandatory: true }, last_name: { $id: Now.ID['user_import_validation_last_name_rule'], mandatory: true }, }, }) ``` ### Mixed Rules -- Different Enforcement per Field in One Policy Apply different rule types to different fields in one policy. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const comprehensivePolicy = DataPolicy({ $id: Now.ID['comprehensive_policy'], table: 'incident', shortDescription: 'Comprehensive field enforcement for critical incidents', conditions: 'priority=1^urgency=1', rules: { short_description: { $id: Now.ID['comprehensive_policy_short_description_rule'], mandatory: true }, state: { $id: Now.ID['comprehensive_policy_state_rule'], readOnly: true }, priority: { $id: Now.ID['comprehensive_policy_priority_rule'], mandatory: true, readOnly: false }, category: { $id: Now.ID['comprehensive_policy_category_rule'], readOnly: true }, subcategory: { $id: Now.ID['comprehensive_policy_subcategory_rule'], mandatory: true }, }, }) ``` ### Inherited Policy -- Apply Once, Enforce Across a Table Hierarchy Apply a policy to a parent table so all child tables automatically inherit it. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const taskAssignmentPolicy = DataPolicy({ $id: Now.ID['task_assignment_policy'], table: 'task', shortDescription: 'Require assignment for in-progress tasks', description: 'Applies to all tables extending task (incident, problem, change, etc.)', inherit: true, conditions: 'state=2', rules: { assigned_to: { $id: Now.ID['task_assignment_policy_assigned_to_rule'], mandatory: true }, assignment_group: { $id: Now.ID['task_assignment_policy_assignment_group_rule'], mandatory: true }, }, }) ``` ### Cross-Table -- Enforce Rules on Referenced Fields via Dot-Walk Enforce rules on fields in referenced tables using dot-walk notation as rule keys. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const incidentReferenceValidation = DataPolicy({ $id: Now.ID['incident_reference_validation'], table: 'incident', shortDescription: 'Enforce caller contact information for high priority incidents', conditions: 'priority=1^urgency=1', rules: { 'caller_id.email': { $id: Now.ID['incident_reference_validation_caller_id_email_rule'], mandatory: true }, 'caller_id.phone': { $id: Now.ID['incident_reference_validation_caller_id_phone_rule'], mandatory: true }, 'assigned_to.department': { $id: Now.ID['incident_reference_validation_assigned_to_department_rule'], readOnly: true }, 'assignment_group.manager': { $id: Now.ID['incident_reference_validation_assignment_group_manager_rule'], mandatory: true }, }, }) ``` ### Multi-Condition -- Strict Validation Using IN and AND Operators Combine multiple condition operators to enforce strict requirements for a specific record type and state. The `IN` operator matches multiple states, `^` joins conditions with AND. ```typescript fluent import { DataPolicy } from '@servicenow/sdk/core' export const emergencyChangeValidation = DataPolicy({ $id: Now.ID['emergency_change_validation'], table: 'change_request', shortDescription: 'Strict validation for emergency changes', description: 'Emergency changes require CAB approval, detailed justification, and rollback plan', conditions: 'type=emergency^stateIN1,2,3', rules: { cab_delegate: { $id: Now.ID['emergency_change_validation_cab_delegate_rule'], mandatory: true }, justification: { $id: Now.ID['emergency_change_validation_justification_rule'], mandatory: true }, backout_plan: { $id: Now.ID['emergency_change_validation_backout_plan_rule'], mandatory: true }, risk_impact_analysis: { $id: Now.ID['emergency_change_validation_risk_impact_analysis_rule'], mandatory: true }, emergency_contact: { $id: Now.ID['emergency_change_validation_emergency_contact_rule'], mandatory: true }, }, }) ``` ### Layered Validation -- Data Policy for Security, UI Policy for UX Use Data Policy for server-side security and UI Policy for client-side UX — together for complete coverage. ```typescript fluent import { DataPolicy, UiPolicy } from '@servicenow/sdk/core' // Data Policy: server-side enforcement (cannot be bypassed) export const contractDataPolicy = DataPolicy({ $id: Now.ID['contract_approval_data_policy'], table: 'ast_contract', shortDescription: 'Enforce contract approval requirements', conditions: 'value>100000', rules: { vendor: { $id: Now.ID['contract_approval_data_policy_vendor_rule'], mandatory: true }, approver: { $id: Now.ID['contract_approval_data_policy_approver_rule'], mandatory: true }, legal_review: { $id: Now.ID['contract_approval_data_policy_legal_review_rule'], mandatory: true }, }, }) // UI Policy: client-side visibility and messages export const contractUiPolicy = UiPolicy({ $id: Now.ID['contract_approval_ui_policy'], table: 'ast_contract', shortDescription: 'Show approval section for high-value contracts', onLoad: true, conditions: 'value>100000', actions: [ { field: 'approval_section', visible: true }, { field: 'approval_notes', visible: true, mandatory: true }, { field: 'vendor', fieldMessage: 'Vendor approval required for contracts over $100,000', fieldMessageType: 'warning' }, ], }) ``` ## Avoidance - **Never include default value properties in code** — do NOT write `active: true`, `applyToImportSets: true`, `applyToSOAP: true`, `useAsUiPolicyOnClient: true`, `reverseIfFalse: true`, `inherit: false`, or `conditions: ''` - **Never set both `mandatory: true` and `readOnly: true` on the same field** — mutually exclusive; causes a build error - **Never use display labels in conditions** — always use stored values for choice fields (right-click field → Show Choice List → "Value" column) - **Never make fields mandatory for out-of-scope tables** — the mandatory flag is silently disabled; use a Business Rule as an alternative (see the `business-rule-guide` topic) - **Never use a `table` property inside rules for cross-table enforcement** — use dot-walk notation as the rule key (`'employee.department'`) instead - **Never create conflicting Data Policies** — overlapping conditions with opposing rules cause unpredictable results; most restrictive wins - **Never use Data Policies for visibility control** — use UI Policies instead (see the `ui-policy-guide` topic) - **Never rely on UI Policies alone for critical validation** — they can be bypassed via API, imports, or browser manipulation (see the `ui-policy-guide` topic) - **Never create policies with empty conditions unintentionally** — omitting `conditions` applies the policy to ALL records on the table; be deliberate