โšก Advanced โ€” Platform Rules ๐Ÿ‡ฆ๐Ÿ‡ท Comuniqa Examples Edition 2.3 ยท Winter '26

Constraint Modeling
Language (CML)

The recommended approach for product configuration rules in Agentforce Revenue Management. Understand CML's core concepts, constraint types, and how to apply them to Comuniqa's bundle and qualification scenarios.

01 Foundation

Foundational Definitions

CML is a domain-specific language used to define constraint models for complex product configuration. It describes real-world entities, their relationships, and business rules declaratively โ€” without extensive custom code. The constraint engine compiles CML into a working configuration model at runtime.

๐Ÿ—๏ธ

Constraint Model

The complete CML file describing a product's structure, attributes, and rules. Compiled by the constraint engine when a product is configured in Product Configurator.

๐Ÿงฑ

Type

The foundational building block of CML. Represents an entity โ€” a product, bundle, component, or class. Similar to a class in object-oriented programming.

๐Ÿ”ข

Variable

A property or characteristic defined within a type. Can be a string, number, boolean, date, or picklist. Represents product attributes and fields.

๐Ÿ”—

Relationship

Defines how types are associated. In Revenue Cloud, relationships represent the product structure in a bundle โ€” the root product's relationship to its components.

โš™๏ธ

Constraint

A logical rule applied to types, variables, or relationships. Enforces business logic โ€” e.g. "if Contract Term is 24 months, Watch Device Type must be specified".

๐ŸŒ

External Variable

A global CML variable set by the environment that launches the constraint engine. Used to import sales transaction fields from the Context Definition.

๐Ÿ“

Group Type

A special CML type representing a PCM product component group. Identified by minInstanceQty and maxInstanceQty annotations. Controls how many components a customer can select from a group.

๐Ÿ’ฌ

Rule

A specific type of constraint: require, exclude, hide, disable, message, preference, recommend. Each triggers a different runtime behavior in Product Configurator.

How CML Fits Into the Revenue Cloud Architecture

PCM
Bundle Structure
โ†’
CML Editor
Import + Write Rules
โ†’
Constraint Engine
Compiles CML
โ†’
Product Configurator
Runtime Enforcement
โ†’
Quote / Order
Valid Configuration
Critical Rule โ€” Bundle Scope To define a constraint for a child product in a bundle, you must include the entire bundle in the constraint model. For example, to constrain MoviMundo Plus+ inside Comuniqa Go!, the entire Comuniqa Go! bundle must be in the CML model โ€” not just MoviMundo Plus+ alone.
02 CML vs BRE

CML vs Business Rules Engine โ€” Why CML Wins

Salesforce Revenue Cloud offers two approaches to writing product configuration and qualification rules: the Business Rules Engine (BRE) and Constraint Modeling Language (CML). For all advanced implementations, CML is the recommended path.

DimensionBRE (Legacy)CML (Recommended)
ApproachPoint-and-click Decision Tables + Expression SetsDeclarative domain-specific language with full IDE support
Complexity ceilingLimited โ€” struggles with nested bundle constraints and multi-attribute rulesHandles highly complex constraint models with type hierarchies and group nesting
Bundle group supportBasic cardinality onlyFull Group Type support with minInstanceQty / maxInstanceQty and dot-notation constraints
ReadabilityRules scattered across multiple records; hard to auditAll rules in one CML file โ€” readable, versionable, reviewable
Performance toolsLimited debuggingFull Apex debug log with execution stats, backtrack counts, and constraint violation tracing
Product Selling ModelNot supported in rulesCan set PSM via constraint: productSellingModel == PSM_ID
RecommendationsNot availablerecommend rule for AI-assisted product suggestions
Salesforce guidanceContinue supporting pre-Winter '26 modelsRecommended for all new implementations (Winter '26+)
๐Ÿ‡ฆ๐Ÿ‡ท Comuniqa Design Decision For all Comuniqa configuration rules โ€” MoviMundo mutual exclusivity, Vida Digital cardinality enforcement, watch device type constraints, and qualification rules โ€” CML is the implementation path. BRE may be used for simple lookup-based pricing elements (e.g. Decision Tables for price book lookups) but CML handles all configuration and eligibility logic.
03 Core Concepts

CML Core Concepts

1 โ€” Types

