Rule Application Process

How a business rule actually fires, what users see, and where decisions still need to be made

The BDD specs describe outcomes in abstract terms — "the sync is blocked", "an alert fires", "the integration proceeds normally". This document grounds those phrases in actual user-visible behaviour, and flags decisions that need confirming so the manifestation is consistent across rules.

1. The pipeline — from user action to outcome

Every rule follows the same lifecycle. Here's the sequence:

sequenceDiagram autonumber actor User participant SF as Salesforce Trigger participant Engine as Rule Engine participant CMT as Active Rules CMT participant Action as Action Handler participant Xero as Xero API User->>SF: Edit and save record SF->>SF: Trigger fires SF->>Engine: evaluate record Engine->>CMT: Query active rules CMT-->>Engine: Candidate rules Engine->>Engine: Evaluate conditions Engine-->>SF: Evaluation results alt Any rule has block_sync SF-->>User: Save error with message else Rules are flag, alert, map, or log SF->>Action: Apply each action Action->>Action: Set field, create task, log entry SF->>Xero: Sync proceeds end

Key insight: rules are pure evaluators — they don't directly affect the system. The caller (trigger or service class) decides what to do based on returned results. This keeps the engine testable and lets us evolve action handling separately.

2. What each action type means in practice

This is the part the BDD specs gloss over. Each abstract phrase maps to a concrete user-visible outcome — but several have options we need to decide on.

Action BDD phrase What actually happens
block_sync
(BR-001, BR-003, BR-022, BR-029, BR-040, BR-041, BR-051, BR-053, BR-056)
"the sync is blocked" Three patterns to choose from:
A) Pre-save validation rule — record never saves; user sees standard SF validation error toast inline.
B) Save proceeds, sync attempt blocked separately; record gets Xero_Sync_Error__c populated; user sees no immediate error but sees red flag on next view.
C) Apex throws DmlException from trigger; user sees blocking error toast at save time but with our custom message.
Recommendation: Pattern C. User-facing immediate feedback with our wording, doesn't pollute SF's validation rule UI.
flag_record
(BR-003, BR-021)
"the record is flagged" A picklist or text field on the record is set to a specific value. Save proceeds normally. User sees the flag on next view.
Example: BR-003 sets Opportunity.Invoice_Status__c = "No Invoice Required". User sees this in the standard field on the record page.
alert
(BR-002, BR-012, BR-022, BR-032)
"an alert is raised" Three patterns to choose from:
A) Salesforce Task created and assigned to a designated user (e.g. Ilkay, Sara, Jo per BR-032). Appears in their My Tasks list and email digest.
B) Email via Messaging.SingleEmailMessage — direct email to recipients.
C) Chatter post on the affected record — visible to everyone following.
Recommendation: Pattern A (Task). Already used by XeroTokenRefreshJob for token expiry alerts. Persistent, assignable, dismissable, doesn't spam inboxes.
map_field
(BR-013, BR-014, BR-018, BR-023, BR-026, BR-030, BR-044)
"field mapping is applied" Invisible to the user during normal sales work — happens at sync time. The Xero invoice is built with mapped values. User sees the result only on the synced Xero invoice, or in the audit log.
Example: BR-018 sets Tax_Rate__c = 0 for non-UK invoices. The Salesforce-side invoice record gets the value, and so does the synced Xero invoice.
execute_apex
(BR-005, BR-006, BR-015, BR-019, BR-027, BR-028, BR-032, BR-033, BR-039, BR-049, BR-050, BR-052, BR-055)
"the configured logic runs" Engine delegates to a named Apex class (e.g. OpportunityTypeDeriver). The class does whatever the rule needs — multi-step logic, complex calculations, multi-record updates. Outcome depends entirely on the handler. Each handler should have its own clear UX expectation.
Note: Workflow rules are where the highest variance lives. Each one needs its own UX spec at design time.
log
(BR-036, BR-038, BR-042, BR-043, BR-047)
"an audit log entry is recorded" A Xero_Webhook_Log__c (or new audit object) record is created. Invisible to most users; visible to Finance/Ops in the Webhook Logs tab and the monthly governance review.
require_approval
(BR-048)
"an approval request is raised" Standard Salesforce Approval Process triggered. Approver receives email + in-app notification. User sees record locked pending approval. After approval (or auto-approval after timeout), record progresses normally.
Example: BR-048 — discount >50% requires CRO sign-off, auto-approves after 1 day if no response.

3. What different users see

Same rule, different audiences experience it differently.

