Chapter 02

Business rules

A rule is a decision the business makes every time a record is written: what this lead is worth, who picks it up, whether this discount is allowed, how urgent this case is. In EML a rule is a flowchart — a picture a sales manager can read and an engine can evaluate.

Anatomy of a rule section

Two directives above a flowchart turn it into a rule: %%meta kind: rules says how to read the diagram, and %%rule binds it to an entity and a lifecycle event.

emlthe binding line
%%meta kind: rules
%%rule leadScoring on Lead event: beforeCreate priority: 10
flowchart TD
    ...
PartWhat it decides
leadScoringThe rule's name. It appears in the Business Rules screen and is how a saga step can reference the rule instead of copying its table.
on LeadThe entity. Must be one the ERD declares.
event: beforeCreateWhen it runs. Any of the thirteen lifecycle hooks; before* can still block the write.
priority: 10Order when several rules bind to the same entity and event. Lower runs first.

Shapes carry the meaning

The node shape is not decoration. It is how the flowchart compiles into a GoRules decision graph.

ShapeWrittenBecomesUse it for
StadiumA([Start: Lead Captured])Input or output nodeThe entry point, and each terminal. A stadium with only incoming edges is an output.
DiamondB{score >= 70?}Switch nodeA branch. Label every outgoing edge with its condition.
RectangleC[Set rating hot]Expression nodeAn assignment or an action taken on that branch.
CircleD((Compute lead score))Function nodeA computation several branches converge on.

A complete rule

Lead scoring: firmographics, then source, then revenue, then a grade. Each branch adds points; the circle is where they are totalled; the last diamond turns the total into a rating that routing and conversion both read.

emllanguage/examples/crm.eml.mmd · Section 2
%%meta name: Lead Scoring
%%meta kind: rules
%%rule leadScoring on Lead event: beforeCreate priority: 10
flowchart TD
    A([Start: Lead Captured]) --> B{employee_count >= 1000?}
    B -->|Yes| C[Add 35 firmographic points]
    B -->|No| D{employee_count >= 100?}
    D -->|Yes| E[Add 20 firmographic points]
    D -->|No| F[Add 5 firmographic points]
    C --> G{lead_source == referral?}
    E --> G
    F --> G
    G -->|Yes| H[Add 30 source points]
    G -->|No| I{lead_source == event?}
    I -->|Yes| J[Add 20 source points]
    I -->|No| K[Add 8 source points]
    H --> L{annual_revenue > 10000000?}
    J --> L
    K --> L
    L -->|Yes| M[Add 25 revenue points]
    L -->|No| N[Add 5 revenue points]
    M --> O((Compute lead score))
    N --> O
    O --> P{score >= 70?}
    P -->|Yes| Q[Set rating hot]
    P -->|No| R{score >= 40?}
    R -->|Yes| S[Set rating warm]
    R -->|No| T[Set rating cold]
    Q --> Z([End: Lead Scored])
    S --> Z
    T --> Z
Four things the checker insists on

Exactly one start stadium. At least one end stadium. Every diamond has two or more labelled outgoing edges. Every node is reachable from the start. A rule that fails any of these is a rule that will do something other than what the picture shows.

Actions: when deciding is not enough

A decision flow works out an answer. An action is what the engine does with it. Without actions a modelled rule could only ever think; these are how it reaches the record and the rest of the application.

ActionEffectRequires
validation-errorRejects the write; the message goes back to the caller.message
transformOverwrites a field on the record being written.field, value
trigger-workflowRuns a workflow by name — this is how a condition starts a process.workflow
emlthe discount policy, and the two actions it emits
%%rule quoteDiscountApproval on Quote event: beforeUpdate priority: 10
flowchart TD
    A([Start: Quote Priced]) --> B{discount_percent > 40?}
    B -->|Yes| C[Reject: Above the maximum discount]
    B -->|No| D{discount_percent > 25?}
    D -->|Yes| E[Require VP approval]
    D -->|No| F{discount_percent > 15?}
    F -->|Yes| G[Require sales manager approval]
    F -->|No| H{discount_percent > 0?}
    H -->|Yes| I[Auto approve within rep authority]
    H -->|No| J[No discount applied]
    ...

    %%action escalateDiscount trigger-workflow when: discount_percent > 15 workflow: QuoteApprovalEscalation message: Discount above rep authority — holding the quote for approval
    %%action refuseDiscount validation-error when: discount_percent > 40 message: Discounts above 40 percent cannot be approved by anyone — reprice the quote