Types are the building blocks of CML. Every product, bundle, and component is represented as a type. Types support inheritance โ€” a subtype inherits all variables and constraints from its parent type.

Type Declaration โ€” Basic & Inheritance
// Basic type declaration
type ComuniqaGo;

// Type hierarchy โ€” subtype inherits from parent
type MobileProduct;
type ComuniqaGo : MobileProduct;     // inherits all MobileProduct variables
type ComuniqaGoYouth : ComuniqaGo;  // inherits ComuniqaGo + MobileProduct

2 โ€” Variables

Variables define the properties of a type. They can have fixed domains (allowed values), be calculated from other variables, or be mapped from context definitions. Annotations control runtime behaviour.

Variable Declarations โ€” Comuniqa Go! Attributes
type ComuniqaGo : LineItem {
    // Picklist variable with fixed domain
    @(defaultValue = "No Contract", sequence=1)
    string Contract_Term = ["No Contract", "12 Months", "24 Months"];

    // Picklist variable โ€” SIM format
    @(defaultValue = "Physical SIM", sequence=2)
    string SIM_Format = ["Physical SIM", "eSIM"];

    // Read-only โ€” populated by fulfillment system
    @(configurable = false)
    string ICCID;

    // Static descriptor โ€” no domain, value fixed in catalog
    string Voice_Calls_Included = "Unlimited";
}

3 โ€” External Variables & Context Path

External variables pull values from the sales transaction context โ€” account data, customer fields, or header values. They are the bridge between CML and the customer's real data.

External Variables โ€” Comuniqa Qualification Context
// External variables set by the ProductDiscoveryContext
@(contextPath = "SalesTransaction.customerAge")
extern int customerAge = 0;

@(contextPath = "SalesTransaction.outstandingAmount")
extern decimal(2) outstandingAmount = 0.00;

@(contextPath = "SalesTransaction.creditScore")
extern int creditScore = 999;

4 โ€” Relationships & Group Types

Relationships define how bundle components connect to their parent. In Winter '26+, product component groups from PCM are imported as Group Types โ€” a distinct CML type with minInstanceQty and maxInstanceQty annotations that mirror PCM cardinality.

Group Types โ€” Comuniqa Go! Bundle Structure in CML
// Partner VAS Group โ€” Max 1 enforces mutual exclusivity
@(minInstanceQty=0, maxInstanceQty=1)
type PartnerVASGroup {
    relation moviMundoPlus : MoviMundoPlus;
    relation moviMundoBasic : MoviMundoBasic;
}

// Value-Added Services Group โ€” Max 3
@(minInstanceQty=0, maxInstanceQty=3)
type ValueAddedServicesGroup {
    relation familyBundle : ComuniqaFamilyGoBundle;
    relation goTwo : ComuniqaGoTwo;
    relation goSharedTwo : ComuniqaGoSharedTwo;
}

// Bundle root uses group types as variables
type ComuniqaGoBundleRoot : LineItem {
    PartnerVASGroup     partnerVASGroup;
    ValueAddedServicesGroup vasGroup;
    // Constraints reference groups via dot notation
}
Winter '26 Key Change โ€” Group Types Are Required From Winter '26, when you import a bundle into the CML Editor, the product component group structure is automatically included. Constraints on child products inside groups must use dot notation starting with the group variable name: partnerVASGroup.moviMundoPlus.someAttribute. The old syntax without group references is only supported for pre-Winter '26 constraint models.
04 Constraint Types

Constraint Types โ€” Complete Reference

constraint() require() exclude() message() preference() rule() โ€” hide/disable table constraint rule() โ€” recommend
โš™๏ธ constraint() โ€” Logical Constraint โ–ผ

The core constraint type. Defines a statement that must always be true. Supports all logical operators: &&, ||, -> (implication), <-> (equivalence), ^ (xor), ?: (conditional).

type ComuniqaGoTwo : LineItem {
    string Watch_Device_Type = ["Apple Watch", "Samsung Galaxy Watch"];
    string Watch_IMEI;

    // Implication: if device type is set, IMEI must be present
    constraint(Watch_Device_Type != null -> Watch_IMEI != null,
        "Watch IMEI is required when a device type is selected");

    // Conditional operator: eSIM requires eSIM-capable device
    constraint(Watch_Device_Type == "Apple Watch" ?
        Watch_IMEI != null : Watch_IMEI != null);
}

