Skip to Content
CapabilitiesPolicy Evaluation

Policy Evaluation: Multi-Engine Business Rules

Status: MVP (v1.0.0) | Maturity: Production-Ready | Tests: 12 (OPA: 7, DMN: 3, Integration: 2)
Performance: OPA <5ms, DMN 10-50ms | Engines: OPA (primary), DMN (secondary)

Cascade Platform evaluates business rules using multiple policy engines, enabling complex decisions without modifying application code.


What is Policy Evaluation?

Policy evaluation is the ability to encode business rules separately from application code and execute them in workflows. Instead of hardcoding approval thresholds, compliance checks, or business logic, you define policies once and reference them everywhere.

Key benefits:

  • ✅ Rules defined in business-friendly languages
  • ✅ No code changes to update rules
  • ✅ Audit trail of policy evaluation
  • ✅ A/B testing different policies
  • ✅ Multi-engine support (OPA, DMN, WASM future)

Architecture: Decision Strategy

4-Tier Decision Strategy

Cascade implements a layered approach to decisions:

┌─────────────────────────────────────────┐ │ Tier 1: Fast-Path (Milliseconds) │ │ - Choice states (comparison) │ │ - Simple conditions (1-5 milliseconds) │ └─────────────────────────────────────────┘ ├─→ Can decide? → Answer + log └─→ Need more logic? ↓ ┌─────────────────────────────────────────┐ │ Tier 2: OPA Policies (< 5 milliseconds) │ │ - Rego rules (most cases) │ │ - Medium complexity │ │ - JSON input/output │ └─────────────────────────────────────────┘ ├─→ Can decide? → Answer + log └─→ Need structured decisions? ↓ ┌─────────────────────────────────────────┐ │ Tier 3: DMN (10-50 milliseconds) │ │ - Decision tables (complex logic) │ │ - Multiple outputs per decision │ │ - Auditable decision trace │ └─────────────────────────────────────────┘ ├─→ Can decide? → Answer + trace └─→ Can't decide? ↓ ┌─────────────────────────────────────────┐ │ Tier 4: Human Review │ │ - Escalation to human │ │ - Manual judgment (HumanTask) │ │ - Business context │ └─────────────────────────────────────────┘

Engine 1: OPA (Open Policy Agent)

What is OPA?

OPA uses Rego language to express policies as rules. Think of it as “business logic as code” but in a declarative language.

When to use: Most common policies, scoring, complex logic

Performance: <5ms typical, <20ms worst-case

Basic Rego Policy

Simple approval rule:

package loan_policies # Allow approval if score >= 700 and amount <= 100k allow_approval { input.credit_score >= 700 input.loan_amount <= 100000 } # Deny if any red flags deny_approval { input.previous_bankruptcies > 0 } # Require manager review if score between 650-700 require_manager_review { input.credit_score >= 650 input.credit_score < 700 }

CDL Integration

- name: EvaluateLoanPolicy type: EvaluatePolicy resource: urn:cascade:policy:loan_approval parameters: credit_score: "{{ $.credit_score }}" loan_amount: "{{ $.loan_amount }}" previous_bankruptcies: "{{ $.bankruptcy_count }}" result: $.policy_decision next: ApplyDecision

Policy evaluation returns:

{ "allow_approval": true, "deny_approval": false, "require_manager_review": false, "reason": "Credit score acceptable, amount within limits" }

Advanced Rego: Scoring Logic

Calculate approval score:

package loan_policies import data.scoring_rules # Score calculation approval_score = score { score := scoring_rules.credit_score_weight * input.credit_score / 850 + scoring_rules.income_weight * min(input.annual_income, 200000) / 200000 + scoring_rules.employment_weight * (input.years_employed / 20) } # Approval decision based on score allow_approval { approval_score >= 0.75 } # Strong approval strong_approval { approval_score >= 0.90 } # Marginal - needs review marginal_approval { approval_score >= 0.60 approval_score < 0.75 } # Recommendation recommendation = rec { strong_approval -> rec := "auto_approve" marginal_approval -> rec := "manager_review" _ -> rec := "deny" }

