-
Notifications
You must be signed in to change notification settings - Fork 0
ADR 0008 Decision JSON DSL
Tiana_ edited this page May 30, 2026
·
1 revision
Status: Accepted Date: 2026-04-25 Decider: Maintainer
FinCore's Decision Engine evaluates rules deterministically over input contexts to produce decisions (APPROVE/REJECT/REVIEW). Use cases:
- Pre-payment risk screening
- AML transaction monitoring
- KYC routing
- Pre-credit approvals (no ML)
- Feature flagging / segmenting
Choices for rule expression:
- JSON DSL - declarative, structured, machine-parsable, version-controllable
- Embedded scripting (JavaScript, Groovy, Python) - flexible but unsafe and slow
-
Custom DSL (like Drools' .drl, Formance's
numscript) - bespoke syntax, learning curve - DMN (Decision Model and Notation) - OMG standard, XML-heavy
- Drools KIE - full BRMS, heavyweight
For modern fintech that wants:
- Operators (compliance, risk) editing rules without code deploys
- Rules in version control / GitOps (Compliance-as-Code)
- Audit-friendly evaluation logs
- LLM-friendly synthesis ("translate this English to a rule")
JSON wins. It's the closest format to "data" - readable, parseable by any tool, valid in every language.
Use a typed JSON DSL with the following grammar:
{
"id": "rule-uuid",
"ruleSetId": "payment-screening",
"priority": 100,
"terminate": false,
"definition": {
"conditions": {
"all": [
{ "field": "amount.amount", "op": ">", "value": 10000 },
{ "field": "destination.country", "op": "in", "value": ["NG", "PK", "RU"] },
{
"any": [
{ "field": "user.ageDays", "op": "<", "value": 90 },
{ "field": "user.kycStatus", "op": "!=", "value": "APPROVED" }
]
}
]
},
"action": {
"decision": "REVIEW",
"reason": "high_amount_high_risk_country_or_unverified"
}
}
}Operators supported:
- Comparison:
=,!=,<,<=,>,>= - Set:
in,not_in,contains - Pattern:
matches(regex),starts_with,ends_with - Existence:
is_null,is_not_null - Logical:
all,any,none(nested)
Field paths use dotted JSONPath-lite (amount.amount, user.kycStatus). Resolved against input context object at evaluation time.
Rules are validated server-side at insert time:
- Schema-level: structural (every condition has
field,op,value; logical wrappers have valid children) - Type-level:
opmatchesvaluetype (>requires numeric,inrequires array) - Reference-level:
fieldpaths exist in the declaredRuleSet's context schema
- Operator-editable: a non-developer compliance officer can write rules with a JSON editor
- Version-controllable: rules in git, PR reviews, GitOps deployment (Compliance-as-Code Killer Feature)
- LLM-friendly: GPT/Claude can synthesize rules from natural language easily
-
Audit-friendly:
decision_logsstores the matched rule's full JSON for replay - Type-safe: strict server-side validation rejects malformed rules at write time
- Cross-language: any language can serialize/deserialize JSON; rules can be embedded in YAML, environment configs, etc.
- Testable: rule + context = deterministic decision, trivially unit-testable
- Verbose: deeply nested rules get noisy. Mitigated by tooling (rule visualizer in operator UI, Phase E v0.4)
- Limited expressiveness: complex math (e.g., velocity over time windows) needs context preparation outside the engine
- No turing completeness: can't loop, can't recurse. By design - keeps evaluation fast and predictable.
- DMN proponents will critique. DMN is great for some industries; for fintech it's overkill (and the XML hurts)
- Drools veterans may want full BRMS. Their use case is not v0.1's target.
- Rejected: security risk (sandboxing JS is hard)
- Rejected: non-deterministic timing (engine pauses for GC)
- Rejected: harder to log "what evaluated to what"
- Rejected: heavyweight, requires KIE Workbench / Guvnor for editing
- Rejected: alien syntax for Kotlin/Java natives
- Rejected: pulls in lots of dependencies
- Genuinely strong for complex BRMS use cases - out of v0.1 scope
- Considered: standardized, tooling exists (Camunda DMN Modeler)
- Rejected: XML, too heavy for our use cases
- May add DMN compatibility layer if customer demand emerges (Y2)
- Considered: more human-readable than JSON
- Rejected: less ecosystem support; JSON wins for API serialization
- Note: rules can be stored as YAML in git (Compliance-as-Code) and converted to JSON server-side. We allow both ingest formats.
- Considered: typed, fast, sandboxed
- Rejected: less familiar to fintech developers
- Rejected: requires CEL parser dependency in every language
- Considered: most expressive
- Rejected: requires recompilation to add rules; fails operator-edit goal
- Rejected: not language-agnostic (can't use TypeScript, Python adopters)
- Schema validation: 100% of valid rules accepted, 100% of malformed rules rejected with field-level error
- Performance: 100-rule evaluation p99 < 10ms, 1000-rule evaluation p99 < 50ms
- Determinism: same input + same active rules → same output, byte-for-byte
- LLM synthesis: feed natural language, parse output, verify it passes server-side validation in 95%+ of generated cases
- Architecture-Services#decision-engine - engine design
- Overview
- Services
- Data Model
- Domain Model
- Event Flow
- Security
- Observability
- Resilience
- SLA / SLI / SLO