The when: expression is evaluated against the record being written. when: true fires on every write and is a legitimate choice — say it deliberately, because a missing when means the same thing by accident and the checker warns about it (EML282).

This is the seam between the two halves of the model

trigger-workflow is how chapter 02 reaches chapter 03. The rule owns the condition; the workflow owns the steps. Keeping them apart is what lets you change "when do we escalate?" without touching "what does escalating do?".

The eight rules in the CRM

RuleEntity · eventDecides
leadScoringLead · beforeCreateScore from firmographics and source; grade hot / warm / cold.
leadRoutingLead · afterCreateWhich desk picks it up. Partner leads go back to the partner desk whatever their size.
leadQualificationLead · beforeUpdateWhether a lead may convert. Emits trigger-workflow → LeadConversion.
opportunityForecastOpportunity · beforeUpdateProbability and forecast category, derived from stage rather than typed in.
opportunityCloseGateOpportunity · beforeUpdateWhat a deal must have before anyone may call it won.
quoteDiscountApprovalQuote · beforeUpdateDiscount authority bands; escalates or refuses.
caseTriageSupportCase · beforeCreatePriority from account tier and case type. Escalates a critical case.
renewalRiskContract · beforeUpdateGrades a renewal. The renewal saga evaluates this rule as a step.

Where they land in the application

Business Rules, seeded from the model. The eight named rules sit alongside the seventeen validation rules the generator adds per entity — 25 in total, all active, each showing its entity and the operation it fires on.
The Business Rules screen in the generated CRM listing 25 active rules across 17 entities, including leadScoring, leadRouting, leadQualification, opportunityForecast, opportunityCloseGate, quoteDiscountApproval, caseTriage and renewalRisk.

Open one and you get the decision table the flowchart compiled into, with a test panel and a coverage check. This is a real editor: an operations person can change a threshold here without a deploy, and the model stays the source of truth for what gets seeded on the next generation.

leadQualification in the editor. The input is status; the outputs carry the action (trigger-workflow), its message, and the workflow name (LeadConversion). The coverage panel warns that there is no catch-all row — worth knowing before a record falls through it.
The rule editor showing leadQualification as a decision table, with an input on status and outputs for action, message, ruleId and workflowName, plus test and coverage panels.

Rules an administrator can write without you

The 25 seeded rule tables are not the ceiling. The same screen carries an automation builder, and it ships with its own documentation — written for the person configuring the system, not for the person who generated it.

One sentence: when, only if, then. Every rule table in the application is listed down the left — the eight from the model and the seventeen per-entity validations. The panel explains the six things a step can do, including calling an external web service, and how to reference an earlier step's result with {{tier.discount_pct}}. None of this text was written for the CRM; it is generated alongside it.
The Automations help screen. A left rail lists 25 rule tables including bus_account_validation, bus_activity_validation, renewalRisk, leadRouting, leadScoring, leadQualification, opportunityCloseGate and opportunityForecast. The main panel, titled 'How an automation runs', explains the trigger, the checks and the steps, and lists the six step types: look up a rule table, create a record, update a field, delete a record, work out a value, and call a web service.
Why this matters more than the rule count

A generator that emits logic you cannot change afterwards has only moved the bottleneck. Here the decision tables are data, the editor is part of the application, and the person changing a threshold needs neither the repository nor a deploy window.

When rules run

Rules are evaluated inside the transaction that saves the record. If one fails, the write is rolled back and nothing it changed survives. That is the difference between a rule and a reminder: you cannot get a record into the database that violates one.

The generated application shows this as a document status. Every save starts as Draft; the rules and workflows attached to the entity run together; if all of them succeed the record becomes Final. If any fails, the record stays Draft with the reason written onto it, so you can fix the cause and retry.