Override & approvals workflow (decision Layer 4 — operational)
The human layer on top of the deterministic decision engine (
decision.md). No credit policy survives contact with reality without exceptions — so overrides exist, but they are delegated-authority-gated, reason-coded, attributable, and immutable in audit history (SME 2026-06-18). Decisions are configurable; overrides are controlled.
Concepts
- Authority level (per user, owner-assigned):
none(default),l1junior,l2senior,l3credit-manager. The org owner is implicitly l3. - Queue item — when a decision needs a human (action
refer, or a borrower-requested review), it enters the tenant’s approvals queue. Underwriters claim, open, and resolve it. - Resolution —
approve/decline/override(to a different action) /escalate(to a higher authority). Every resolution writes an immutable override/event record.
What each level may do
| Level | May resolve | May override a decline? |
|---|---|---|
| l1 junior | documentation/data-quality refers; mark approve-with-conditions met → approve | No |
| l2 senior | refers + moderate policy exceptions (e.g. FOIR breach with co-applicant/collateral) | Yes — non-hard-blocked, moderate |
| l3 manager | major exceptions | Yes — anything not hard-blocked |
Hard-blocked for everyone (never overridable): data-integrity failure (reconciliation fail),
suspected fraud/tampering, and external hard-stops — policy-prohibited borrower types
(blacklist/fraud/sanctions/PEP/restricted-geo) supplied by the caller via external_hard_stop, since
those come from KYC/bureau/compliance systems, not the statement. These can only go back for fresh
data, never be approved.
Override reason codes (SME): acceptable — temporary FOIR breach, strong co-applicant, additional collateral, existing customer relationship, proven additional income. The reason is mandatory and recorded.
Build status — ✅ implemented
- ✅ Authority model —
User.authority_level(migration..._19); owner sets it (PATCH /v1/org/users/{email}/authority, surfaced in Team);src/domain/authority.pyencodes “can this level take this resolution on this decision?” + the hard-block list + reason codes. - ✅ Queue + enqueue —
DecisionQueueItem(+DecisionEvent), migration..._20; the decision endpoint enqueues onqueue=true;GET /v1/queue,POST /v1/queue/{id}/claim. - ✅ Resolve / override / escalate —
POST /v1/queue/{id}/resolve(authority-gated, reason-coded) and/escalate; append-onlyDecisionEventrows (user, level, action, reason, note, ts) — never updated; terminal status written back; audit-logged. - ✅ Console UI — the Approvals queue view (
/queue, list / claim / open / resolve / override / escalate); the decision panel’s “Send to queue”; the Team view’s authority-level control.
Tests: test_authority.py (matrix + hard-block), test_queue.py (enqueue → claim → resolve,
gating, escalate, immutable trail), test_invites.py (authority endpoint).
What the approver sees
The queue detail view gives the underwriter enough to decide without leaving the screen:
- The ask — requested amount / tenure / product.
- Borrower financials — risk band, existing FOIR, core monthly income, monthly obligations (the numbers the decision keys off).
- Eligibility + cost of credit — supportable EMI, max loan, recommended, and
over N months: ₹X repayable = ₹principal + ₹interest(the present-value vs total-repaid gap made explicit). - The “why” — risk flags, the engine’s triggered rule(s), any conditions, adverse-action
reasons, and the
policy_version. - The trail — the ordered claim → escalate → resolve events.
The engine’s reason line is a terse rule citation (“Medium risk, FOIR 45% — needs underwriter judgement”); the specificity comes from the financials + flags shown alongside it, so two superficially similar refers are distinguishable at a glance.
The queue is paginated (server-side, page size 20, actionable items ordered before resolved)
and exportable to CSV (GET /v1/queue/export.csv): one row per decision with the ask, borrower
financials, the engine call, and how it was resolved (level, reason code, note) — for the lender’s
audit file or a push into their loan-origination system. The fuller per-case decision packet (PDF)
remains roadmap item #5.
Audit guarantee
Every queue item carries its originating decision snapshot (action, financials, eligibility incl.
cost of credit, reasons, conditions, adverse-action reasons, flags, policy_version) and an
ordered, immutable list of events (claim → escalate → resolve), each user-attributable,
timestamped, and reason-coded — so the full “who decided what, when, and why” is reproducible for
credit, audit, and regulators.