Execution-time checks
Volatile controls are re-evaluated at send, dial, submit, or post time, so neither immediate nor queued work can act on a permission that has gone stale.
Every permission in servicing has a shelf life. Between deciding to call somebody and dialling them, a borrower can revoke consent, file for bankruptcy, or pay in full. So execution asks “may this exact action run now?” and answers it at the moment of acting: the owning operation puts the question itself, and scheduled voice work adds a dispatch gate and a dispatch-bound dial authorization, because a queued call waits exactly where the facts move.
Placing a call: the dial API
curl -X POST "$BASE/v1/servicing/calls/outbound" \
-H "Authorization: Bearer $TOKEN" \
-H "LendEasy-Tenant: demo-lender" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: hbr-call-attempt-2027-05-01-1" \
-d '{
"caseId": 1001,
"customerRef": "412",
"loanRef": "7204",
"contactPointRef": "cp_MAYA_MOBILE_1",
"reasonCode": "COLLECTIONS_FOLLOW_UP"
}'
The dial API does not accept or trust a caller-provided allowed flag. It creates the interaction evidence first, resolves the latest contact facts, makes the execution decision, and only then invokes the telephony gateway — the response is always 201 with the persisted attempt, whose status is DIALING, BLOCKED, or FAILED with the compliance decisionId. A future-dated contact is booked as a scheduled callback on the case, and its start-call command runs this same gate again at dial time; there is deliberately no scheduled email or SMS send to drift stale.
Nothing commits on a version that moved
Checking and then acting leaves a gap, and a gap wide enough to read a fact in is wide enough for that fact to change. So the executor binds what it decided on:
- normalized action payload hash;
- customer, loan, case, instrument, schedule, and restriction versions used;
- policy bundle and rule versions;
- decision ID and expiry;
- idempotency key and attempt number;
- provider request or authoritative action reference.
If a bound version changes before commit, execution fails with a conflict or new decision—not a stale side effect.
Blocked attempts are evidence
A blocked attempt is not a sent interaction and should not count as provider delivery. It still records the intended action, pre-check, current blocking fact, rules, time, actor, and safe next step. Without that record, a control that fired and a control that never ran look identical from the outside — silence proves nothing, and the whole value of a gate is being able to show it held.
A mass action is many small decisions
A mass action fans out into individually checked actions, and each customer receives a fresh evaluation at their own execution time. One can be allowed, one blocked, one missing facts, and one already resolved; the customer record result preserves every branch. A population evaluated once at the top would carry one borrower’s permission to nine thousand others.