# Production loan investigation — 8 October 2026

Source: operator's read-only cPanel investigation, approximately 09:49–10:20 UTC, API HEAD `86b6451`. Reads were sequential while background jobs continued. This note records reported production observations and the subsequent local code finding; no production repair was performed.

## Confirmed code bug and fix

The Paystack `direct_debit.authorization.active` handler updated every Paystack loan activation belonging to the borrower. An event for one authorization could therefore revive unrelated revoked attempts or activate a different pending attempt.

The handler now updates only rows matching borrower, provider and the matched payment mandate's saved Paystack reference. A webhook reference is used only when the matched mandate has no saved reference. Without either reference, no loan activation is changed. Existing saved references are preserved rather than overwritten by an unrelated event reference. Regression tests cover matching-reference updates and missing-reference isolation.

This is a confirmed code defect, not proof that it caused each reported production disagreement. Deployment prevents future blanket updates; historical records still need provider identity verification before repair.

## Production findings

- Counts: 498 disbursed, 9 pending, 3 collection_pending, 10 declined, 4 paid.
- The 498 disbursed loans have legacy metadata and no activation links. Missing Mono IDs on these records do not establish recent mandate creation failures.
- Six pending loans have Paystack activations and no Mono IDs. Check employer/provider requirements before treating “No Mono mandate on record” as an error.
- No same-ID Mono activation/payment status conflicts were found after normalizing equivalent lifecycle statuses. No dangling activation links or ownership mismatches were found in the reviewed current-loan scan.
- Loans 524 (borrower 1195), 21 (1056), and 19 (1111) have revoked activations alongside active Paystack payment records. Exact Paystack reference identity remains unverified.
- Collection-pending loans 4 (38) and 18 (948) link cancelled Mono activations; loan 17 (557) links a pending Mono activation. Loan 18 has a separate active Paystack rail, so borrower-level readiness differs from loan-specific readiness.
- Pending loans 527, 526, 523, 521, 520, and 20 have active mandates but no saved payout metadata. This does not prove a failed payout or justify resending one.
- Paid loan 14 has completed payout evidence but its mandate is now revoked. Current readiness does not establish historical readiness at disbursement.
- Two incoming Mono IDs had no local row in either mandate table: `mmc_6ac7620c6e526a960d651852` and `mmc_6ac7667c1f4a341e10156a61`. Ownership remains unknown.
- Mono logs show unsupported-bank-code HTTP 400 failures and transient HTTP 500 errors. They have not been correlated to specific loans.
- Paystack authorization.active audit records sometimes contain provider_active=false. Why those audit values differ from event names/current state remains unresolved.
- Cron configuration and minute-cadence application logs strongly support scheduler invocation. Successful completion of individual mandate jobs remains unverified.

## Next investigation

1. Compare exact Paystack provider references, authorization IDs, customer identity, creation/expiry times and live provider state for loans 524, 21 and 19. Preserve revoked history; do not blanket-reactivate records.
2. Confirm intended collection provider and employer policy for loans 4, 17 and 18 before any linkage or eligibility repair.
3. Trace unmatched Mono IDs through provider request/customer references. Backfill only after ownership is established.
4. Trace approval, BVN, recommendation, affordability and payout gates for the active-mandate pending loans. A successful statement analysis alone does not establish disbursement eligibility; the admin checklist must distinguish current checks from historical milestones.
5. Correlate Mono creation failures with borrower/request references and validate supported bank codes. Inspect job completion/failure evidence separately from scheduler registration.

No production status changes, relinking, notifications, webhook replay, mandate sync or payout retry are authorized by this diagnostic note itself.

## Checklist follow-up and code repairs

The second read-only production audit confirmed 12 unresolved applications with successful selected analyses but no saved approval or payout evidence. Borrowers other than 38 had profile BVN false versus verification-step BVN true despite recorded BVN success. Local source confirmed the shared `markStep` writer never updated the profile; new BVN success now updates both tables atomically. Historical conflicts remain review items rather than bulk changes.

Booking uses the saved monthly recommendation multiplied by tenor and employer cap. The checklist previously compared total principal directly against a different monthly safe recommendation. It now shows the booking cap and separately labels the safe monthly/total recommendation, preserving zero values. Loan 523 fits the reconstructed booking cap but its safe recommendation and 35% after-loan EMI still require review. The raw-versus-safe lending policy has not been silently changed.

The collection check now evaluates current employer-required rails and pinned Mono readiness. Failed/incomplete analysis does not pass merely because a row exists; analyses created after booking are not backdated. Current checks and historical lifecycle outcomes are shown separately. A disbursed status alone does not confirm payment.

New model-based submitted/pending, approved (including auto-approval) and declined transitions capture versioned inputs/checks, analysis ID and observed values, tenor, EMI, employer configuration/update timestamp, actor and available decision reason. Submission snapshots are preserved; historical loans are not backfilled. These are observed transition inputs, not proof that every recorded check was enforced. Existing payout metadata remains the source of transfer status/reference evidence. Direct SQL imports/updates do not produce model event snapshots.

Salary payouts now fail closed for successful analysis with EMI above the existing audit failure threshold of 50% of income after existing loans, or zero remaining income. This applies before external mandate checks and at the transaction claim on bank and wallet payout paths. The 33–50% band remains a review item. Existing saved submission income is used when available; historical loans use pre-booking analysis. Missing historical analysis does not manufacture a decision.

Production loan 520's reconstructed 75.7% EMI therefore requires affordability review before any payout; no production status change, payout retry or borrower notification was performed by these code repairs. Exact mandate matching, unmatched Mono ownership, provider bank-code failures, historical policy and per-job scheduler execution remain investigation items.