Key rule: The constraint engine never overrides user input. If a user sets a value that violates a constraint, the engine displays an error โ€” it does not silently change the user's value. The exception is the exclude() rule.

โœ… require() โ€” Require Rule โ–ผ

Automatically adds a required component to a relationship when specified conditions are met. Can also set attribute values on the required component.

type ComuniqaGoBundleRoot : LineItem {
    // If Go! Two is added, SIM must also be present
    require(vasGroup.goTwo[ComuniqaGoTwo] > 0,
        simGroup.sim[SIMCard]);

    // Require with attribute values set on the required component
    require(vasGroup.goTwo[ComuniqaGoTwo] > 0,
        simGroup.sim[SIMCard]{ SIM_Format = "eSIM" },
        "Apple Watch requires eSIM โ€” SIM format has been set automatically");
}

Note: When assigning a require rule to a virtual bundle, set one Product Selling Model Option on the required product to Default.

๐Ÿšซ exclude() โ€” Exclude Rule โ–ผ

Automatically removes a specific component from a relationship when a condition is met. Unlike other constraints, the engine overrides user input to enforce the exclusion.

type ComuniqaGoBundleRoot : LineItem {
    // Cannot have MoviMundo AND Vida Digital Pass at the same time
    // PCM cardinality handles this, but CML can also enforce explicitly
    exclude(subscriptionsGroup.vidaDigital[VidaDigitalPass] > 0,
        partnerVASGroup.moviMundoPlus[MoviMundoPlus],
        "Vida Digital Pass is mutually exclusive with MoviMundo Plus+");

    exclude(subscriptionsGroup.vidaDigital[VidaDigitalPass] > 0,
        partnerVASGroup.moviMundoBasic[MoviMundoBasic],
        "Vida Digital Pass is mutually exclusive with MoviMundo Basic+");
}

Important: The exclude rule target must be a leaf type โ€” a type with no children. This is the only constraint that overrides user input automatically.

๐Ÿ‘๏ธ rule() โ€” Hide / Disable Rule โ–ผ

Controls the visibility and interactivity of components, attributes, and attribute values in the Product Configurator UI. Used to progressively show relevant options based on current configuration state.

type ComuniqaGoTwo : LineItem {
    string Watch_Device_Type = ["Apple Watch", "Samsung Galaxy Watch"];
    string Watch_IMEI;

    // Hide IMEI field until device type is selected
    rule(Watch_Device_Type == null, "hide", "attribute", "Watch_IMEI");
}

type ComuniqaGoBundleRoot : LineItem {
    // Disable Shared Two if standard Go! Two already selected
    rule(vasGroup.goTwo[ComuniqaGoTwo] > 0,
        "disable", "relation", "vasGroup",
        "type", "ComuniqaGoSharedTwo");

    // Hide Phone Number Category unless type = Premium
    rule(phoneNumGroup.phoneNumber.Phone_Number_Type != "Premium",
        "hide", "attribute", "Phone_Number_Category");
}
๐Ÿ’ฌ message() โ€” Message Rule โ–ผ

Displays a message to users based on specified conditions. Supports three severity levels: Info (gray, non-blocking), Warning (yellow, blocks next step), Error (red, blocks current task).

type ComuniqaGoBundleRoot : LineItem {
    // Info: remind agent about eSIM process
    message(simGroup.sim.SIM_Format == "eSIM",
        "eSIM activation requires customer to scan QR code within 24 hours",
        "Info");

    // Warning: no-contract customers cannot select 24-month device
    message(basePlan.comuniqaGo.Contract_Term == "No Contract" &&
             vasGroup.goTwo[ComuniqaGoTwo] > 0,
        "No-contract customers pay full device price for Go! Two",
        "Warning");

    // Error: block if GoogleEmail missing on YouTube Mundo
    message(vasGroup.goTwo[ComuniqaGoTwo] > 0 &&
             vasGroup.goTwo.GoogleEmail == null,
        "Google Account email is required to activate YouTube Mundo",
        "Error");
}
๐Ÿ“‹ table constraint โ€” Valid Combinations โ–ผ

Defines valid combinations of attribute values in rows. Each row inside {} is one valid combination. Can also import data from a Salesforce object using the SalesforceTable keyword.

