MigrationGuard: architecture and verification
How generated schema catalogs, human-approved mappings, and an exact-arithmetic engine move a ledger from QuickBooks Online to NetSuite — with every claim in this paper stated against its evidentiary basis.
Executive summary
Financial migrations rarely fail loudly. They fail quietly: an enumeration validated against the wrong value set, a boolean copied without noticing the target stores its inverse, a column summed in floating point that drifts a cent at a time. Each passes a demo. Each corrupts a ledger.
MigrationGuard is built on one refusal: nothing in the pipeline may rest on an assumption that cannot be traced to a published vendor schema or measured from the customer's own data. Field names and permitted values are generated from schemas Intuit and Oracle publish. Requiredness — which those schemas deliberately cannot supply — is measured by profiling, never defaulted. Every mapping is approved by a named human, with the machine's confidence and reasoning shown. And after the load, the engine recomputes each business figure from the source and reads the same figure back from NetSuite, producing a reconciliation an auditor can read.
This paper describes the architecture as built and verified and states plainly which components remain in build.
The problem
Silent corruption beats loud failure
A crashed migration is recoverable; a plausible one is not. During MigrationGuard's
own build, two defects of exactly this class were caught before a line of transform
code existed. A specification assumed QuickBooks has five account types; Intuit's
schema defines sixteen, and the five-value rule would have rejected every bank
account, every receivables and payables control account, and all income — surfacing
only later as an inexplicable trial-balance failure. The same specification mapped
Active to isActive; NetSuite has no such field. It stores
isInactive, the logical inverse, and a straight copy would have
deactivated every migrated record.
Documentation you can't machine-read isn't a source of truth
Intuit's per-entity reference pages are a client-rendered application: an HTTP fetch returns a loading stub with no field content. Oracle's REST field catalog is rendered per account, behind authentication. A pipeline whose correctness depends on prose pages that cannot be fetched, diffed, or versioned is a pipeline built on transcription — and transcription is where invented field names come from.
The schema cannot tell you what's required
Across the 2,309 fields in Intuit's schemas, only 15 carry a minimum-occurrence constraint. One schema serves QuickBooks Online and Desktop, and serves create, update and read payloads alike, so it is permissive by construction. Requiredness must therefore be established empirically, from the tenant being migrated — which is why profiling is a gate in this architecture, not a convenience.
Architecture
Generated catalogs, not transcribed ones
The QuickBooks catalog is extracted from the XSD schemas Intuit publishes to generate its own official V3 SDK — 81 entities, 104 enumerations, 2,309 fields, with inheritance resolved and Intuit's embedded business-rule annotations captured as citable data-quality seeds. The NetSuite catalog is extracted from Oracle's published SuiteTalk schemas — 189 record types, 273 enumerations, 7,117 fields. Both are produced by scripts, so a vendor schema release regenerates the catalog rather than triggering a re-transcription.
Inference that shows its work
Every source column receives a proposed target with a confidence band — curated, high, medium, low, or none — and a stated reason. Confident matches arrive pre-approved; everything else arrives unticked, so the default action is review, not acceptance. Curated hints are validated against both catalogs at load time: a hint naming a field that does not exist is dropped and reported, never silently applied. Enumerated columns additionally receive a value translation; all sixteen QuickBooks account types resolve to their NetSuite counterparts mechanically, and any value left untranslated rejects its records rather than guessing.
A high-throughput ETL engine that refuses
Monetary amounts are integer minor units with per-currency scale — two decimals for
USD, zero for JPY, three for KWD — parsed digit-by-digit with no floating point,
every operation overflow-trapped. A value with more precision than its currency
permits is rejected, not rounded: the caller must decide, in writing, what rounding
is acceptable. The engine is built as a batch ETL core engineered for throughput —
batched writes, configurable rate limiting and concurrency, and
checkpointed resume so a large load restarts where it stopped rather than from row
one. Consistent with this paper’s policy, measured throughput figures will be
published with their benchmark basis once runs against live endpoints exist. The configuration loader enforces the same discipline upstream — a
mapping whose requiredness is still UNVERIFIED stops the run.
Reconciliation as a deliverable
After the load, eight checks recompute from source and read back from NetSuite: row counts, amount totals, receivables by customer, payables by vendor, the trial balance, chart-of-accounts structure, dangling references, and an itemised exception report. The default monetary tolerance is zero. A tolerance above zero must be justified in writing, because a cent of accepted drift per row across a hundred thousand rows is a thousand dollars no one can explain.
Verification
MigrationGuard publishes verification evidence, not performance benchmarks — none have been run yet, and this paper will not invent them. Every figure below states its basis.
| Measure | Result | Basis |
|---|---|---|
| Engine test assertions | 55 / 55 pass | GCC 13.3 · -Wall -Wextra -Werror -Wconversion · Jul 2026 |
| INI contract, cross-language parity | 7 / 7 fixtures identical | High Fidelity structural diff parser |
| Account-type value translation | 16 / 16 at high confidence | Intuit AccountTypeEnum ↔ Oracle AccountType |
| QuickBooks catalog | 81 entities · 2,309 fields | Intuit V3 SDK XSDs, v6.5.5, commit c4d5dfa |
| NetSuite catalog | 189 records · 7,117 fields | Oracle SuiteTalk WSDL v2019.1 |
| Exactness property | 10,000 × 0.01 = 100.00 | test_core.cpp — false in IEEE-754 double |
| Unverified-required interlock | run refused | generated 77-mapping config, gate demonstrated in test |
Reproduction: every artifact above is generated or compiled from the repository; catalogs regenerate from vendor schemas by script.
Deployment model
The engine writes to NetSuite over SuiteTalk REST. This is not a preference: Oracle's own schedule names 2025.2 as the last planned SOAP endpoint and removes SOAP at 2028.2, at which point existing SOAP integrations stop working. The SOAP schemas are used at build time only, as the sole public machine-readable catalog of the NetSuite record model; no SOAP client ships in the engine.
Two items are disclosed as open in the project's own unverified-items register: target field names remain provisional until reconciled against the customer account's REST metadata catalog, and account-level rate limits are read from the account, never assumed. Engineering that discloses its open questions is the same engineering that caught the sixteen account types.
About Pacific Data Integrators
Pacific Data Integrators builds data platforms and migration accelerators for enterprise systems. MigrationGuard™ for QuickBooks Online → NetSuite joins PDI's product family alongside its SAP ECC to S/4HANA Accelerator.
Bring your chart of accounts.
A briefing runs against your entity list, not a demo set.
Request a briefing