Usage in workflow:

- name: EvaluateScore type: EvaluatePolicy resource: urn:cascade:policy:loan_scoring parameters: credit_score: "{{ $.credit_score }}" annual_income: "{{ $.income }}" years_employed: "{{ $.employment_tenure }}" result: $.score_result next: RouteBasedOnScore - name: RouteBasedOnScore type: Choice choices: - condition: "{{ $.score_result.recommendation == 'auto_approve' }}" next: ProcessApproval - condition: "{{ $.score_result.recommendation == 'manager_review' }}" next: ManagerReview default: DenyAndNotify

Advanced Rego: Compliance Checks

Multi-rule compliance policy:

package compliance_policies # Sanctions check pass_sanctions_check { not data.sanctions_list[input.applicant_name] } # Age verification passes_age_check { input.age >= 18 } # Income verification passes_income_check { input.annual_income >= 25000 } # Combined compliance all_checks_pass { pass_sanctions_check passes_age_check passes_income_check } # Detailed compliance result compliance_result = result { result := { "sanctions": pass_sanctions_check, "age": passes_age_check, "income": passes_income_check, "overall": all_checks_pass } }

Engine 2: DMN (Decision Model & Notation)

What is DMN?

DMN is a visual language for representing business decisions as decision tables. Easy for business analysts to understand and modify.

When to use: Complex multi-condition decisions, audit requirements, visual representation needed

Performance: 10-50ms typical (slower than OPA but more readable)

Simple Decision Table

Loan routing table:

Credit ScoreLoan AmountApprovalRouteNotes
>= 750<= 100KAutoDisburseExcellent credit
>= 750> 100KEscalateManagerHigh amount
700-749<= 50KAutoDisburseGood credit, small
700-749> 50KEscalateManagerNeeds review
650-699AnyReviewManagerBorderline
< 650AnyDenyRejectionPoor credit

CDL Integration

- name: RouteApplication type: EvaluatePolicy resource: urn:cascade:policy:loan_routing_dmn parameters: credit_score: "{{ $.credit_score }}" loan_amount: "{{ $.loan_amount }}" result: $.routing_decision next: ApplyRouting

DMN Result:

{ "approval": "Auto", "route": "Disburse", "notes": "Excellent credit", "decision_id": "dmn-123456" }

DMN XML Format

Behind the scenes, DMN is XML-based:

<?xml version="1.0" encoding="UTF-8"?> <definitions xmlns="http://www.omg.org/spec/DMN/20191111/MODEL/"> <decision name="LoanApproval" id="d1"> <decisionTable id="dt1"> <input id="i1"> <inputExpression typeRef="number"> <text>credit_score</text> </inputExpression> </input> <input id="i2"> <inputExpression typeRef="number"> <text>loan_amount</text> </inputExpression> </input> <output id="o1" name="approval" typeRef="string"> <text></text> </output> <rule id="r1"> <inputEntry id="ie1"> <text>creditScoreCondition</text> </inputEntry> <inputEntry id="ie2"> <text>loanAmountCondition</text> </inputEntry> <outputEntry id="oe1"> <text>"auto_approve"</text> </outputEntry> </rule> <!-- More rules --> </decisionTable> </decision> </definitions>

Real-World Example: Insurance Claims Policy

Multi-Engine Policy Chain

Complete claims evaluation workflow:

