{
  "validationRules": [
    {
      "id": "vrule_lead_post_convert_requires_view_edit_permission",
      "objectApiName": "Lead",
      "name": "Lead_Post_Convert_Requires_AllowViewEditConvertedLeads",
      "description": "Salesforce v63 parity: editing a converted Lead requires the AllowViewEditConvertedLeads permission (SF v63 canonical: PermissionsAllowViewEditConvertedLeads). Users with this permission can still edit non-anchor fields; users without it are blocked from any post-convert mutation. Anchor fields (IsConverted, ConvertedAccountId, ConvertedContactId, ConvertedOpportunityId, ConvertedDate) remain immutable regardless of this permission — see the per-anchor rules below.",
      "active": true,
      "condition": "PRIORVALUE(IsConverted) = TRUE && $Permission.AllowViewEditConvertedLeads != true",
      "errorMessage": "Editing a converted Lead requires the View and Edit Converted Leads permission."
    },
    {
      "id": "vrule_lead_isconverted_immutable_post_convert",
      "objectApiName": "Lead",
      "name": "Lead_IsConverted_Immutable_Post_Convert",
      "description": "Salesforce v63 parity: once a Lead is converted, IsConverted cannot be toggled back to false. Other Lead fields remain editable; per-anchor rules cover the other convert-anchor fields.",
      "active": true,
      "condition": "PRIORVALUE(IsConverted) = TRUE && ISCHANGED(IsConverted)",
      "errorMessage": "Cannot un-convert a converted lead."
    },
    {
      "id": "vrule_lead_converted_account_immutable",
      "objectApiName": "Lead",
      "name": "Lead_ConvertedAccountId_Immutable_Post_Convert",
      "description": "Salesforce v63 parity: ConvertedAccountId is stamped by the convert process and cannot be modified afterward.",
      "active": true,
      "condition": "PRIORVALUE(IsConverted) = TRUE && ISCHANGED(ConvertedAccountId)",
      "errorMessage": "ConvertedAccountId cannot be changed after lead conversion."
    },
    {
      "id": "vrule_lead_converted_contact_immutable",
      "objectApiName": "Lead",
      "name": "Lead_ConvertedContactId_Immutable_Post_Convert",
      "description": "Salesforce v63 parity: ConvertedContactId is stamped by the convert process and cannot be modified afterward.",
      "active": true,
      "condition": "PRIORVALUE(IsConverted) = TRUE && ISCHANGED(ConvertedContactId)",
      "errorMessage": "ConvertedContactId cannot be changed after lead conversion."
    },
    {
      "id": "vrule_lead_converted_opportunity_immutable",
      "objectApiName": "Lead",
      "name": "Lead_ConvertedOpportunityId_Immutable_Post_Convert",
      "description": "Salesforce v63 parity: ConvertedOpportunityId is stamped by the convert process and cannot be modified afterward.",
      "active": true,
      "condition": "PRIORVALUE(IsConverted) = TRUE && ISCHANGED(ConvertedOpportunityId)",
      "errorMessage": "ConvertedOpportunityId cannot be changed after lead conversion."
    },
    {
      "id": "vrule_lead_converted_date_immutable",
      "objectApiName": "Lead",
      "name": "Lead_ConvertedDate_Immutable_Post_Convert",
      "description": "Salesforce v63 parity: ConvertedDate is stamped by the convert process and cannot be modified afterward.",
      "active": true,
      "condition": "PRIORVALUE(IsConverted) = TRUE && ISCHANGED(ConvertedDate)",
      "errorMessage": "ConvertedDate cannot be changed after lead conversion."
    },
    {
      "id": "vrule_order_activate_requires_contract",
      "objectApiName": "Order",
      "name": "Order_Activate_Requires_Contract",
      "description": "Order activation requires an associated Contract.",
      "active": true,
      "condition": "ISCHANGED(Status) && Status == \"Activated\" && ISBLANK(ContractId)",
      "errorMessage": "Cannot activate order: no Contract is associated"
    },
    {
      "id": "vrule_order_activate_requires_permission",
      "objectApiName": "Order",
      "name": "Order_Activate_Requires_Permission",
      "description": "Order activation requires the ActivateOrder permission on direct record updates as well as UI action invocation.",
      "active": true,
      "condition": "ISCHANGED(Status) && Status == \"Activated\" && $Permission.ActivateOrder != true",
      "errorMessage": "Cannot activate order: missing ActivateOrder permission"
    },
    {
      "id": "vrule_order_activate_contract_status",
      "objectApiName": "Order",
      "name": "Order_Activate_Contract_Status",
      "description": "Order activation requires the associated Contract to be activated or effective.",
      "active": true,
      "condition": "ISCHANGED(Status) && Status == \"Activated\" && !ISBLANK(ContractId) && Contract.Status != \"Activated\" && Contract.Status != \"Effective\"",
      "errorMessage": "Cannot activate order: the associated Contract must be in Activated/Effective status"
    },
    {
      "id": "vrule_order_activate_contract_start",
      "objectApiName": "Order",
      "name": "Order_Activate_Contract_Start",
      "description": "Order activation requires the Contract start date to cover the Order effective date.",
      "active": true,
      "condition": "ISCHANGED(Status) && Status == \"Activated\" && !ISBLANK(EffectiveDate) && (ISBLANK(Contract.StartDate) || Contract.StartDate > EffectiveDate)",
      "errorMessage": "Cannot activate order: the associated Contract must start on or before the Order EffectiveDate"
    },
    {
      "id": "vrule_order_activate_contract_end",
      "objectApiName": "Order",
      "name": "Order_Activate_Contract_End",
      "description": "Order activation requires the Contract end date to cover the Order end date.",
      "active": true,
      "condition": "ISCHANGED(Status) && Status == \"Activated\" && !ISBLANK(EndDate) && (ISBLANK(Contract.EndDate) || Contract.EndDate < EndDate)",
      "errorMessage": "Cannot activate order: the associated Contract must end on or after the Order EndDate"
    },
    {
      "id": "vrule_order_post_activate_read_only",
      "objectApiName": "Order",
      "name": "Order_Post_Activate_Readonly",
      "description": "Prevents edits to activated Order records through generic validation metadata.",
      "active": true,
      "condition": "PRIORVALUE(Status) == \"Activated\"",
      "errorMessage": "This order has already been activated. No further changes are allowed."
    },
    {
      "id": "vrule_contract_post_activate_lock",
      "objectApiName": "Contract",
      "name": "Contract_Post_Activate_Lifecycle_Lock",
      "description": "Locks core lifecycle fields on activated/effective contracts while still allowing lifecycle status progression.",
      "active": true,
      "condition": "(PRIORVALUE(Status) == \"Activated\" || PRIORVALUE(Status) == \"Effective\" || PRIORVALUE(Status) == \"Expired\" || PRIORVALUE(Status) == \"Terminated\" || Status == \"Activated\" || Status == \"Effective\" || Status == \"Expired\" || Status == \"Terminated\") && (ISCHANGED(AccountId) || ISCHANGED(StartDate) || ISCHANGED(ContractTerm) || ISCHANGED(EndDate) || ISCHANGED(Pricebook2Id))",
      "errorMessage": "Activated contracts lock core lifecycle fields; only allowed status transitions may proceed."
    },
    {
      "id": "vrule_sharing_rule_target_exactly_one",
      "objectApiName": "SharingRule",
      "name": "SharingRule_Target_Exactly_One",
      "description": "SharingRule records must target exactly one role or group through ShareWithRoleId or ShareWithGroupId.",
      "active": true,
      "condition": "(ISBLANK(ShareWithRoleId) && ISBLANK(ShareWithGroupId)) || (!ISBLANK(ShareWithRoleId) && !ISBLANK(ShareWithGroupId))",
      "errorMessage": "sharing rule must target exactly one role or group"
    },
    {
      "id": "vrule_sharing_rule_criteria_forbids_shared_from",
      "objectApiName": "SharingRule",
      "name": "SharingRule_Criteria_Forbids_SharedFrom",
      "description": "Criteria-based SharingRule records are defined by Criteria and cannot also carry SharedFromRoleId or SharedFromGroupId.",
      "active": true,
      "condition": "!ISBLANK(Criteria) && (!ISBLANK(SharedFromRoleId) || !ISBLANK(SharedFromGroupId))",
      "errorMessage": "criteria-based sharing rules cannot define SharedFromRoleId or SharedFromGroupId"
    },
    {
      "id": "vrule_sharing_rule_owner_based_requires_one_shared_from",
      "objectApiName": "SharingRule",
      "name": "SharingRule_Owner_Based_Requires_One_SharedFrom",
      "description": "Owner-based SharingRule records must define exactly one owner source through SharedFromRoleId or SharedFromGroupId.",
      "active": true,
      "condition": "ISBLANK(Criteria) && ((ISBLANK(SharedFromRoleId) && ISBLANK(SharedFromGroupId)) || (!ISBLANK(SharedFromRoleId) && !ISBLANK(SharedFromGroupId)))",
      "errorMessage": "owner-based sharing rules must define exactly one SharedFromRoleId or SharedFromGroupId"
    },
    {
      "id": "vrule_identitysyncsetting_singleton_name",
      "objectApiName": "IdentitySyncSetting",
      "name": "IdentitySyncSetting_Name_Must_Be_Default",
      "description": "IdentitySyncSetting is a singleton: the only permitted Name is \"default\". Combined with the UNIQUE constraint on Name, this makes at most one configuration row possible (Name=\"default\"). Public standard REST mutation surfaces are closed by the CRM dialect; the deploying L2 pack authors the default row and the serving layer reads only Name=\"default\".",
      "active": true,
      "condition": "Name != \"default\"",
      "errorMessage": "IdentitySyncSetting is a singleton; the only permitted Name is \"default\"."
    }
  ]
}
