Compare a fictional membership migration
Choose a supplied pair, or two JSON datasets following the method. Checks cover users, purchases, progress and job references. An invariant failure remains unresolved; equal row totals do not prove preservation. Each file is limited to 256 KiB and 1,000 records per table. Files stay local.
Choose both files. JavaScript is required.
Results contain at most 100 validation issues and 500 differences. Any limit or unresolved input is explicit. Reload clears inputs.
Unchanged control
Valid records survive unchanged.
{
"status": "compared",
"afterIssueCodes": [],
"summary": {
"added": 0,
"removed": 0,
"changed": 0
},
"differences": []
}Missing job
A valid dataset can still omit a required source record.
{
"status": "compared",
"afterIssueCodes": [],
"summary": {
"added": 0,
"removed": 1,
"changed": 0
},
"differences": [
{
"type": "removed",
"table": "jobs",
"id": "j2",
"before": {
"id": "j2",
"userId": "u2",
"purchaseId": "p2",
"status": "queued"
}
}
]
}Duplicate identifier
Equal counts conceal a duplicate and lost identity.
{
"status": "unresolved",
"afterIssueCodes": [
"DUPLICATE_ID"
]
}Broken user join
Equal counts conceal a purchase pointing to an absent user.
{
"status": "unresolved",
"afterIssueCodes": [
"MISSING_USER",
"MISSING_ENTITLEMENT",
"JOB_OWNER_MISMATCH"
]
}Job owner mismatch
Existing identifiers can still form the wrong relationship.
{
"status": "unresolved",
"afterIssueCodes": [
"JOB_OWNER_MISMATCH"
]
}Progress without a purchase
A progress course must match a purchase for the same user.
{
"status": "unresolved",
"afterIssueCodes": [
"MISSING_ENTITLEMENT"
]
}Changed price and progress
Exact numeric field differences remain visible.
{
"status": "compared",
"afterIssueCodes": [],
"summary": {
"added": 0,
"removed": 0,
"changed": 2
},
"differences": [
{
"type": "changed",
"table": "purchases",
"id": "p1",
"field": "amountCents",
"before": 1200,
"after": 120
},
{
"type": "changed",
"table": "progress",
"id": "g1",
"field": "percent",
"before": 25,
"after": 0
}
]
}Equal-count record replacement
Two job totals match while one id disappears and another appears.
{
"status": "compared",
"afterIssueCodes": [],
"summary": {
"added": 1,
"removed": 1,
"changed": 0
},
"differences": [
{
"type": "removed",
"table": "jobs",
"id": "j2",
"before": {
"id": "j2",
"userId": "u2",
"purchaseId": "p2",
"status": "queued"
}
},
{
"type": "added",
"table": "jobs",
"id": "j9",
"after": {
"id": "j9",
"userId": "u2",
"purchaseId": "p2",
"status": "queued"
}
}
]
}Download the standalone practice kit
What the synthetic kit checks
Each dataset has users, purchases, progress, and jobs. Every record has an exact string identifier. Purchases and progress must refer to a known user; progress needs a purchase for the same user and course; job references must agree with their purchase. The downloadable method defines the complete invariants.
Two exports with equal row counts can still contain a missing ID or a broken reference. The supplied cases deliberately seed these failures. Unknown or invalid inputs remain unresolved rather than receiving invented matches.
Use it as one layer of an acceptance rehearsal
Keep untouched before and after exports. Run the known cases first, then adapt only the schema and invariants you have explicitly reviewed. The original fixtures are not a real membership platform export. Passing this model does not verify passwords, email, entitlements, schedules, consent, or actual destination behavior.
The standalone practice kit includes a Node runner and expected outcomes. It needs no account or network connection.
Sources & verification
Product details and prices can change. Check the linked provider before buying.
- PostgreSQL documentation: constraints and foreign keys Accessed 2026-09-14
Sources link directly to providers. Product buttons may use separately labeled affiliate links. Read our disclosure.