type MoviMundoPlus : LineItem {
    int Simultaneous_Devices;
    string Video_Quality;
    string Plan_Tier;

    // Valid combinations of devices, quality, and tier
    constraint(
        table(Simultaneous_Devices, Video_Quality, Plan_Tier,
            {1, "HD",  "Basic"},
            {2, "FHD", "Plus"},
            {4, "4K",  "Premium"}
        )
    );

    // OR: import from Salesforce object
    // constraint(table(Display_Size,Memory,
    //   SalesforceTable("MoviMundoPlan__c","Devices__c,Quality__c")))
}
๐ŸŽฏ rule() โ€” recommend โ€” Product Recommendations โ–ผ

Adds product recommendations that appear at runtime via an invocable action in a flow. Unique to CML โ€” not available in BRE.

type ComuniqaGo : LineItem {
    // If agent selects 24-month contract, recommend Go! Two
    rule(Contract_Term == "24 Months",
        "recommend", "relation", "vasGroup");

    // If eSIM selected, recommend MoviMundo Plus+ (streaming)
    rule(SIM_Format == "eSIM",
        "recommend", "type", "MoviMundoPlus");
}
๐Ÿ’ฒ productSellingModel โ€” Set PSM in Constraint โ–ผ

Sets the Product Selling Model for a type based on a constraint. Updates PSM for new line items only โ€” cannot update PSM on existing quote lines.

type ComuniqaGo : LineItem {
    @(defaultValue = "No Contract")
    string Contract_Term = ["No Contract", "12 Months", "24 Months"];

    // Automatically set the correct PSM based on contract selection
    constraint(Contract_Term == "12 Months" ->
        productSellingModel == "PSM_12M_ID");

    constraint(Contract_Term == "24 Months" ->
        productSellingModel == "PSM_24M_ID");

    constraint(Contract_Term == "No Contract" ->
        productSellingModel == "PSM_EVERGREEN_ID");
}

Replace PSM_12M_ID etc. with the actual Salesforce record IDs of your Product Selling Model records โ€” found in the URL when viewing the PSM record in your org.

05 Comuniqa

Comuniqa CML Examples โ€” Real Implementation Scenarios

๐Ÿ‡ฆ๐Ÿ‡ท Scenario 1 โ€” MoviMundo Mutual Exclusivity Partner VAS Group

A customer can select only one of MoviMundo Plus+, MoviMundo Basic+, or Vida Digital Pass. PCM cardinality (Max=1 on Partner VAS group) handles the group-level enforcement. CML adds explicit error messaging and cross-group exclusion with the Subscriptions group.

type ComuniqaGoBundleRoot : LineItem {
    PartnerVASGroup     partnerVASGroup;
    SubscriptionsGroup  subscriptionsGroup;

    // Explicit cross-group mutual exclusivity via CML
    exclude(subscriptionsGroup.vidaDigitalPass[VidaDigitalPass] > 0,
        partnerVASGroup.moviMundoPlus[MoviMundoPlus],
        "Remove MoviMundo Plus+ โ€” Vida Digital Pass already covers partner content");

    exclude(subscriptionsGroup.vidaDigitalPass[VidaDigitalPass] > 0,
        partnerVASGroup.moviMundoBasic[MoviMundoBasic],
        "Remove MoviMundo Basic+ โ€” Vida Digital Pass already covers partner content");

    // Message when agent tries to add both
    message(partnerVASGroup.moviMundoPlus[MoviMundoPlus] > 0 &&
             subscriptionsGroup.vidaDigitalPass[VidaDigitalPass] > 0,
        "Conflicting partner content selections detected โ€” only one allowed",
        "Error");
}

@(minInstanceQty=0, maxInstanceQty=1)
type PartnerVASGroup {
    relation moviMundoPlus  : MoviMundoPlus;
    relation moviMundoBasic : MoviMundoBasic;
}

@(minInstanceQty=0, maxInstanceQty=1)
type SubscriptionsGroup {
    relation vidaDigitalPass : VidaDigitalPass;
}
๐Ÿ‡ฆ๐Ÿ‡ท Scenario 2 โ€” Watch Device Constraint + Auto-PSM Go! Two

When a customer adds Comuniqa Go! Two, the Watch Device Type is required. If Apple Watch is selected, an eSIM is recommended. The PSM is automatically set based on the contract term.