workflows: - name: EvaluateClaim start: FastPathCheck states: # Tier 1: Quick decision - name: FastPathCheck type: Choice choices: - condition: "{{ $.claim_amount <= 1000 && $.claim_age < 7 }}" next: FastApprove description: "Small, recent claim" - condition: "{{ $.claim_amount > 50000 }}" next: ComplexEvaluation description: "Large claim needs evaluation" default: StandardEvaluation # Tier 1 result: Fast path - name: FastApprove type: Task resource: urn:cascade:activity:auto_approve_claim end: true # Tier 2: OPA evaluation - name: StandardEvaluation type: EvaluatePolicy resource: urn:cascade:policy:claim_evaluation_opa parameters: claim_amount: "{{ $.claim_amount }}" claimant_history: "{{ $.history.claims_count }}" claimant_rating: "{{ $.history.customer_rating }}" damage_type: "{{ $.damage_assessment.type }}" result: $.opa_result next: CheckOPAResult - name: CheckOPAResult type: Choice choices: - condition: "{{ $.opa_result.auto_approve }}" next: ProcessApproval - condition: "{{ $.opa_result.needs_investigation }}" next: InitiateInvestigation default: DMNEvaluation # Tier 3: DMN for complex cases - name: DMNEvaluation type: EvaluatePolicy resource: urn:cascade:policy:claim_decision_dmn parameters: claim_value: "{{ $.claim_amount }}" claimant_tenure: "{{ $.history.customer_since }}" previous_fraud_indicators: "{{ $.fraud_score }}" result: $.dmn_result next: ApplyDMNDecision - name: ApplyDMNDecision type: Choice choices: - condition: "{{ $.dmn_result.approval == 'Approve' }}" next: ProcessApproval - condition: "{{ $.dmn_result.approval == 'Review' }}" next: ManagerReview default: DenyAndClose - name: ManagerReview type: HumanTask ui: schema: urn:cascade:schema:claim_review_form target: appsmith timeout: 3d next: ProcessApproval - name: ProcessApproval type: Task resource: urn:cascade:activity:approve_and_pay end: true - name: DenyAndClose type: Task resource: urn:cascade:activity:deny_claim end: true - name: InitiateInvestigation type: Task resource: urn:cascade:activity:start_investigation next: WaitForInvestigation - name: WaitForInvestigation type: Wait duration: 14d next: ReviewInvestigation

Policy Definitions

OPA Policy (claims_evaluation):

package claims_policies # Auto-approve low-risk claims auto_approve { input.claim_amount <= 5000 input.claimant_history.claims_count <= 2 input.claimant_history.customer_rating >= 4.5 } # Flag suspicious patterns needs_investigation { input.claimant_history.claims_count > 5 } needs_investigation { input.fraud_score > 0.7 } # Recommendation recommendation = rec { auto_approve -> rec := "approve" needs_investigation -> rec := "investigate" _ -> rec := "review" }

DMN Policy (claims_decision_dmn):

Claim ValueCustomer TenureFraud ScoreApprovalRoute
<= 5K> 2 years< 0.3ApproveAuto
5K-25K> 2 years< 0.5ApproveAuto
5K-25K> 2 years0.5-0.7ReviewManager
25K-100K> 1 year< 0.5ReviewManager
> 100KAnyAnyReviewDirector
Any< 1 year> 0.5InvestigateInvestigation

Advanced Patterns

Pattern 1: A/B Testing Policies

Test two policies simultaneously:

- name: ABTestApprovalPolicy type: Parallel branches: - name: EvaluateControlPolicy type: EvaluatePolicy resource: urn:cascade:policy:approval_control parameters: credit_score: "{{ $.credit_score }}" amount: "{{ $.amount }}" result: $.control_decision - name: EvaluateTestPolicy type: EvaluatePolicy resource: urn:cascade:policy:approval_test parameters: credit_score: "{{ $.credit_score }}" amount: "{{ $.amount }}" result: $.test_decision completion_strategy: ALL next: LogABTest - name: LogABTest type: Task resource: urn:cascade:activity:log_ab_test_results parameters: test_id: "approval_policy_v2" control_result: "{{ $.control_decision }}" test_result: "{{ $.test_decision }}" approved: "{{ $.control_decision.approved }}" # Use control for actual approval next: ProcessApproval

Pattern 2: Policy Chain (Consensus)

Require agreement from multiple policies:

