Mapping Audit

The Mapping Audit page is what makes the Migration Importer auditable. Every committed record carries its lineage; this page surfaces that lineage as a searchable, exportable, internal-auditor-friendly view that keeps working long after the migration team has moved on.

Mapping audit
Per-column mapping audit with overrides.
At a glance — Path: MFI Suite -> Migration -> Mapping Audit. Group: read for everyone with MFI / Auditor or higher, write only for system administrators. Records here are immutable and retained for at least 10 years per CBK Prudential Guideline on record retention.

What is audited

Mapping Audit captures three flavours of records: (1) one row per production record created by the Migration Importer, (2) one row per warning acknowledgement, and (3) one row per bulk-edit on staging or post-commit override. Every row references the Source, Run, staging row hash, and the production record id where applicable.

Record typeCapturesTypical volume for a Kenyan SACCO migration
Production lineageSource -> Run -> Staging row -> Production recordSum of committed records across all Runs (~200,000 for a 25k-member SACCO)
Warning acknowledgementRule code, severity, reason, signer, timestamp300-2,000 per migration
Manual overrideStaging or production field edited by hand, before/after value, signer20-200 per migration
Reverse commit eventRun id, reason, signer, downstream-conflict report0-2 per migration

The lineage view on production records

Every production record migrated by the importer exposes a Migration tab on its form. The tab shows lineage details and a link to the Mapping Audit row.

  • Source: 'Tujikomboe — Members — v2'
  • Run: 'TJK-MEM-003' (link)
  • Source key: 'M-00421' (original Loan Performer MemberNo)
  • Staging hash: SHA-256 of the original CSV row content, displayed as the first 12 characters
  • Committed by: Jane Wambui at 2026-01-13 22:04:11
  • Acknowledged warnings on this record: list of (rule_code, reason, signer)
  • Manual overrides on this record: list of (field, before, after, signer, timestamp)

Searching the audit

The Mapping Audit list view supports the filters an auditor will most commonly use.

Question the auditor is askingFilter to use
Where did this member's opening balance come from?Production record id = res.partner X — opens lineage card
Show every member migrated from Loan Performer with KYC_MISSING_LARGE_BALANCE acknowledged.Source.legacy_system = Loan Performer AND Acknowledgement.rule_code = KYC_MISSING_LARGE_BALANCE
List every manual override done by Peter Otieno during migration.Manual Override AND Signer = Peter Otieno
Show the full reverse-commit trail for tenant X.Type = Reverse Commit AND Tenant = X
Show every commit signed off by Florence Achieng.Type = Production Lineage AND Counter-signed by = Florence Achieng

Export formats for auditors and regulators

The audit can be exported for external auditors (KPMG, PwC, Mazars) and for SASRA inspection visits. Three formats are supported.

  • CSV — flat dump of the selected filter, suitable for spreadsheet analysis.
  • PDF — formatted report with header, signatures, source list and exception summary. Used as an exhibit in audit binders.
  • JSON — full structured export including staging hashes. Used by internal data science to reconstruct migration deltas.
Note — Exports are themselves logged in the audit — every export creates a row noting who exported what filter and when. Even auditors leave a trail.

Retention and immutability

Mapping Audit records cannot be edited or deleted by any user, including system administrators, through the normal BridgeERP UI. They can only be modified by a database administrator with shell access — and any such modification triggers a SOC 2 alert and a Slack notification to the audit committee.

  • Retention: 10 years from the date of the Migration Run, or 7 years post account closure for member-specific records, whichever is later. Matches CBK Prudential Guideline section on record retention.
  • Backup: nightly database backup includes mapping_audit; weekly offsite snapshot is taken to a separate S3 bucket retained for the full 10 years.
  • Hash chain: every audit row stores a hash of itself plus the previous row's hash, forming a tamper-evident chain. Auditor can request a chain-verification report at any time.
Warning — Do not assume the database backup alone is sufficient. The hash chain is the proof of immutability for an external auditor; preserve it deliberately.

Worked scenarios

Scenario — Tujikomboe responds to a SASRA inspection in November 2027

Setting: SASRA inspects Tujikomboe 22 months after go-live. The inspector asks: 'Show me every member whose opening balance was acknowledged with a KYC warning during migration, and prove no funds left their account before KYC was completed.'