type ComuniqaGoTwo : LineItem {
    @(defaultValue = "Apple Watch", sequence=1)
    string Watch_Device_Type = ["Apple Watch", "Samsung Galaxy Watch"];

    @(configurable = false)
    string Watch_IMEI;  // fulfillment-populated, not agent-editable

    string Paired_Mobile_Number;

    // IMEI visible only after activation โ€” hide in ordering
    rule(true, "hide", "attribute", "Watch_IMEI");

    // Paired number required when any device type is selected
    constraint(Watch_Device_Type != null ->
        Paired_Mobile_Number != null,
        "Paired mobile number is required for watch service activation");
}

type ComuniqaGoBundleRoot : LineItem {
    ValueAddedServicesGroup vasGroup;

    // If Apple Watch selected, recommend eSIM on SIM card
    rule(vasGroup.goTwo.Watch_Device_Type == "Apple Watch",
        "recommend", "relation", "simGroup");

    // Message: Samsung watch users must use Physical SIM
    message(vasGroup.goTwo.Watch_Device_Type == "Samsung Galaxy Watch" &&
             simGroup.sim.SIM_Format == "eSIM",
        "Samsung Galaxy Watch requires Physical SIM โ€” please update SIM format",
        "Warning");
}
๐Ÿ‡ฆ๐Ÿ‡ท Scenario 3 โ€” Qualification Rule: Age + Credit via External Variables Comuniqa Go! Youth

Comuniqa Go! Youth is restricted to customers aged 18 or under. The credit score check gates term-based subscriptions. Both rules use external variables mapped from the ProductDiscoveryContext.

// External variables from ProductDiscoveryContext
@(contextPath = "SalesTransaction.customerAge")
extern int customerAge = 99;

@(contextPath = "SalesTransaction.creditScore")
extern int creditScore = 999;

@(contextPath = "SalesTransaction.outstandingAmount")
extern decimal(2) outstandingAmount = 0.00;

type ComuniqaGoYouth : LineItem {
    // Age gate: block if customer is over 18
    constraint(customerAge <= 18,
        "Comuniqa Go! Youth is only available for customers aged 18 or under");

    // Block if outstanding debt exists
    constraint(outstandingAmount == 0,
        "New subscriptions are not available while account has an outstanding balance");
}

type ComuniqaGo : LineItem {
    @(defaultValue = "No Contract")
    string Contract_Term = ["No Contract", "12 Months", "24 Months"];

    // Credit check: term contracts require credit score >= 600
    constraint(
        (Contract_Term == "12 Months" || Contract_Term == "24 Months") ->
        creditScore >= 600,
        "Credit score of 600 or above is required for contract subscriptions");

    // Outstanding balance blocks all new plan subscriptions
    constraint(outstandingAmount == 0,
        "Outstanding balance must be cleared before adding a new plan");
}
06 Capabilities

Capabilities & Limitations

What CML Can Do

CapabilityTypeNotes
Logical constraints (if/then/else)CoreFull boolean logic with implication, equivalence, conditional
Group type cardinality enforcementCoreminInstanceQty / maxInstanceQty on group types
Cross-group mutual exclusivityCoreexclude() across group types in the same bundle
Hide/Disable attributes & componentsCoreProgressive UI disclosure based on configuration state
Auto-add components (require)CoreWith optional attribute value pre-population
Auto-remove components (exclude)CoreOnly constraint that overrides user input
Messaging (Info / Warning / Error)CoreError blocks current task; Warning blocks next step
Table constraints (valid combinations)CoreCan import rows from Salesforce objects via SalesforceTable
Product Selling Model in constraintAdvancedSets PSM on new line items only
Product recommendationsAdvancedVia recommend rule + invocable action in flow
External variables from contextAdvancedMaps SalesTransaction fields into CML for qualification rules
Type hierarchies (inheritance)AdvancedSubtypes inherit all variables and constraints
Variable functions (sum, min, max, count)AdvancedAggregate across descendants of a type

Documented Limitations