- name: CheckConsensus type: Parallel branches: - name: PolicyA type: EvaluatePolicy resource: urn:cascade:policy:approval_policy_a result: $.policy_a - name: PolicyB type: EvaluatePolicy resource: urn:cascade:policy:approval_policy_b result: $.policy_b - name: PolicyC type: EvaluatePolicy resource: urn:cascade:policy:approval_policy_c result: $.policy_c completion_strategy: ALL next: CheckConsensusResult - name: CheckConsensusResult type: Choice choices: - condition: "{{ $.policy_a.approved && $.policy_b.approved && $.policy_c.approved }}" next: ProcessApproval description: "All policies agree" default: EscalateForReview

Pattern 3: Policy Versioning

Test new policy versions safely:

- name: EvaluateWithVersion type: EvaluatePolicy resource: "urn:cascade:policy:approval_{{ $.test_version | default: 'v1' }}" parameters: credit_score: "{{ $.credit_score }}" amount: "{{ $.amount }}" result: $.policy_result next: RouteByVersion

Usage:

# Use v1 (default) cascade process start \ --workflow EvaluateLoan \ --input '{"credit_score": 720, "amount": 50000}' # Test v2 cascade process start \ --workflow EvaluateLoan \ --input '{"credit_score": 720, "amount": 50000, "test_version": "v2"}'

Performance Characteristics

Latency Comparison

EngineTypicalP95P99Notes
Choice<0.1ms<1ms<5msIn-memory
OPA3ms5ms15msCached policies
DMN20ms40ms50msTable evaluation
LLM (future)500ms2s5sNetwork latency

Throughput

EngineOps/secBottleneck
Choice100K+CPU
OPA20K+Rego compilation
DMN1-2K+Table I/O

Monitoring & Observability

Policy Execution Metrics

# Prometheus metrics cascade_policy_evaluation_duration_seconds # Policy latency cascade_policy_decisions_total # Decision count cascade_policy_errors_total # Evaluation failures cascade_policy_approval_rate # % approved

Audit Trail

Every policy evaluation logged:

{ "timestamp": "2025-10-29T15:45:23Z", "workflow_id": "wf-123", "policy": "urn:cascade:policy:loan_approval", "engine": "opa", "input": { "credit_score": 720, "amount": 50000 }, "result": { "allow_approval": true, "reason": "Score acceptable" }, "latency_ms": 3.2, "decision_id": "dec-456" }

Query audit trail:

# Show all loan policy decisions today cascade audit query \ --policy "loan_approval" \ --since "24h" # Show decisions by result cascade audit query \ --policy "approval" \ --result "approved" \ --count 100

Troubleshooting

OPA Policy Not Found

Error: Policy urn:cascade:policy:my_policy not found

Solution:

# List available policies cascade policy list # Check policy details cascade policy inspect urn:cascade:policy:my_policy # Validate policy syntax cascade policy validate policy.rego

Unexpected Policy Decision

Symptom: Policy returns unexpected result

Diagnosis:

# Check policy with exact input cascade policy test \ --policy urn:cascade:policy:loan_approval \ --input '{"credit_score": 720, "amount": 50000}' # Show policy trace cascade policy trace \ --policy urn:cascade:policy:loan_approval \ --input '{"credit_score": 720, "amount": 50000}'

Performance Issues

Symptom: Policy evaluation slow (>50ms)

Solutions:

  • Profile: cascade perf profile policy
  • Check Rego complexity
  • Consider caching results
  • Move to DMN if complex

Best Practices

DO:

  • Use OPA for most policies (fast, flexible)
  • Test policies thoroughly before deployment
  • Version policies like code
  • Log all decisions for audit
  • Use meaningful policy names (URNs)
  • Document policy logic with comments

DON’T:

  • Hardcode business logic in workflows
  • Mix policy engines unnecessarily
  • Skip policy testing
  • Deploy untested policy changes
  • Store sensitive data in policies
  • Make policies overly complex

Next Steps

Ready to write policies?Policy Development Guide

Need Rego help?OPA/Rego Reference

Want to visualize decisions?DMN Modeling

Production deployment?Policy Deployment Guide


Updated: October 29, 2025
Version: 1.0
OPA Version: v0.50+
DMN Version: v1.3
Production-Ready: Yes

Last updated on