Policy Engine
The policy engine evaluates JsonLogic rules against incoming transaction data and resolves conflicts using a severity ranking.
Rule storage
Rules live in data/active_policy.json. The file is loaded on each API request — no restart needed to deploy a new policy.
Format: a JSON array of rule objects.
[
{
"if": {"and": [
{"==": [{"var": "device_is_emulator"}, true]},
{">": [{"var": "geo_velocity"}, 500]}
]},
"action": "REQUIRE_VIDEO_ID"
},
{
"if": {"<": [{"var": "typing_entropy"}, 1.0]},
"action": "REQUIRE_MFA"
}
]
Each rule object must have two keys:
| Key | Type | Description |
|---|---|---|
if |
JsonLogic expression | Condition evaluated against the request payload |
action |
string | One of the five action constants below |
Actions and severity
When multiple rules fire on the same transaction, the highest-severity action wins.
| Action | Severity | Meaning |
|---|---|---|
DECLINE |
5 | Hard block — transaction rejected |
REQUIRE_VIDEO_ID |
4 | High friction — synchronous video identity check |
REQUIRE_MFA |
3 | Medium friction — step-up authentication |
DELAY_4H |
2 | Soft hold — transaction queued for manual review |
APPROVE |
1 | Pass — no additional friction |
If no rules fire, the action defaults to APPROVE.
Decision outcome
The decision field in the response is derived from the winning action:
| Action | Decision |
|---|---|
DECLINE or REQUIRE_VIDEO_ID |
BLOCK |
REQUIRE_MFA, DELAY_4H, or APPROVE |
PASS |
The boundary is SEVERITY_MAP > 3 → BLOCK, implemented in src/policies/evaluator.py.
Missing fields
If a rule references a payload field that is not present:
- A
WARNINGis logged:Missing field in payload during rule evaluation: <field> | rule=<rule> - That rule is skipped
- The remaining rules still evaluate normally
- The request never fails due to a missing feature field
This allows adding new signals to policies before they are available in all callers.
Policy signature
Every call to evaluate_policy() computes a SHA256 signature over the rule JSON:
The signature appears in metadata.policy_version in every response, enabling exact reconstruction of the rule logic at audit time.
JsonLogic operator reference
The policy engine uses src/utils/json_logic_compat.py — a Python 3 reimplementation of the JsonLogic spec.
| Operator | Example |
|---|---|
| Variable lookup | {"var": "amount"} |
| Equality | {"==": [{"var": "device_is_emulator"}, true]} |
| Greater than | {">": [{"var": "geo_velocity"}, 500]} |
| Less than | {"<": [{"var": "typing_entropy"}, 1.0]} |
| Logical AND | {"and": [<expr1>, <expr2>]} |
| Logical OR | {"or": [<expr1>, <expr2>]} |
| Logical NOT | {"!": [<expr>]} |
See Policy Format Guide for full authoring examples.