LimitationImpact
split=true not supported for child products in a dynamic bundleUse split=false or omit split annotation for dynamic bundle children
Cannot write a constraint directly on a group's attribute to apply to all componentsconstraint(accessoryGroup.Wireless == true) is invalid โ€” must reference specific component types via dot notation
productSellingModel constraint cannot update PSM on existing quote linesPSM is set only when the line item is first created โ€” amendments do not re-trigger PSM constraints
Constraint engine never overrides user input (except exclude rule)If a user sets a value that violates a constraint, an error is shown โ€” not silently corrected
The entire bundle must be in the CML model to constrain a child productCannot write a standalone CML model for just one child product โ€” the bundle root must be included
Only lowest-level groups imported into CML for nested group structuresCardinality of only leaf-level groups is evaluated at runtime by the constraint engine
07 Best Practices

CML Best Practices

From the official CML User Guide โ€” these practices prevent performance degradation and unexpected behavior.

1 โ€” Specify the smallest required relationship cardinality โ–ผ

The constraint engine tests every quantity value up to the maximum. relation engine : Engine; (no cardinality) makes the engine test 1 through 9,999. relation engine : Engine[0..1]; limits this to 0 or 1 โ€” dramatically faster.

Comuniqa application: All Comuniqa Go! component relationships should specify explicit cardinality matching PCM settings โ€” e.g. relation sim : SIMCard[1..1];, relation goTwo : ComuniqaGoTwo[0..1];

2 โ€” Use integers instead of decimals where possible โ–ผ

A decimal(2) variable with domain [0..3] tests 0.00, 0.01, 0.02... up to 2.99 โ€” 300 permutations. An int with the same domain tests only 0, 1, 2, 3 โ€” 4 permutations. Use int for Member Count, Simultaneous Devices, and other whole-number attributes on Comuniqa products.

3 โ€” Keep variable domains as small as possible โ–ผ

Each additional value in a domain multiplies the engine's search space. Comuniqa's Contract Term picklist (3 values), SIM Format (2 values), and Watch Device Type (2 values) are already well-scoped. Avoid open-ended text domains on configurable attributes โ€” use picklists where possible.

4 โ€” Put calculations inside constraints, not inline expressions โ–ผ

Inline expressions (area = length * width) force the engine to evaluate the expression at every choice point. A constraint (constraint(area == length * width)) is evaluated only when needed. For Comuniqa, bundle total price calculations and member count validations should use constraints, not inline variable assignments.

5 โ€” Combine related relationships to reduce performance impact โ–ผ

Multiple separate relationships on a type multiply constraint evaluations. Where possible, combine components of the same logical category into one relationship. Comuniqa's group type structure (PartnerVASGroup, ValueAddedServicesGroup) already follows this pattern correctly.

6 โ€” Use sequence annotations for ordered attribute evaluation โ–ผ

When constraints depend on attributes being set in a specific order, use @(sequence=N) to tell the engine the evaluation order. For Comuniqa Go!, Contract Term (sequence=1) should be evaluated before SIM Format (sequence=2) because downstream constraints depend on the contract term selection.

7 โ€” Separate auto-add and attribute-set into distinct constraints โ–ผ

When you need to both auto-add a product AND set its attributes, use two separate constraints instead of one combined constraint. This improves clarity and reduces constraint engine backtracking.

// โœ… CORRECT โ€” separate constraints
constraint(vasGroup.goTwo[ComuniqaGoTwo] > 0,
    simGroup.sim[SIMCard] > 0);
constraint(simGroup.sim[SIMCard] > 0,
    simGroup.sim.SIM_Format == "eSIM");

// โŒ AVOID โ€” combined in one constraint
constraint(vasGroup.goTwo[ComuniqaGoTwo] > 0,
    simGroup.sim[SIMCard] > 0 && simGroup.sim.SIM_Format == "eSIM");
08 Architecture

Architectural Decision Guidance

When to Use CML vs Other Mechanisms

