WTWilly Tai
Case studyAI Governance & AssuranceLive demo · synthetic data

Field-level proof that the migration landed, before anyone says go-live.

SAP ECC mainstream maintenance ends on 31 December 2027, and every migration programme ends the same way: someone must attest that GL balances, open items and vendor masters arrived in S/4HANA complete and unchanged. Today that attestation rests on throwaway spreadsheets that auditors distrust and that vanish before the ICFR walkthrough. Migration Recon Gate replaces the spreadsheet with a control. Deterministic code reconciles every batch in three tiers, ending in per-record SHA-256 hashes over canonicalised field-level data. The model layer may only locate the differing records and quote both verbatim. A named migration lead dispositions every mismatch, and the go-live button is a code decision that stays dead until every wave is signed.

3,343
records hashed at field level
Every row on both sides of nine batches.
5/5
seeded mismatches caught
Row counts, control totals, then per-record hashes.
2 of 5
invisible to control totals
A transposed date and a colliding key change no total.
0
go-lives before disposition
The gate opens only when a named human has signed both waves.

Synthetic extracts · 1,672 source rows vs 1,671 target rows across two waves · seeded generator + deterministic engine → identical hashes and verdicts on every run

01 · The deadline

The control environment is weakest at exactly the moment the books change systems.

SAP committed in 2020 to mainstream maintenance for Business Suite 7 until the end of 2027, and the analysis brief behind this demo puts migration completion at roughly 30% of the installed base. That arithmetic produces a 2026 to 2027 crunch in which thousands of finance functions will move their entire ledger between systems under time pressure, with contractors doing the loads and the year-end audit arriving months later.

The proving step is where the discipline collapses. The Basis team extracts ECC tables, a consultant runs migration cockpit loads in waves, and then someone exports both sides to Excel and compares row counts and a few control totals. Mismatches get explained verbally in the cutover war room. The spreadsheet is overwritten by the next wave's version. Go-live is declared on a status slide, and at year-end the auditor asks for migration completeness evidence and receives a screenshot. Data-migration completeness and accuracy is a standard ITGC and ICFR focus area in any ERP change, and the standard evidence for it is a file nobody can re-execute.

02 · Why the obvious AI fix fails here

A model that explains a variance it cannot exhibit is writing fiction into the audit file.

The tempting move is to hand both extracts to a language model and ask whether the migration reconciled. Every property that makes migration assurance hard is a property models lack:

So the allocation is strict. Code owns the verdict on every batch. The model layer earns one narrow job: for records the hashes have already proven different, drill to the pair, quote both sides verbatim, and name a cause only when the quoted values prove the pattern mechanically. In this demo that layer is a deterministic stub, stated on screen, so the whole run reproduces exactly; the cage around it is what a real LLM would live in.

03 · The trust boundary, drawn through a cutover

Three tiers decide, the explainer quotes, one person signs, and the gate is code.

Each load batch is a pair of extracts: an ECC table on one side and its S/4HANA destination on the other, with the field mapping declared as data and applied in code (vendor numbers become Business Partner keys, dates become ISO, amounts become fixed two-decimal strings). Tier 1 compares row counts. Tier 2 compares control totals by company code or account group. Tier 3 canonicalises every record, hashes it with SHA-256, and compares the full multisets, then pairs the leftovers by business key to locate each difference at field level. A batch passes only if all three tiers match, and that verdict belongs to code alone.

MIGRATION RECON GATE · ONE BATCH, INSTRUMENTED ECC extract SKA1 · LFA1 · BSIK · GLT0 S/4 side: ACDOCA · BUT000 DETERMINISTIC · THE VERDICT T1 row counts T2 control totals T3 per-record SHA-256, all rows mapping applied in code · pass = all three trust boundary Explainer locate + phrase only quotes checked in code else: cause not determinable Human gate disposition each mismatch sign each wave · named GO-LIVE GATE · CODE DECISION opens only when every wave is signed and every mismatch carries a named disposition HASH-CHAINED LEDGER · REPLAYABLE extract hashes · mapping version · every verdict, quoted explanation, disposition, sign-off · agent ID + human ID
One batch, instrumented: the verdict lives in code, the explainer quotes or abstains, and the gate answers to signatures in the chain.

The explainer's honesty rule is mechanical. It may assert "trailing zero dropped" only because the target amount is exactly the source amount divided by ten; it may assert "day and month transposed" only because the two date strings swap their middle segments; it may assert "deduplicated in Business Partner conversion" only because a second source record with identical name and city exists in the extract and its converted twin does. Where no such test holds it must print cause not determinable from extracts and quote the two values anyway. A code check confirms every quoted value is literally present in the extracts before the explanation renders.