CharacterRole
Peter OtienoTujikomboe Operations Manager
Florence AchiengTujikomboe Accountant
SASRA InspectorExternal regulator

Timeline

  1. Nov 2027, Day 1, 10:00: Inspector arrives, requests the dataset. (Peter opens Mapping Audit, filters: Source.legacy_system = Loan Performer AND Acknowledgement.rule_code = KYC_MISSING_LARGE_BALANCE.)
  2. Nov 2027, Day 1, 10:05: Filter returns 87 members. (Each row links to res.partner with current KYC state.)
  3. Nov 2027, Day 1, 10:30: Peter exports the 87 to PDF. Each row shows: opening balance, acknowledgement reason ('Bria-led KYC capture plan'), date KYC was completed post go-live, current balance. (PDF stamped with retention chain hash.)
  4. Nov 2027, Day 1, 14:00: Inspector asks: 'Have any of these 87 transacted before their KYC was completed?' (Peter runs a cross-query: filter savings.transaction by member in the 87 and date < member.kyc_completed_at.)
  5. Nov 2027, Day 1, 14:15: Cross-query returns 4 members who deposited (no withdrawals). 0 withdrew before KYC. (Peter prints the cross-query results.)
  6. Nov 2027, Day 1, 15:00: Inspector accepts: deposits without KYC are within risk appetite; the absence of withdrawals before KYC satisfies the inspection. (Inspection closes for this control.)

Outcome — SASRA inspection cleared on this control in under a day, thanks to lineage that survived 22 months. The PDF export becomes an exhibit in the inspection report. Mapping Audit pays for itself in one query.

Reference

Mapping Audit row fields

Every row has these fields. None are editable through the UI.

FieldTypeDescription
idIntegerSequential audit id.
audit_typeSelectionproduction_lineage | warning_ack | manual_override | reverse_commit | export
tenant_idMany2one companyTenant under audit.
source_idMany2one mfi.migration.sourceSource the row originated from.
source_versionCharv1, v2, ... — frozen at time of run.
run_idMany2one mfi.migration.runRun that processed the row.
staging_tableChare.g. mfi.migration.staging.member
staging_hashChar (64)SHA-256 of the original row content.
production_modelChare.g. res.partner
production_idIntegerId of the production record.
source_keyCharNatural key from legacy system.
actor_idMany2one res.usersWho triggered the event.
counter_signer_idMany2one res.usersWho counter-signed (if applicable).
actor_atDatetimeTimestamp.
details_jsonText (JSON)Type-specific payload (e.g. for warning_ack: rule_code, reason, severity).
prev_hashChar (64)Hash of the previous audit row.
this_hashChar (64)SHA-256 of this row's content + prev_hash.

details_json shapes by audit_type

The details_json is type-specific.

audit_typeShape
production_lineage{record_count: 1, two_person: bool, ks_total: float}
warning_ack{rule_code: str, severity: str, reason: str, exception_id: int}
manual_override{field: str, before: any, after: any, scope: 'staging'|'production'}
reverse_commit{reason: str, downstream_conflicts: [...], original_run_id: int}
export{format: 'csv'|'pdf'|'json', filter: dict, row_count: int}

Troubleshooting

SymptomLikely causeFix
Mapping Audit row count is lower than the number of committed records.An older migration ran before the mfi_migration_audit module was installed.Run the back-fill job at Settings -> Technical -> Migration -> Backfill Audit. It walks production records with x_migration_run_id set and creates missing audit rows. Marks them with backfilled=True for transparency.
Hash chain verification report flags row 4,217 as broken.A database admin edited the audit row directly via psql.Investigate via BridgeERP logs and DB audit; never accept an edited row as legitimate. SOC 2 control failure requires an incident response.
Cannot find lineage on a member who I am sure was migrated.Member was matched to an existing partner during Commit rather than created new (match-by-PIN). Lineage is on the matched partner, not a new one.Search by source_key on Mapping Audit, not by res.partner.name.
Exporting PDF for SASRA fails for filters returning more than 50,000 rows.PDF generation is single-threaded and capped to protect the worker.Split the filter or use the CSV export and convert externally; or queue the PDF as a background job (Settings -> Technical -> Migration -> Queued PDF Export).

See also

Was this page helpful?