ScenarioUseWhy
Mutual exclusivity between bundle components (e.g. MoviMundo vs Vida Digital)CML exclude()Cross-group exclusion requires CML โ€” PCM cardinality only handles within-group
Hide/show attributes based on other selectionsCML rule() hideProgressive disclosure must happen at configuration runtime โ€” only CML can do this
Customer eligibility checks (age, credit, debt)CML + ContextExternal variables in CML map cleanly from ProductDiscoveryContext โ€” cleaner than BRE Expression Sets
Auto-set Product Selling Model on contract selectionCML productSellingModelOnly CML constraint can set PSM based on attribute value โ€” not possible in BRE or PCM alone
Valid attribute combinations (e.g. device tier + streaming quality)CML table constraintTable constraint is the most maintainable pattern โ€” rows can be Salesforce-object-driven
Group cardinality (min/max components in a group)PCM Group CardinalityPCM is the source of truth for bundle structure โ€” CML reads from it, not replaces it
Pricing discounts based on attribute valuesPricing ProcedureCML does not calculate prices โ€” Pricing Engine reads attributes and applies discounts
Fulfillment rules (e.g. provision ICCID)DROCML operates at configuration time โ€” DRO handles post-order fulfillment logic
โœ… Architect Principle โ€” CML's Role CML answers one question: "Given what the customer has selected so far, what is valid, required, excluded, or visible next?" It is a configuration-time engine. It does not price, fulfill, bill, or qualify products for purchase โ€” those are Pricing Engine, DRO, Billing, and Qualification Rule concerns respectively.
โš  Key Governance Reminder CML models are versioned through the CML Editor in Salesforce. Always test constraint models in a sandbox before activating in production. Use the Apex debug log (set to FINE level) to diagnose performance issues โ€” optimal execution is under 100ms with fewer than 1,000 backtracks.
โœฆ Knowledge Check

Knowledge Check โ€” 10 Questions

All questions grounded in the Comuniqa product model and the official CML User Guide (Edition 2.3, Winter '26).

Question 1 of 10 โ€” Core Concepts

In CML, what is the relationship between a Type and a Variable?

A) Types define the valid values for variables.
B) Types are the foundational building blocks that represent entities (products, bundles, components). Variables are the properties defined within a type โ€” they represent product attributes, fields, and configuration options.
C) Variables and types are interchangeable โ€” both represent product characteristics.
D) Types are only used for bundles; variables are used for standalone products.
Question 2 of 10 โ€” Group Types
๐Ÿ“‹ Comuniqa: The Partner VAS group in Comuniqa Go! allows a maximum of 1 component selection (MoviMundo Plus+ OR Basic+). How is this represented in CML?

What CML construct enforces this and what annotation values are used?

A) A constraint() with a table listing valid combinations of the two products.
B) A Group Type with @(minInstanceQty=0, maxInstanceQty=1) โ€” this mirrors the PCM group cardinality directly in CML. The Group Type is identified by these two annotations and controls how many component instances a customer can select from the group.
C) An exclude() rule that removes one product when the other is selected.
D) A variable domain restriction on the bundle root type.
Question 3 of 10 โ€” Constraint Behavior

An agent selects Samsung Galaxy Watch for Go! Two, then selects eSIM for the SIM Card. A CML constraint states these two cannot be combined. What does the constraint engine do?

A) Automatically changes SIM Format back to Physical SIM to resolve the conflict.
B) Displays an error to the agent โ€” the constraint engine never overrides user-selected values. The agent must manually resolve the conflict by either changing the Watch Device Type or the SIM Format. The exception to this rule is the exclude() rule, which does override user input.
C) Removes Samsung Galaxy Watch from the configuration automatically.
D) Allows the configuration to proceed and flags it during order submission instead.
Question 4 of 10 โ€” External Variables
๐Ÿ“‹ Comuniqa: The CML model for Comuniqa Go! needs to check the customer's credit score (stored in the ProductDiscoveryContext) and block term-based contracts if it is below 600.

How do you bring the credit score value into the CML model?

A) Create a variable inside the ComuniqaGo type and map it to the Account object via SOQL.
B) Declare an external variable with the @(contextPath) annotation pointing to the SalesTransaction field: @(contextPath = "SalesTransaction.creditScore") extern int creditScore = 999; This brings the value from the ProductDiscoveryContext into CML at runtime.
C) Use a global constant: define MIN_CREDIT_SCORE 600.
D) External data cannot be used in CML โ€” decision tables must handle all data lookups.
Question 5 of 10 โ€” Rule Types

What is the difference between a message() rule with "Error" severity and one with "Warning" severity at runtime?