User What they experience when a rule fires
Sales / RM
(creates the deal)
block_sync: red error toast at save with the rule's message
flag_record: a field value updates after save
require_approval: record becomes locked, sees "Pending CRO approval"
• Most other rule types: no visible signal during normal flow
Sales Ops / Finance
(reviews invoices)
• Sees the consequences in dashboards and reports — flagged opportunities, approval queue, audit log
• Receives Tasks for alerts and exceptions
• Reviews Webhook Logs for sync failures
• Monthly governance review (BR-037) surfaces aggregate issues
System admin
(maintains the integration)
• Monitors the Xero_Webhook_Log__c object for failures
• Sees CMT records for active rules in Setup
• Can deactivate individual rules without code deploy (set Is_Active__c = false)
• Receives critical alerts via Task or email
Finance leadership
(governance)
• Doesn't see rules firing day-to-day
• Sees outcomes via reports — invoice consistency, ARR/TCV correctness, intercompany separation
• Reviews business-rule register quarterly via the rules viewer

4. Design decisions still needed

The BDD spec is intentionally agnostic about these — we should land them before Phase 2.

UX-1. When sync is blocked, should the save fail (Pattern C — DmlException) or should the save succeed but sync fail silently with an error flag (Pattern B)?
Recommendation: Pattern C. Immediate visible feedback. Matches how validation rules already work.
UX-2. When an alert fires, where does it go — Task, Email, Chatter, or all three?
Recommendation: Task as primary. Email for high-priority only (BR-032 sync failures). No Chatter — too noisy.
UX-3. How does the user know which rule fired? Just the message text, or do we link to a rule-detail page?
Recommendation: Include the rule ID in the message (e.g. "BR-003: Zero-value contract — no invoice required"). Eventually link to the rules viewer for full context.
UX-4. For map_field rules, should the source record reflect the mapped value, or only the synced Xero record? (e.g. BR-018 setting Tax_Rate to 0 — does that go on Salesforce too?)
Recommendation: Both — Salesforce-side too, so users see what was sent. Audit log captures any subsequent change.
UX-5. Should rules be silently rolled out (Is_Active__c = true), or require an admin to enable each? Phased activation strategy?
Recommendation: Phased. Activate validation rules first (lowest risk), then mapping, then workflow. Already in Phase 6.3 of the plan.
UX-6. When a rule fires repeatedly on the same record (e.g. user keeps hitting save without fixing the issue), do we de-duplicate alerts/tasks?
Recommendation: Yes — Task creation should check for an existing open Task on the same record + rule. Avoid spam.

5. When does the engine actually run?

Rules are evaluated at events — defined in each rule's when.event. Here's the full picture:

flowchart TD A[User saves record] --> B{Trigger event} B -->|after_update| C[Engine evaluates
after_update rules] B -->|after_insert| D[Engine evaluates
after_insert rules] B -->|before_save| E[Engine evaluates
before_save rules
via validation rule trigger] F[Integration sync runs] --> G{Sync event} G -->|before_sync| H[Engine evaluates
before_sync rules] G -->|on_sync| I[Engine evaluates
on_sync rules] G -->|after_sync| J[Engine evaluates
after_sync rules] K[Scheduled job] --> L{Schedule event} L -->|scheduled_monthly| M[Engine evaluates
scheduled_monthly rules
e.g. BR-037 data quality] L -->|scheduled_daily| N[Engine evaluates
scheduled_daily rules
e.g. BR-039 reconciliation] O[Webhook arrives] --> P{Webhook event} P -->|on_webhook| Q[Engine evaluates
webhook rules] style C fill:#e8f5e9 style D fill:#e8f5e9 style E fill:#e8f5e9 style H fill:#e1f5fe style I fill:#e1f5fe style J fill:#e1f5fe style M fill:#fff3e0 style N fill:#fff3e0 style Q fill:#f3e5f5

A single rule can fire at multiple events if needed (rare). Most rules fire once at a defined point in the lifecycle. Adding new event hooks is cheap (a few lines in the relevant trigger or service).

6. TL;DR for the meeting

  1. Rules live as configuration (Business_Rule__mdt), not code. Active rules are queried at runtime.
  2. The engine is a pure evaluator — returns results, doesn't act directly.
  3. The caller (trigger or service) interprets results and applies the action handlers.
  4. Action handlers map to clear user-visible outcomes — but several patterns need confirmation (UX-1 to UX-6 above).
  5. Different users see different things — Sales sees inline errors and field flags; Ops sees Tasks and audit logs; Admin monitors via Webhook Logs.
  6. Rules can be deactivated individually without a code deploy — flexibility for phased rollout.