Skip to Content
ProductOverride workflow

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), l1 junior, l2 senior, l3 credit-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.
  • Resolutionapprove / decline / override (to a different action) / escalate (to a higher authority). Every resolution writes an immutable override/event record.

What each level may do

LevelMay resolveMay override a decline?
l1 juniordocumentation/data-quality refers; mark approve-with-conditions met → approveNo
l2 seniorrefers + moderate policy exceptions (e.g. FOIR breach with co-applicant/collateral)Yes — non-hard-blocked, moderate
l3 managermajor exceptionsYes — 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

  1. Authority modelUser.authority_level (migration ..._19); owner sets it (PATCH /v1/org/users/{email}/authority, surfaced in Team); src/domain/authority.py encodes “can this level take this resolution on this decision?” + the hard-block list + reason codes.
  2. Queue + enqueueDecisionQueueItem (+ DecisionEvent), migration ..._20; the decision endpoint enqueues on queue=true; GET /v1/queue, POST /v1/queue/{id}/claim.
  3. Resolve / override / escalatePOST /v1/queue/{id}/resolve (authority-gated, reason-coded) and /escalate; append-only DecisionEvent rows (user, level, action, reason, note, ts) — never updated; terminal status written back; audit-logged.
  4. 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.