A) Error displays in red; Warning in yellow โ€” both block the agent from saving the quote.
B) An Error message blocks the agent from continuing the current task until the condition is resolved. A Warning message allows the agent to continue the current task but blocks them from moving to the next step (e.g. submitting the order) until addressed. Info messages are completely non-blocking.
C) Both have the same behavior โ€” the distinction is purely visual.
D) Warning messages are only visible to admins; Error messages are visible to agents.
Question 6 of 10 โ€” Bundle Scope Rule
๐Ÿ“‹ A developer wants to write a CML constraint only for MoviMundo Plus+ (a child component of Comuniqa Go!) and creates a standalone CML model containing only MoviMundoPlus as a type.

Will this constraint run correctly at runtime?

A) Yes โ€” CML constraints on individual products are evaluated independently of the bundle.
B) No โ€” to define a constraint for a child product in a bundle, the entire bundle must be included in the constraint model. The CML model must include ComuniqaGo (the bundle root) and the full group structure, even if constraints are only written on MoviMundoPlus. Otherwise the constraint will not execute.
C) Yes โ€” but only for attribute constraints; relationship constraints require the full bundle.
D) It depends on whether MoviMundo Plus+ is flagged as configurable in PCM.
Question 7 of 10 โ€” exclude() vs constraint()
๐Ÿ“‹ Comuniqa wants to automatically remove MoviMundo Plus+ from a quote when the agent adds the Vida Digital Pass, because the two are mutually exclusive.

Which CML rule type should be used and why?

A) constraint() โ€” it enforces the logical condition and blocks the invalid combination.
B) exclude() โ€” because the requirement is to automatically REMOVE a component when a condition is met, not just display an error. The exclude() rule is the only CML constraint that overrides user input, making it the correct choice for automatic component removal. Note: the target type must be a leaf type.
C) message() with Error severity โ€” it blocks the configuration and forces the agent to resolve it.
D) rule() with "disable" โ€” it prevents the agent from re-adding MoviMundo Plus+ after it is removed.
Question 8 of 10 โ€” Performance

A CML model for Comuniqa Go! has a Member Count variable declared as decimal(2) memberCount = [0..5]. An architect flags this as a performance concern. Why, and what is the fix?

A) Decimals cannot be used for count variables โ€” the type must be changed to string.
B) A decimal(2) variable with domain [0..5] forces the engine to test 0.00, 0.01, 0.02... up to 4.99 โ€” 500 permutations. An integer with the same domain [0..5] tests only 0, 1, 2, 3, 4, 5 โ€” 6 permutations. The fix is: int memberCount = [0..5]. Always use int for whole-number counts.
C) The domain [0..5] is too small โ€” it should be [2..5] to match PCM cardinality, which fixes performance.
D) The variable should be configurable = false to prevent the engine from evaluating it.
Question 9 of 10 โ€” productSellingModel
๐Ÿ“‹ Comuniqa wants the Product Selling Model to be automatically set when an agent selects Contract Term = "24 Months" on Comuniqa Go!.

What CML syntax achieves this and what is the key limitation?

A) Use a require() rule to add the correct selling model as a component when Contract Term = 24 Months.
B) Use: constraint(Contract_Term == "24 Months" -> productSellingModel == "PSM_24M_ID"); where PSM_24M_ID is the Salesforce record ID of the 24-Month Term-Defined selling model. The key limitation: this only works for new line items. It cannot update the PSM on an existing quote line during amendments.
C) Use a message() rule to prompt the agent to manually select the correct selling model.
D) PSM assignment cannot be done via CML โ€” it must be set in PCM or via a Pricing Procedure.
Question 10 of 10 โ€” CML vs BRE Architectural Decision
๐Ÿ“‹ A junior architect on the Comuniqa project suggests using BRE Decision Tables to enforce the mutual exclusivity of MoviMundo Plus+ and Vida Digital Pass, since "BRE is simpler and the team already knows it".

How should the senior architect respond?

A) Agree โ€” BRE Decision Tables are the correct mechanism for all configuration rules in Revenue Cloud.
B) Disagree โ€” cross-group mutual exclusivity (Partner VAS group and Subscriptions group are different groups) requires CML's exclude() rule with dot-notation group references. BRE cannot express cross-group constraints or automatically remove components. Additionally, CML is the recommended path for Winter '26+, provides a single auditable file, has full debug tooling, and supports PSM constraints and recommendations that BRE cannot handle.
C) Agree for this scenario, but mandate CML for pricing rules only.
D) Escalate to Salesforce support โ€” neither BRE nor CML can handle cross-group constraints.