04 · The decision function

Pass or fail is a conjunction of three comparisons, so it is written as one.

This is the shipped decision function, condensed. Nothing downstream of it, model output included, can change what it returns.

// A batch passes only if all three tiers match. The mapping is data,
// applied in code; canonical records are sorted-key strings, hashed whole.
function reconcileBatch(batch) {
  var srcC = batch.src.map(function (r) { return canonSource(r, batch.map); });
  var tgtC = batch.tgt.map(function (r) { return canonTarget(r, batch.map); });

  var t1 = { pass: srcC.length === tgtC.length };          // tier 1: counts
  var t2pass = totalsMatch(controlTotals(srcC, batch.ctl),  // tier 2: totals
                           controlTotals(tgtC, batch.ctl));

  // tier 3: per-record SHA-256 over every canonicalised row, then pair
  // the leftovers by business key to locate each difference field by field
  var mismatches = locateDifferences(srcC, tgtC, batch.map);

  return {
    srcHash: sha256(srcC.map(recordHash).sort().join("")),
    tgtHash: sha256(tgtC.map(recordHash).sort().join("")),
    pass: t1.pass && t2pass && mismatches.length === 0
  };
}
05 · A worked cutover

Six cards hash green, one flips red, and the button at the top refuses to wake.

Load Wave 1 and the three master-data batches reconcile clean: 580 source rows, three exact hash matches, one signature. Load Wave 2 and six batch cards hash themselves in sequence until two flip red. The engine locates exactly five differing records among 1,092 source rows and the explainer handles each on its own evidence:

Two of the five change no row count and no control total, which is the argument for tier 3 in one sentence: the checks migrations actually run today would have passed them. The go-live button sits grey through all of it, its label counting the undispositioned mismatches. Remediate a record and the batch reloads and re-reconciles clean, with the new hash in the ledger. Accept a difference and the ledger takes your rationale and your name. Sign both waves and the gate opens; the click that declares go-live is the last entry in the chain.

Why the failures are seeded where they are: a migration fails in transformation, and transformation errors preserve counts and often preserve totals. Field-level hashing is the only tier that treats the record, all of it, as the unit of proof.
06 · The numbers, and their limits

True by construction, and stated as such.

The extracts are synthetic and the five mismatches exist because I seeded them into a deterministic generator; the app says so on screen. No public ECC-to-S/4 extract pair exists, so the shapes mimic real, named SAP structures (SKA1, LFA1, KNA1, BSID/BSIK on the source side; ACDOCA and BUT000 on the target), and what changes shape follows SAP's own simplification list for the Business Partner conversion. What the demo proves is the control architecture: 100% of rows reconciled at field level rather than sampled, all five differences located to the exact field, every disposition named, and go-live provably blocked until the last one.

The limits are real. Demo scale is hundreds of rows per batch and 3,343 records in total; a real migration moves billions, and while the pattern scales, the browser does not. Delta and near-zero-downtime loads, unicode conversion and custom Z-tables are out of scope. And browser-side hashing proves that the extracts given to the app reconcile with each other; it cannot prove the extracts faithfully represent the source system, which remains an ITGC matter about extraction controls, and the app says that too.

07 · See it run

Load the waves, chase the red cards, and try to go live early.

Watch the batch cards hash themselves, open a red one to see the source and target records side by side with the differing field highlighted, disposition all five mismatches, sign both waves, and only then watch the go-live button come alive. Then tamper with the ledger and watch Verify locate the exact broken entry, and hit Replay to re-execute the reconciliation the way a year-end auditor would.

▶ Open the live demo Runs entirely in your browser · synthetic data · seeded, reproducible run

The evidence should outlive the war room.

Migration sign-off is the one control decision a finance function makes while its control environment is packed in boxes. Every habit that makes the close auditable, versioned evidence, named approvals, re-performable checks, tends to dissolve for exactly the weeks the ledger is in transit, and the year-end audit arrives after the consultants have left. This build is my answer to that gap, and it reflects how I approach agentic AI in regulated finance generally: put the arithmetic beyond argument, confine the model to quoting what it can exhibit, and make the consequential click impossible until a named person has answered for every exception. S/4 programmes will be signed off thousands of times before the end of 2027. The artefact worth keeping from each one is a chain the auditor can re-run.